C++ memory management is hard, and best practices don't solve all the problems you encounter. (Note that I have more experience with desktop programs, like "browsers and operating systems", than embedded boards.)

Managing tree-shaped allocation trees is possible but mistake-prone, and Rust includes a sound checker to catch mistakes. (Rust pushes users towards using "writable xor aliased" pointers which is integral to this sound checker. "Writable xor aliased" is one of Rust's biggest strengths and weaknesses, and differs from both C++ and GC'd languages.)

Managing intrusive linked lists is common in C (I have less C experience than C++) and probably tractable as long as you don't carry around pointers which may dangle. Rust doesn't help write correct intrusive code, and worse yet Stacked Borrows (a proposed set of rules for pointer aliasing) interferes with attempts to write intrusive collections in unsafe code (https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a...). Maintaining intrusive sorted-tree maps and such is probably possible in C, but perhaps even trickier to get right (note that I've never built one myself).

I try to avoid aliased object graphs (multiple objects hold pointers to another object) when architecting greenfield projects, and it's often (but not always) possible. When I encounter pointer aliasing anyway (often in existing codebases), I usually fall back to shared_ptr (reference counting) because manual memory management requires borderline-intractable global reasoning to make sure every possible codepath never uses an aliasing pointer after the object has been freed through another pointer. (This is especially difficult on code I'm not wholly familiar with, because I either didn't build it myself or forgot the details.)

And if you have reference cycles, shared_ptr leaks memory, so you need to manually clear shared_ptr on destruction (error-prone and risks leaking), use weak_ptr (runtime overhead and boilerplate on every read, not just creation/destruction), or use raw pointers (error-prone and risks memory unsafety). Rust doesn't help you with reference cycles either, sadly (Rust makes mutating shared references painful, Rc leaks memory, and Weak has runtime overhead and boilerplate, but at least Rc/Weak aren't atomic like shared_ptr). A garbage collector makes your life a lot easier, at the cost of runtime pauses (often higher throughput than refcounting, but slowdown occurs in bursts).

----

C++ is intractable to write correct threaded code in. The language doesn't tell you whether a T& is visible from one or multiple threads (which determines whether you need to acquire a mutex to access it). And if 1 object has methods A and B both exposed through a public API (accessible from multiple threads), but method A (which acquires a mutex) calls method B, then method B must acquire the mutex when called from outside, but cannot acquire the mutex when called by method A unless it's a recursive mutex. The alternative is to make neither A nor B acquire the mutex, and require callers to acquire the mutex beforehand (which they may forget to do). I've written more about this at https://write.as/nyanpasu64/c-mutexes-are-easy-to-misuse-and....

I honestly don't think Python and Java are that much more tractable to write correct threaded code (note that Python has a global interpreter lock, and I have limited experience with Java threading). Neither distinguishes references to an object visible to one thread, from references to an object visible to multiple threads. And neither can force users to acquire a mutex before accessing an object visible to multiple threads, though Java's synchronized methods use recursive locks which can avoid the method A/B problem above. Also the consequences for threading errors are slightly less severe (corrupted data and exceptions instead of segfaults).

Rust is tractable to write correct threaded code. Its type system distinguishes &T (sometimes shared between threads, usually read-only), &Mutex<T> (cannot access T without taking a lock), and MutexGuard<T> and &mut T (obtained when you acquire a lock, provides unique access to T while you hold the mutex). This ensures you'll never forget to acquire a mutex, and have to go out of your way to try to acquire a second lock to a mutex you already own.