trait ProduceOneView<'a> {
type View: 'a + View;
fn produce(&self, s: &'a String) -> Self::View;
}
trait ProduceView: for<'a> ProduceOneView<'a> {}
impl<T> ProduceView for T where T: for<'a> ProduceOneView<'a> {}
impl<'a, F, R> ProduceOneView<'a> for F
where
F: Fn(&'a String) -> R,
R: 'a + View,
{
type View = R;
fn produce(&self, s: &'a String) -> Self::View {
self(s)
}
}
And then elsewhere
impl<F: ProduceView> View for State<F> {
fn draw(&self) {
let s = "hello world".to_string();
self.f.produce(&s).draw();
}
fn process(&self, _data: &String) -> Option<String> {
let s = "hello world".to_string();
let v = self.f.produce(&s);
v.process(&s)
}
}
The key to the first part is that you can only name the result type R in the context of a single lifetime. (If you name R in for<'any> Fn(&'any String) -> R, R cannot capture a borrow involving 'any.)
The helper trait approach I presented above is necessary on stable because the Fn(...) -> ... sugar does not allow eliding the return type like the unboxed_closures syntax does, so you cannot properly write the top bound on T. Or alternatively because you can't write something like for<'any, U: View + 'any> Fn(&'any String) -> U or -> impl View + 'any, etc.
In the playground, State<F> will implement View for functions that return anything that implements View (so long as the functions work with all lifetimes) -- that is how I got rid of the box, and that's why it works with Empty. But it also means that if you want the option of keeping the box, Box<dyn View + '_> needs to implement View in order for the original state to work.
That's right, a `Box<dyn Trait>` does not automatically implement `Trait`.
This is often a surprise. You can still call Trait methods on a Box<dyn Trait> parameter due to deref conversion, just like you can call str's Display implementation on a String -- the language lets you utilize all traits of concrete types. But here we're in a generic context, so we need to implement View for Box<dyn View + '_> so that Box<dyn View + '_> will meet the trait bound on the implementation.
You can't just keep your original implementation and the one I've added, as the compiler will think there's the potential for overlapping implementations (and there is on nightly, where one could implement the Fn traits in multiple interesting ways). At least, I think that's a way to get actual overlap, I didn't try it out just now.
Note that state does still work in the playground, because I added the implementation:
// I also added this
impl View for Box<dyn View + '_> {
fn draw(&self) { (&**self).draw() }
fn process(&self, data: &String) -> Option<String> { (&**self).process(data) }
}
(You might want similar for Box<dyn View + Send + Sync + '_> or others, depending on your use cases.)
I'm a little confused, sorry. I don't want the option of keeping the box. Just need a version of that state function that will work for test_state_nested.
There's something going on with the captures between the outer closure and the inner closure. Using type erasure and the dyn lifetime solves it, but I have yet to find the right nobs or nudges to get the outer closure to be properly higher-ranked. Perhaps someone else can.