Does &'a mut T : Send has much meaning when 'a : 'static is required?

I know the rule &mut T : Send requiring T : Send; it is not enough.

Send &'a mut T also requires 'a : 'static, which requires T living in a static scope.

Seems only static variable satisfies this.
While the question is, if T is a static variable, then I can access it directly in the other thread, why need I send the mut ref to the thread?

Any T of not static but satisfying the requirement exists?

I'm not sure what you mean by "not enough".

This is certainly inaccurate, as this compiles:

fn assert_send<T: Send>(_: T) {}

fn main() {
    let mut x: u64 = 0;
    let ptr: &mut u64 = &mut x;
    assert_send(ptr);
}

Yes, u64 is Send, but you can't send &mut u64 to another thread; except x is a static variable, that then makes the &mut u64 : Send meaningless

Did you check that example? It puts a Send bound on the type being passed in, which is &mut u64 in the above code. It doesn't need to be &'static.

1 Like
fn main() {
    let mut x: u64 = 0;
    let ptr: &mut u64 = &mut x;

    std::thread::spawn(move || {
        ptr;
    });
}

gets

   |
52 |       let mut x: u64 = 0;
   |           ----- binding `x` declared here
53 |       let ptr: &mut u64 = &mut x;
   |                           ^^^^^^ borrowed value does not live long enough
54 |
55 | /     std::thread::spawn(move || {
56 | |         ptr;
57 | |     });
   | |______- argument requires that `x` is borrowed for `'static`
58 |   }
   |   - `x` dropped here while still borrowed


There is a bound on thread::spawn that requires the closure to be 'static. Try using thread::scope instead:

use std::thread;

fn main() {
    let x = &mut 0;
    
    thread::scope(|s| {
        s.spawn(|| *x += 1);
    });
    
    assert_eq!(*x, 1);
}

Playground.

2 Likes

The problem in that code is not that &mut u64 is not Send. But the compiler just told you that. The problem is that threads created using thread::spawn() must not borrow, because they might live forever potentially (thus, any non-static borrows have the potential of expiring and becoming dangling).

If you want to use thread::spawn(), you'll need to hand it owned data. If you need to mutate something from multiple threads, then the usual way to do this is wrap it into Arc<Mutex<_>>:


fn main() {
    let ptr = Arc::new(Mutex::new(0));

    std::thread::spawn(move || {
        *ptr.lock().unwrap() += 1;
    });
}

(For primitive types, it will almost always be better to use atomics, e.g. Arc<AtomicU64> instead of an explicit mutex.)

2 Likes

That is Ok, I know.
While I am talking any T meets the requirements exists and still has some meaning.

static variables, like I said, does not means much

'static is not only satisfied by static variables but by all owned data. Any type that does not contain any references (shorter than 'static) satisfies 'static (see here).

2 Likes

I'm not sure what you mean by "does not mean much". In what sense? Surely the 'static lifetime as well as static items do have a meaning. What's your actual complaint?

If you are having trouble expressing yourself in reasonably good English, then please seek the help of someone who is a fluent speaker to translate your question.

T : 'static does not meets 'a : 'static in &'a mut T.

I know how to make T satisfiy 'a : 'static, e.g. declaring T as a static variable, and seems there is no other way except that.

But then T as a static variable can be accessed in any thread naturally, does not need the &'a mut T : Send to get the accessing permission @H2CO3

Well yes, of course &'a T does not satisfy 'static if 'a is not the 'static lifetime.

The way I see it, your problem is as follows:

You want to call a function which has an interface that requires its input to satisfy 'static. You have a program that does not satisfy the interface, because it tries to pass &'a mut T (not 'static).

I'd say you have three options to go from here:

  • use idiomatic shared-state concurrency as shown by @H2CO3 (Arc<Mutex<T>)
  • use something that has a less restrictive interface (like thread::scope).
  • take a completely different approach to concurrency, like message passing

I am not facing such a problem.
I am trying to understand what &mut T : Send means, like I said, in general, it does not mean much.

Just curious is there anything I omitted and that makes &mut T : Send meaning more.

Okay, then I've misinterpreted your question, my bad.

Unfortunately I don't have a deeper/more meaningful answer than that it allows you to use it in contexts that require it to be Send, but does not require it to be 'static (like Scope::spawn, for example).

1 Like
use std::thread;
use std::time::Duration;

fn main() {
    let (mut a, mut b) = (1, 2);
    
    thread::scope(|s| {
        s.spawn(|| some_expensive_computateion(&mut a));
        s.spawn(|| some_expensive_computateion(&mut b));
    });
    
    println!("{a}, {b}")
}

fn some_expensive_computateion(n: &mut i32) {
    thread::sleep(Duration::from_secs(5));
    *n += 1;
}

Here since &mut i32 is Send you can complete 2 expensive computations within 5 seconds. If you want some more practical examples, see the rayon crate.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.