Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Mutex contain a Box?

Tags:

mutex

rust

Rust's std::sync::Mutex is a struct that contains a heap-allocated inner mutex, along with this semi-mysterious comment:

pub struct Mutex<T: ?Sized> {
    // Note that this mutex is in a *box*, not inlined into the struct itself.
    // Once a native mutex has been used once, its address can never change (it
    // can't be moved). This mutex type can be safely moved at any time, so to
    // ensure that the native mutex is used correctly we box the inner mutex to
    // give it a constant address.
    inner: Box<sys::Mutex>,
    poison: poison::Flag,
    data: UnsafeCell<T>,
}

The comment explains that Box is used to give the inner mutex a stable address. But I can't seem to find any explanation for why a stable address is required in the first place.

On Unix-like platforms at least, the "native mutex" here (sys::Mutex) is ultimately a wrapper around libc::pthread_mutex_t (source code).

In C, it almost makes sense to have a rule against moving mutexes, because mutexes are used through pointers, and moving one while there is a live pointer to it would be clearly wrong. But in Rust, you can't even try to move something unless there are no live references to it. So this line of argument doesn't seem convincing.

Why must the native mutex have a stable address?

like image 405
trent Avatar asked Jul 18 '18 01:07

trent


1 Answers

I think the answer here is essentially just "pthread_mutex_t is an opaque type". The POSIX standard doesn't guarantee that it's movable, so we can't make any assumptions about it. Mara Bos from the Rust library team discussed this in a Twitter thread.

There are many ways that a C type might not be movable. It might contain a self-referential pointer, i.e. one of its fields might point into another of its fields. This isn't usually allowed in Rust, but it is allowed in C and C++, and some real world types do this as an optimization. (For example, GCC's implementation of std::string does this.) Another thing a C type might do, is to insert a pointer to itself into some global registry. In either of these cases, moving or copying an instance of such a type would lead to dangling pointers and other inconsistencies.

Some (most?) implementations of pthread_mutex_t don't do these things. But the POSIX standard doesn't prohibit them, and any given implementation could start doing these things in the future without warning. The Rust standard library has to be defensive about this, and that's why Mutex always needs a Box on Unix. (Note that Mutex has changed a bit since this question was written. It no longer contains a Box on Windows.)

As an aside, it's also the case that the futex system call (which is how pthread_mutex_t is implemented on Linux) takes the address of the lock as an argument. This sort of thing is another reason that pthread_mutex_t isn't movable in general. However, this particular issue doesn't concern Rust, because a Mutex in Rust is always borrowed while it's locked, and that means it can only move while it's unlocked.

like image 182
Jack O'Connor Avatar answered Sep 23 '22 01:09

Jack O'Connor