I'll swallow the bait and try asking some sceptical questions here.
My understanding of Rust memory management is that move semantics and default lifetime-checked pointers are used for single threaded code, but for multi-threaded code Rust uses smart pointers like C++, roughly Arc = shared_ptr, Weak = weak_ptr, Box = unique_ptr.
My question is: what extra static checks Arc has over shared_ptr? Same for Weak over weak_ptr, and Box over unique_ptr.
Here's a static check Rust's Box<T> offers over C++'s std::unique_ptr<T>.
The following program is obviously incorrect to someone familiar with smart pointers. The code compiles without error, and the program crashes as expected.
% cat demo.cpp
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<std::string> foo = std::make_unique<std::string>("bar");
std::unique_ptr<std::string> bar = std::move(foo);
std::cout << *foo << *bar << std::endl;
}
% clang -std=c++2b -lstdc++ -Weverything demo.cpp
warning: include location '/usr/local/include' is unsafe for cross-compilation [-Wpoison-system-directories]
1 warning generated.
% ./a.out
zsh: segmentation fault ./a.out
The equivalent Rust code fails to compile.
% cat demo.rs
fn main() {
let foo = Box::new("bar");
let bar = foo;
println!("{foo} {bar}")
}
% rustc demo.rs
error[E0382]: borrow of moved value: `foo`
--> demo.rs:5:13
|
2 | let foo = Box::new("bar");
| --- move occurs because `foo` has type `Box<&str>`, which does not implement the `Copy` trait
3 | let bar = foo;
| --- value moved here
4 |
5 | println!("{foo} {bar}")
| ^^^^^ value borrowed here after move
help: consider cloning the value if the performance cost is acceptable
|
3 | let bar = foo.clone();
| ++++++++
Not only does Rust emit an error, but it even suggests a fix for the error.
> but for multi-threaded code Rust uses smart pointers like C++
That's not the whole story. There's also Send and Sync marker traits, move by default semantic also makes RAII constructs like Mutex<T> less error prone to use.
The big deal is that Rust will refuse to compile if you forget to use the appropriate smart pointer. You can’t just accidentally forget that you left a mutable reference in another thread: if you want a thread to use a reference, Rust will ensure you don’t accidentally let another thread access it simultaneously.
Ok, so do I understand correctly that Arc<MyType> would be read-only, and for write access I'll have to use Arc<Mutex<MyType>> or Arc<RwLock<MyType>>? So what about Mutex and RwLock, do they have any static checks associated with them? Do they introduce an extra layer of pointer indirection, or Rust resolves Arc<Mutex> and Arc<RwLock> to 2 different implementations with only 1 layer of indirection?
Doesn't this mean that Mutex introduces one more pointer?
For example, in Java every Object has a built-in mutex, adding some memory overhead in order to remove one extra layer of pointer dereferencing. As far as I understand, Rust introduces an extra layer of pointer indirection with Mutex, which can hurt performance significantly with cache misses.
It is only Arc and not Mutex that allocates and thus has "extra" pointer indirection. A Mutex can live perfectly well on the stack, as long as it outlives the threads accessing it. Arc has to allocate, because it is meant to outlive the function that created it, and the only place to safely do that is on the heap.
My understanding of Rust memory management is that move semantics and default lifetime-checked pointers are used for single threaded code, but for multi-threaded code Rust uses smart pointers like C++, roughly Arc = shared_ptr, Weak = weak_ptr, Box = unique_ptr.
My question is: what extra static checks Arc has over shared_ptr? Same for Weak over weak_ptr, and Box over unique_ptr.