struct Signal<T> {
handlers: Vec<Box<dyn Fn(&T)>>,
}
impl<T> Signal<T> {
fn emit(&mut self, data: &T) {
for handler in &self.handlers {
handler(data);
}
}
}
struct Message<'a, T> {
data: &'a T,
}
fn main() {
let mut signal = Signal { handlers: Vec::new() };
let data = 0; // can be any arbitrary data
signal.emit(&Message { data: &data }); // error: borrowed value does not live long enough
}
signal is a Signal<Message<'a, i32>>, where 'a must last as long as signal. Can I somehow constrain 'a to the smallest possible lifetime, so it only lasts for each call to emit()? I'm trying to get a similar outcome to Fn(&T), where &T has the smallest possible lifetime.
What I really want is the type for<'a> Signal<Message<'a, i32>>, but of course Message is not a trait so that's not possible. Is it possible to express this some other way?
The problem seems to be that dyn Fn(&T) is invariant in T. If you use function pointers instead of boxed closures, then your code will compile (playground):
struct Signal<T> {
handlers: Vec<fn(&T)>
}
I'm not sure if there's any trick that would extend this to stateful closures while keeping the type contravariant. It would need to somehow forbid closures that use internal mutability to copy the &T argument into their internal state.
It is not because of invariance. The original code failed because the type Signal<Message<'a, T>> is annotated with the lifetime 'a, and to pass a reference to the data variable to emit, the lifetime on signal must be the lifetime of data.
However, a struct must not outlive any lifetimes annotated on it, so the signal must be destroyed before the lifetime 'a ends, hence data must go out of scope aftersignal does. This compiles:
fn main() {
let data = 0;
let mut signal = Signal { handlers: Vec::new() };
signal.emit(&Message { data: &data });
}
The function pointer works because of a special case in the drop-checker for types with simple destructors.
@nologik Unfortunately in this case, the for<'a> syntax doesn't help, because the problematic lifetime is the one on the type used in place of T (i.e. the Message struct), not the one on the reference.
But in my playground link, Signal<Message<'a, T>> is still annotated with the 'a lifetime. The only difference is the variance!
Because fn(&T) is contravariant in T, you can pass the shorter-lived &data to the longer-lived signal, because you can coerce Signal<Message<'a, T>> to Signal<Message<'b, T>> for any 'a: 'b.
This doesn't work when Signal<T> is covariant or invariant in T, as in the original code.
It's natural to expect dyn Fn(&T) to have the same subtyping as fn(&T). The only cases where this would not work involve UnsafeCell. Unfortunately, the compiler needs to care about these cases.
Where would the cast of signal's type happen? It can't happen when passing it to emit because it takes an &mut Signal<Message<'a, i32>>, and the mutable reference makes it invariant.
Yes, and the destructor on Vec doesn't cause problems because it is annotated with #[may_dangle].
@whatisaphone Sorry for hijacking your thread for discussing why it compiles in a weird edge case. You don't need to understand all of this. The answer to your question is that the compiler is worried that your signal variable contains a reference to the data variable, so it wants to make sure that signal is destroyed before data is. You will probably have to restructure your code to avoid this issue, since I assume the data can't outlive the signal struct in practice.