The innovation of Rust is the borrow checker, which is primarily of interest to systems programmers. If your primary interest is highly abstracted business logic, there are tools that don't require manual memory management or being pedantic about the different types of strings. You could just use Go, Java, Haskell, Python, etc.
1. Rust doesn't force you to do manual memory management. Rust memory management is automatic by default and only if you really, really want to, you can do it manually.
2. Memory is not the only resource. The GCs in languages you listed only solve the memory management problem, but regarding the other types of resources, their ergonomics are often worse than C - you have to remember to close the resources manually and you get virtually no help from the compiler.
3. None of the listed languages address problems related to concurrency, e.g. data races. Ok, Haskell kinda avoids the problem by imposing other restrictions - by not allowing mutation / side effects ;)
4. Rust offers way better tools for building high level abstractions than Go, Python and Java. It has set a very high bar with algebraic data types, pattern matching, traits/generics and macros.
3. Rust only addresses problems related to data races, not as an example. All the other race conditions are still on the table. It is a good thing to have, but I think they are the least problematic, and easiest to solve part of concurrency issues.
4. All of these have been known for like 3 decades. There are plenty of managed languages with these, ML, OCaml, Haskell, Scala. But I think your claim is subjective at best.
Then you've got a different definition of "manual" than mine. Manual means that developer has to insert calls to allocate / deallocate memory and that the developer is responsible for proving the correctness of those calls. Automated means those calls are done by the runtime or by the compiler automatically, and the compiler makes sure they are correct. In case of Rust, those calls are inserted automatically by the compiler.
> memory/ownership details leak into public APIs
The fact that ownership is a part of public API is a good thing, similarly how it is a good thing to specify an argument is an integer and not a string.
> There are plenty of managed languages with these, ML, OCaml, Haskell, Scala.
I referred to the ones mentioned in the above comment, which mentuoned Java/Go/Python. Haskell/Scala/Ocaml/ML are quite niche even compared to Rust these days.
But even though Haskell / Scala might get close on some type-system features, they don't offer similar experience as Rust in other areas. Haskell is more restrictive in terms of managing state than borrow-checker, and Scala tooling / compile times has been always horrible.
> Rust only addresses problems related to data races, not as an example. All the other race conditions are still on the table.
This is like saying a statically typed language doesn't stop you from putting a string telephone number into a string surname field. Sure it doesn't. But despite that, the value of static types is hard to overestimate.
In practice, the borrow checking + RAII + Send/Sync rules can be used to make the other types of concurrency problems very unlikely by properly modeling the APIs. Sure, no language can protect from all concurrency problems in general, but at least Rust gives you some good tools. For instance it is trivial to forbid concurrent access to something that shouldn't be accessed concurrently and let the compiler enforce that. Now try enforcing that in your "business oriented language of choice". In my experience the majority of concurrency related problems in real large-scale software development happen when some code not designed to handle concurrency accidentally becomes executed concurrently because developers don't realize something is shared and mutated at the same time. Another type of common issue is with communicating concurrent threads of execution, when one sends a message but the receiver is not there on the other end because of premature exit e.g. due to error, leading to a deadlock. Rust protects from those really well.
For embedded software, the underlying code basically reads and writes a bunch of registers. Unsafe memory access with side effects. The benefit of using rust here is that you can easily model these access patterns to make an api that cannot be abused. So the driver reads and writes addresses and the user code operates through the driver with all the benefits of ownership at hand to avoid race conditions and other foot-guns.
So, in this way rust does indeed “solve for” ownership of devices. You can’t have two threads (or interrupt handlers) mutating the same device without satisfying the ownership rules.
The trouble with this is that abstract 'devices' don't necessarily map neatly to the underlying hardware. Configuring peripherals on a typical microcontroller typically requires setting flags in a bunch of random registers which don't necessarily have neatly separated responsibilities.
Take PWM as an example. Is there a PWM 'device'? Is there a PWM setting for each port, according to some abstract representation of ports? What about the timer used to generate the PWM output? Does the PWM device own the timer, or does the timer own the PWM device? Any such abstractions cause more problems than they solve. You really just need to think carefully about how you are manipulating the underlying hardware.
First layer gives you safe access to the hardware registers. For example, ensure atomic/synchronized access, forbid invalid/reserved values. Name the flags/bits to reduce human mistakes (reg |= Prescaler::Div8.
You can still miss-configure the PWM/Timer settings of course.
The second layer gives you a safe driver interface. Giving you all the options to configure a timer for a some PWM settings for example.