Define "soundly". Follow the rules and it won't break. Will it break if someone memcpys some object internals or something? Sure. Just don't do that. The rules that make this abstraction robust in C++ are easy to follow, just like the rule that says "don't use unsafe" in Rust. Just like Rust, you can break the rule if you know what you're doing.
That's the problem here: you're on the honor system that everyone knows and follows every rule every time. Maybe you have a really top-notch team, great code review, etc. but can you say for a certainty that this will always be true, or that it's true of every bit of code you use?
Being able to prove that in advance, especially in more complicated scenarios, has a significant value from checking on every build, especially when you think of the many bugs which have been caused by maintenance code breaking some of the assumptions which the original authors had.
Virtually no large scale C++ app has successfully consistently followed the safety rules. It is too easy to, for example, store references to the mutex-protected object somewhere and have those references persist past the unlock operation, allowing unsynchronized access. References and raw pointers are created invisibly in C++ (example: "this"), unlike in Rust where "unsafe" is always explicit (and can be forbidden with a compiler switch or source annotation).
It’s an exactly same reasoning behind static & dynamic type system. “Don’t put null value to this function and it will not break. Just put object with these properties into this function and it will not break.” Static type system allow you to ENFORCE those contracts regarding what is the data. Same with this, Rust’s affine type system allows you to ENFORCE the contracts regarding HOW to use the data (i.e. if already be free, it couldn’t be used any more) as opposed to the infamous guideline “Don’t use the data after it’s already been freed “
You can't just wrap a statement the compiler has deemed to not pass the borrow checker and make it compile. Unsafe blocks can bypass the type system, but you have to call extra magic to do so.
So, really, what we're discussing is how much ceremony a language ought to require before enabling unsafe behavior. I think C++ provides enough ceremony that you can write decently safe programs in C++.
It's not about ceremony - it's disingenuous to say that C++ can enforce the same safety rules as Rust w.r.t. to lifetimes or that C++ is as safe as Rust is within it's unsafe blocks.
Whilst currently the borrow checker is a bit greedy, it's safe to say that most of the time the borrow checker is checking the code is safe the same way I try to reason about memory accesses in C and C++ when dealing with concurrency or freeing memory. The power of the borrow checker shows itself when reasoning about a local function and no longer requires you to reason about the whole application state as the compiler ensures locality to the extent that shared mutable state is not allowed.
That has empirically been proven false over a decade of C++ use. Additionally, C++ allows for plenty of memory safety problems without any syntactic ceremony.