Hacker News new | past | comments | ask | show | jobs | submit login

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.


That's a very good example, thank you!


> 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.


In rust, you still can’t get mutable access to any object in two threads at the same time in a non-thread safe way.

In “very rough c++ish”, stuff in a shared ptr is immutable unless it is also protected by a mutex.


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?


The extra static checks are the mutable vs non-mutable references, and the Sync/Send traits. Mutex does not introduce indirection.


From Rust official docs:

`let lock = Arc::new(Mutex::new(0_u32));`

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.


I would suggest you read the source code of Mutex <https://doc.rust-lang.org/src/std/sync/mutex.rs.html#178-182> and then of UnsafeCell <https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html>.

So, the layout of Mutex<T> is the same as T and then some lock (well, obviously).

>Rust introduces an extra layer of pointer indirection with Mutex, which can hurt performance significantly with cache misses.

Why would there be an extra pointer dereference? There isn't.


Thanks for the explanation!


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.


> Doesn't this mean that Mutex introduces one more pointer?

No. That syntax is roughly equivalent to the following C++:

    auto const lock = std::make_shared<std::pair<std::mutex, uint32_t>>(
        std::piecewise_construct,
        std::make_tuple(),
        std::make_tuple(0));


Thanks, that's the best explanation!




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: