If you don't allocate memory in the first place, Rust also looses a lot of appeal though, because dynamic memory management is often the root cause of memory corruption issues in C and C++ (while working with a upfront defined static memory layout avoids some typical footguns, because of things like stable memory locations throughout the lifetime of the application). I'm aware that Rust also helps to make accessing "static" memory safe, but Rust's safety guarantees are much more important with complex memory management patterns.
While it is true that dynamic memory management in C and C++ is a source of memory corruption issues, the other great source is precisely the reluctance of many C and C++ programmers to use dynamic memory allocation instead of buffers of statically-defined sizes. E.g.
1. Functions which convert a number to a string, where the target string is a byte buffer passed into the function as a pointer argument. This allows C developers to pass pointers to statically-sized arrays of bytes, which leads to buffer overflows. (Inb4 someone shows up with "but that's the programmer's fault for not using an array that's big enough".) Example: strcpy.
2. Functions which return pointers to static storage, which makes them thread-unsafe, and even in single-threaded cases is error-prone, when you call one such function multiple times in succession (aliasing). Example: gmtime.
Both cases are very prominent in the standard library.
If instead functions allocated memory dynamically, then they would avoid buffer overflow, because size would be handled exclusively within those functions, allocating as much memory as is needed, no more, no less; instead of forcing the programmer to worry about the size at every call-site. They could also be easily made thread-safe and less error-prone (eliminating aliasing), provided that the allocator is thread-safe.
Another issue with the first case is that it leads to uninitialised variables. Often a pattern of "bool NumToString(int n, char* dst, int size);" is employed instead of "Optional<std::string> NumToString(int n);". This invites the possibility of using "dst" without checking the return value, which says if the conversion succeeded. In this case dst is most likely uninitialised, and definitely incorrect. An "Optional<std::string>" solves both issues.
All true, but on the other hand you have much more dangerous (because hidden) memory corruption problems in C++ caused by hidden dynamic memory management in the C++ stdlib like iterator invalidation, or dangling std::string_views. And IMHO those cases where C++ suddenly pulls the rug from under your references are much more dangerous than working with a static memory layout in C.
The C stdlib string API is rubbish of course (along with most other C stdlib APIs). But at least it's quite simple in C to ignore the stdlib (and use better 3rd-party-libs instead) without loosing important language features.
The experience with CVEs in MISRA-C(++) vs Java and Ada embedded deployments, proves that there is still room for improvement by pushing C and C++ completely out of the picture.
Rust also helps prevent many types of concurrency bugs, and the type system is much more expressive than c's or c++'s, which can lead to cleaner code as well as preventing bugs like null references.