What does "Box<Fn() + Send + 'static>" mean in rust?
Asked Answered
C

2

32

What does Box<Fn() + Send + 'static> mean in rust?

I stumbled upon this syntax while reading advanced types chapter. Send is a trait but what does it mean to + a lifetime to a trait ('static in this case) in type parametrization ? Also what is Fn() ?

Couture answered 29/12, 2017 at 4:10 Comment(0)
B
48

Let's decompose it one-by-one.

Box

Box<T> is a pointer to heap-allocated T. We use it here because trait objects can only exist behind pointers.

Trait objects

In Box<Fn() + Send + 'static>, Fn() + Send + 'static is a trait object type. In future, it will be written Box<dyn (Fn() + Send + 'static)> to avoid confusion.

Inside dyn are restrictions to the original type. Box<T> can be coerced into Box<Fn() + Send + 'static> only when T: Fn() + Send + 'static. Therefore, although we don't know the original type, we can assume it was Fn() and Send and had 'static lifetime.

Fn()

This is a trait, just like Clone or Default. However, it uses a special syntax sugar.

  • Fn(A1, ..., An) is a syntax sugar for Fn<(A1, ..., An), Output=()>.
  • Fn(A1, ..., An) -> R is a syntax sugar for Fn<(A1, ..., An), Output=R>.
  • This syntax sugar also applies to the following traits: Fn, FnMut, FnOnce, and FnBox.

So what does Fn mean? T: Fn(A1, ..., An) -> R means x: T is a callable object with arguments A1, ..., An and return type R. Examples include function pointers and closures.

Send

Send means that values of this type can be sent across threads. Since this is an auto trait, it can be specified as the second bounds of dyn types (trait object types).

'static bound

In fact, dyn types (trait object types) must have exactly one lifetime bound. It's inferred when omitted. The inference rule is described in RFC 0192 and RFC 1156. It's basically as follows:

  1. If explicitly given, use that lifetime.
  2. Otherwise, it is inferred from the inner trait. For example, Box<Any> is Box<Any + 'static> because Any: 'static.
  3. If the trait doesn't have an appropriate lifetime, it is inferred from the outer type. For example, &'a Fn() is &'a (Fn() + 'a).
  4. If that even failed, it falls back to 'static (for a function signature) or an anonymous lifetime (for a function body).

Conclusion

f: Box<Fn() + Send + 'static> is an owned pointer to a callable value (with the original type unknown and dynamically change) such as closures (with no argument or no return value), which can be sent across threads and lives as long as the program itself.

Burgomaster answered 29/12, 2017 at 6:6 Comment(3)
Thanks a lot Masaki for the detailed explanation! One off-topic question: Does Box<&i32> mean that it will allocate the pointer/reference in the heap and that the borrowed content (i32) (the data it is pointing to) could well be on stack? and that *b will give me &i32 and **b will give 100 (given that let m = 100; let b:Box<&i32> = Box::new(&m);); Not considering println! here which autorefsCouture
@Couture I know you may not care by now lol, but I believe so. There's not much reason to box a reference to a stack-allocated value, of course, and you wouldn't be able to do much with the box since it would be invalid after the i32 gets dropped or moved.Advocation
@HutchMoore, yes it was more of a hypothetical question.Couture
O
8

I found the 'static part needs more elaboration from the top-voted answer.

Denote the underlying concrete type as A.

Trait object Box<dyn Fn() + Send + 'static> can be constructed from an instance of A, which implies A: Fn() + Send + 'static. That is to say, the concrete type A is bounded by static lifetime.

specific explanation for 'static as trait bound:

As a trait bound, it means the type does not contain any non-static references. Eg. the receiver can hold on to the type for as long as they want and it will never become invalid until they drop it.

It's important to understand this means that any owned data always passes a 'static lifetime bound, but a reference to that owned data generally does not

A generative explanation for cases where any lifetime is used as trait bound:

T: 'a means that all lifetime parameters of T outlive 'a. For example if 'a is an unconstrained lifetime parameter then i32: 'static and &'static str: 'a are satisfied but Vec<&'a ()>: 'static is not.

For our case, all lifetime parameters of A must outlive 'static, for example

pub struct A<'a> {
   buf: &'a[u8]
}

can't meet A: 'static requirement.

Orren answered 27/4, 2021 at 3:19 Comment(2)
So is a 'static lifetime a bad thing? If I overuse it does it mean they'll never get released? Or is rust smart enough to even on 'static lifetimes to release the object/reference if it's not being used anymore?Carden
@RafaelMerlin it's not bad at all. if it's under reference context, it means to live the entire program lifetime. You can also use Box to intentionally leak heap memory. Having full control of the underlying memory life cycle is a good thing. If you decide to overuse it, you need to bear the outcome. I think that's fair.Orren

© 2022 - 2024 — McMap. All rights reserved.