This pertains to Ch 16.03 of The Book, specifically Arc
in multithreaded programs
I was just looking at the signature for thread::spawn
(documentation linked to by post URL) wondering if and how the requirement for a thread-safe smart pointer is enforced by the type system. In short, how is the use of Arc
necessitated by the type system??
For the signature, you get this:
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
Where the parameter f
is bound by F: FnOnce() -> T + Send + 'static
.
And … I’m not entirely sure how I should think about this.
Obviously, Send
and 'static
here are related to my question, where in some way the closure must be thread-safe (through the Send
trait) and also have a whole-life-of-the-program lifetime (through the 'static
lifetime). Therefore, in some way, something like Rc
would be invalid but Arc
would be.
But how exactly?
- Are the bounds on the return type of the function/closure, which then sort of back propagate onto the input parameters or captured values of the function/closure?
- Or are these somehow properties of the function itself?
- And why is
T
separately bound bySend + 'static
when they are already present in the bounds onF
?
My best guess is that the bounds on F
are best parsed as …
F: ( FnOnce() -> T ) + Send + 'static
T: Send + 'static
IE, Everything separated by a +
is unitary, and, therefore, FnOnce() -> T
is a single bound and Send
another separate bound.
But I’m unsure about that, haven’t read that anywhere, and am not really sure how to understand how exactly a function can have Send
or 'static
without some logic linking that directly to its input parameters?
Thoughts?
Your guess is correct, it should be understood as
F: ( FnOnce() -> T ) + Send + 'static T: Send + 'static
The
FnOnce() -> T
is syntax sugar forFnOnce<(), Output = T>
and the bounds after apply toF
. That’s also whyT
has separate bounds. They aren’t propagated or inherited. It’s just an ambiguous looking syntax, butT + Trait + 'lifetime
isn’t a valid syntax for applying bounds toT
(unless I missed something).The type
F
may be a closure over values from the calling thread. It has to be possible to send these values to the spawned thread, henceF
needsSend + 'static
. When the thread is done with its work, it returns a value of typeT
that gets passed back viaJoinHandle<T>
, soT
needsSend + 'static
too.I hope this cleared things up a bit.
Thanks!! (Am I correct in guessing that you handle is of Gontian origin?)
Clears up a lot!
It has to be possible to send these values to the spawned thread, hence F needs Send + 'static.
I’m still not sure I understand what this means. I suppose it’s normal for a function/closure to have a lifetime, though how a closure comes to be
'static
I’m not sure. And similarly I’m not clear on how a function/closure would come to haveSend
.I’m guessing from your language that these bounds come from the captured values? That is, whether they are satisfied depends on whether the captured values satisfy them?
A closure may/will try to capture by reference, so it may hold references to the calling function’s scope. For example, this would want to hold a reference to
x
:let x = 0; std::thread::spawn(|| x += 1);
It’s as if you had a struct like this:
struct MyClosure<'a> { x: &'a mut i32, }
That makes the closure non-
'static
, since it holds non-'static
references. The usual solution is to use themove
keyword, to hint that you want it to move by default, or to use scoped threads. But yeahSend
and'static
depend on the captures.Am I correct in guessing that you handle is of Gontian origin?
Yes! 😁 I picked it when I used to play Tibia (15-20 years ago!), and it stuck with me since then. The correct spelling was already taken, so I modified it a bit. This name is usually available.
Thanks so much!!
And yea, nice handle! Mine’s kinda similar. It comes from Tolkien, I used it for an MMORPG back in the day and it’s kinda stuck as it’s almost always available.