Is Send/Sync special cased?


#1

So I’m trying to make sure I have a proper understanding of Send and Sync, and whether they’re special cased by the compiler, or if I could do it myself for some other type to make similar guarantees. I can’t seem to find confirmation in the docs, so I’m just hoping to state my understanding of everything here and have anything that’s incorrect or incomplete pointed out.

So thread::spawn has the type bound where F: FnOnce() -> T, F: Send + 'static (side note: Why 'static? Are there any Fn types that wouldn’t be 'static?). thread::spawn doesn’t need to mention or know about Sync, because there is impl<'a, T> Send for &'a T where T: Sync so the only way to have an aliasable reference that is Send is for the type it’s referencing to be Sync.

Now where I’m starting to get fuzzy is why FnOnce() -> T implements Send. I’m guessing it’s because there’s impl Send for .. { }, so the function would automatically implement it if all of its component parts implement it. However, looking at the source of the Fn traits, I don’t see anything that indicates what’s part of it. There’s the #[rustc_paren_sugar] annotation, and while I can infer what that does, it doesn’t imply what it’s actually deriving. I’m assuming that it involves the Args type parameter. I also don’t see where or how the type of the function involves data that it closes over.

However, if I gloss over the details, I’m assuming that means that Fn includes all variables that it closes over as part of it’s members, which would mean that they would be used to determine if that Fn is Send or not. That seems to be confirmed by this experiment:

#![feature(optin_builtin_traits)]

unsafe trait Foo {
}

unsafe impl Foo for .. { }

impl !Foo for u16 { }

fn call_foo<F, T>(f: F) -> T where F: Fn() -> T, F: Foo {
    f()
}

fn main() {
    let a: u8 = 1;
    let b: u16 = 2;
    println!("{}", call_foo({|| a }));
    println!("{}", call_foo({|| b }));
}

The compiler error also seems to confirm this:

foo.rs:18:20: 18:28 error: the trait `Foo` is not implemented for the type `u16` [E0277]
foo.rs:18     println!("{}", call_foo({|| b }));

While having it point out that the problem is u16 is helpful, it also doesn’t help me confirm if the actual source of the problem is that the derived Fn trait for the closure doesn’t implement Foo because u16 doesn’t implement it. I also just realized that since u16 is part of the return type, that doesn’t actually confirm that closed over variables are part of this as well, but I’m assuming they are?

Is all of this more or less correct? Is there explicit documentation on this somewhere that I’m missing?


#2

If you have an anonymous closure that references anything in its enclosing scope(s), it’s lifetime must be bound to appropriately. Then that Fn type can’t outlive the environment it closes over!


#3

You can find some elaboration on what a closure is in this blog post.


In fact, the best way to think of it is that a || expression generates a fresh structure that has one field for each of the variables it touches. It is as if the user wrote:

let some_salt: String = ...;
let closure = ClosureEnvironment { some_salt: &some_salt };
foo(closure);

where ClosureEnvironment is a struct like the following:

struct ClosureEnvironment<'env> {
    some_salt: &'env String
}

impl<'env,'arg> FnMut(&'arg String) -> uint for ClosureEnvironment<'env> {
    fn call_mut(&mut self, (str,): (&'arg String,)) -> uint {
        myhashfn(str.as_slice(), &self.some_salt)
    }
}

Building on that we could say that the expression

|| b

creates an instance of

ClosureEnvironment { b: u16 }

Since there’s a negative implementation of Foo for u16, the default implementation doesn’t apply to the struct.


#4

Send/Sync are special cased, but only for mostly tangential reasons:

  • thread locals need to be Sync
  • the trait implementations are cached in the compiler

So you can create a mostly equivalent Send/Sync system simply by doing:

#![feature(optin_builtin_traits)]
pub trait MySend {}
impl MySend for .. {} // "default impl" for all types

impl<T> !MySend for *const T {} // opting out
impl<T> !MySend for *mut T {}
// (do the remaining impls and the same for Sync)

http://manishearth.github.io/blog/2015/05/30/how-rust-achieves-thread-safety/ explains how these work together

The 'static bound in Fn() + 'static is to ensure that there is no borrowed data in the callable. For example, the following code won’t work:

let x = Box::new(1);
spawn(|| {
 println!("{}", x)
})

because the closure borrows x and thus isn’t 'static, but this:

let x = Box::new(1);
spawn(move || {
 println!("{}", x)
})

will, since we moved the box into the closure


#5

Interesting. I would not have expected that 'static meant that, though the compiler error is extremely helpful in this case. My understanding was that 'static meant some form of data which lived for the entire life of the program (usually a literal). Do you happen to know if/where there was a discussion on that decision for context, as opposed to something like 'owned, or having a Fn which borrows data actually be a &Fn?


#6

A lifetime bound like F: 'static means that it must be legal for values of that type to live as long as 'static (i.e., in this case, it is legal for them to be kept around “forever”), it doesn’t mean that they have to (i.e. it is OK to drop/discard them before the end of forever).

Being able to live forever just means that the value can’t be restricted to any “smaller” scope, which only happens when it borrows values out of that scope, tracked by lifetimes.


#7

That’s static, not 'static. static is a keyword that can be applied to some global variables. 'static is a lifetime for data which could be placed in a static box, i.e. data which has no compile-time restriction on lifetimes (and hence contains no borrows)