> C++ cannot give the same guarantees for normal code
Can you give me example of a normal C++ code where compiler and/or language will not guarantee safety? Let's put aside UBs, because they are everything but normal code.
Undefined behavior is absolutely easy to trigger in normal C++ code, and being able to prevent it is a massive safety benefit. Ignoring UB while talking about safety is like ignoring car crashes when judging whether seat belts give any benefit.
An easy way to trigger undefined behavior in C++ is by using a pointer after the thing it's pointing to has been freed. Doing this in a process that takes any user input can easily create a vulnerability that allows an attacker to inject code into the process. There are many ways to accidentally do this while thinking you're safe. A pointer to an item inside a data structure like a vector or hash table is dangerous if you use it after some items may have been inserted into the data structure, because inserts can cause the data structure to reallocate and expand its backing memory. This mistake can easily go unnoticed for a long time because only a small fraction of inserts will cause that. This mistake isn't possible to make in safe Rust.
Please define “normal”. Normal C++ for me runs the gamut of looking a lot like C to be a template metaprogramming monstrosity.
Almost all C++ code interacts with bare pointers at some point. And that’s just one of the places where you get into issues with safety.
You can also run into problems with iterators being invalidated: your iterator into a std::vector is no longer valid after a call to push_back().
Anything that stores a reference and can exist after the lifetime of the object, really. Lambdas are a problem here. But so are instances with reference members.
I would avoid saying that it's "Rust" that "gives guarantees". It paints Rust as this magical thing that will solve anything. My preferred explanation is that Rust provides better tools to build wrappers that can't be misused. The idea is to solve hard problem once, and reap the benefits many times. But it all depends on wrapper author. In that regard, it is perfectly possible to write horrible Rust code.
> I would avoid saying that it's "Rust" that "gives guarantees".
Why would you avoid saying that? Rust does give guarantees: about memory safety and concurrency primarily, but also regarding the lack of undefined behavior.
> It paints Rust as this magical thing that will solve anything.
It does not, the above are not magical they are just challenging problems (although at some point they may have been deemed impossible problems and hence magical, I don't know)
> My preferred explanation is that Rust provides better tools to build wrappers that can't be misused.
"wrappers that can't be misused" sounds a LOT like it "gives guarantees".
The biggest wrapper that gives guarantees is the standard library, and usually, when people say that Rust does not do something, they have standard library in mind. For example, standard library made the choice to hide panics in out-of-memory situations. That does not mean you can't write your own version of relevant structures to gain different guarantees. I like to highlight the actual strengths of Rust (as a tool) instead of particular implementation details, especially when we are talking about situation (kernel) where Rust is used without its standard library.
Defining "normal" code as "not having UB" is quite disingenuous though, isn't it?
Iterating over a vector while adding elements for example looks normal, but isn't generally safe, unless you know to pre-allocate enough memory.
An easy way to trigger undefined behavior in C++ is by using a pointer after the thing it's pointing to has been freed. Doing this in a process that takes any user input can easily create a vulnerability that allows an attacker to inject code into the process. There are many ways to accidentally do this while thinking you're safe. A pointer to an item inside a data structure like a vector or hash table is dangerous if you use it after some items may have been inserted into the data structure, because inserts can cause the data structure to reallocate and expand its backing memory. This mistake can easily go unnoticed for a long time because only a small fraction of inserts will cause that. This mistake isn't possible to make in safe Rust.