Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime issue with the Send trait

I have troubles understanding why this code doesn't compile:

use std::cell::{Ref, RefCell};

struct St {
    data: RefCell<uint>
}

impl St {
    pub fn test(&self) -> Ref<uint> {
        self.data.borrow()
    }
}

// This code would compile without T constrained to be Send.
fn func<T: Send>(_: &T) {
}

fn main() {
    let s = St { data: RefCell::new(42) };

    {
        let r7 = s.test();
        // Do not compile
        func(&r7)
    }

    // Compile
    func(&s);
}

It gives the following error:

bug.rs:21:18: 21:19 error: `s` does not live long enough
bug.rs:21         let r7 = s.test();
                           ^
note: reference must be valid for the static lifetime...
bug.rs:17:11: 28:2 note: ...but borrowed value is only valid for the block at 17:10
bug.rs:17 fn main() {
bug.rs:18     let s = St { data: RefCell::new(42) };
bug.rs:19
bug.rs:20     {
bug.rs:21         let r7 = s.test();
bug.rs:22         // Do not compile
          ...

The issue seems to be in the function func() when I try to constrain T to be compatible with the Send trait. Without this constraint this code compiles without error.

Does anybody can explain to me what is the reason of this behavior?

like image 463
user3762625 Avatar asked Nov 06 '14 16:11

user3762625


People also ask

What is send in trait?

Send means that a type is safe to move from one thread to another. If the same type also implements Copy , this also means that it is safe to copy from one thread to another. Sync means that a type is safe to reference from multiple threads at the same time.

What is static lifetime Rust?

A static is never "inlined" at the usage site, and all references to it refer to the same memory location. Static items have the static lifetime, which outlives all other lifetimes in a Rust program. Static items may be placed in read-only memory if the type is not interior mutable.

What is send trait in Rust?

Rust captures this through the Send and Sync traits. A type is Send if it is safe to send it to another thread. A type is Sync if it is safe to share between threads (T is Sync if and only if &T is Send).


2 Answers

Update for Rust 1.0

In Rust 1.0 and later the code in the example (when uints are replaced with some existing type) fails with another error:

% rustc test.rs
test.rs:23:9: 23:13 error: the trait `core::marker::Sync` is not implemented for the type `core::cell::UnsafeCell<usize>` [E0277]
test.rs:23         func(&r7)
                   ^~~~
test.rs:23:9: 23:13 help: run `rustc --explain E0277` to see a detailed explanation
test.rs:23:9: 23:13 note: `core::cell::UnsafeCell<usize>` cannot be shared between threads safely
test.rs:23:9: 23:13 note: required because it appears within the type `core::cell::Cell<usize>`
test.rs:23:9: 23:13 note: required because it appears within the type `core::cell::BorrowRef<'_>`
test.rs:23:9: 23:13 note: required because it appears within the type `core::cell::Ref<'_, i32>`
test.rs:23:9: 23:13 note: required by `func`

This is kinda tricky - another trait, Sync, has appeared out of nowhere.

A type implementing Send trait (though its documentation is certainly lacking as of now) is something which can be transferred across task boundaries. Most of types are Send, but some, like Rc and Weak, are not Send because instances of such types may share non-synchronized mutable state and therefore are unsafe to use from multiple threads.

In older Rust versions Send implied 'static, so references were not Send. Since Rust 1.0, however, Send no longer implies 'static, therefore references can be sent across threads. However, in order for &T to be Send, T must be Sync: this is required by the following implementation:

impl<'a, T> Send for &'a T where T: Sync + ?Sized

But in our case we're not requiring that &T is Send, we only require that T is Send, so it shouldn't really matter, right?

No. In fact, there still are references, even we don't see them right away. Remember, for a type to be Send each its component must be Send, that is, each field of a struct and each part of each enum variant of an enum must be Send for this struct/enum to be Send as well. core::cell::Ref internally contains an instance of struct BorrowRef, which in turn contains a reference to Cell<BorrowFlag>. And here is where Sync comes from: in order or &Cell<BorrowFlag> to be Send, Cell<BorrowFlag> must be Sync; however, it is not and can not be Sync because it provides unsynchronized internal mutability. This is the actual cause of the error.

like image 80
Vladimir Matveev Avatar answered Oct 17 '22 16:10

Vladimir Matveev


According to the Rust reference (emphasis mine) :

Send : Types of this kind can be safely sent between tasks. This kind includes scalars, boxes, procs, and structural types containing only other owned types. All Send types are 'static.

Indeed, if you send something to an other task, you must guarantee that it won't be destroyed before this other task has finished using it, so it can't be owned by the current task.

There are two ways of ensuring it :

  • Having this object being fully owned (basically, all the member of your struct are Send as well)
  • Having your object be in the static storage

So by requiring that the argument of your function to be Send, you require r7 to be 'static, but it cannot outlive s (as it's a reference to the RefCell contents), which isn't 'static as it's defined in your main.

More generally, when writing

fn foo<T: 'a>(bar: T);

You require T to be either :

  • A type with all its lifetime arguments being 'a or longer (or having no arguments)
  • a &'a reference to a type itself being 'a (and you can recurse on these conditions)

And as we saw, T: Send implies T: 'static.

like image 28
Levans Avatar answered Oct 17 '22 15:10

Levans