user3762625
user3762625

Reputation: 365

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?

Upvotes: 5

Views: 880

Answers (2)

Vladimir Matveev
Vladimir Matveev

Reputation: 127771

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.

Upvotes: 11

Levans
Levans

Reputation: 14992

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.

Upvotes: 2

Related Questions