Custom lifetimes for Boxed and BoxedLocal
jmmv opened this issue · comments
I just discovered the futures-lite crate and I thought "let me try to convert some trivial code that I have using futures to this lighterweight crate". And it's great because I trimmed 20 seconds of build time!
Unfortunately, the conversion wasn't as clean as I hoped because BoxedLocal
currently seems to enforce a 'static
lifetime... and I have a piece of code that was previously using LocalBoxFuture
and requires a different lifetime. I was able to easily workaround this by redefining BoxedLocal
to take a custom lifetime and then directly using Box::pin
instead of FuturesExt::boxed_local()
. See this commit.
But I think it'd be nice if the interface in futures-lite supported a custom lifetime on its own for both Boxed
and BoxedLocal
. Thoughts?
I personally never needed a boxed future that wasn't 'static
. :)
A question: why is your function returning a LocalBoxFuture<'a, Result<()>>
? Why not define an async fn
so that the future is an anonymous type? I suppose because you want to be the return type to be nameable? If so, why not define a new future type, e.g. DoIfFuture<'a>
?
The main drawback I see here is that the future is always boxed, even if it is used temporarily on the stack. But also, this might not be a problem when impl Trait
syntax becomes usable in more places than today - and by that I mean type Foo = impl Trait
syntax.
If you are suggesting to remove the boxed futures and simply declare all those functions as async fn
... I wasn't able to do that because:
recursion in an `async fn` requires boxing
recursive `async fn`
note: a recursive `async fn` must be rewritten to return a boxed `dyn Future` rustc(E0733)
exec.rs(242, 65): recursive `async fn`
To be honest, this is the first async code I wrote in Rust, so I might be missing something obvious.
Ah, I see now. This seems unfortunate. :/
A way to solve this is to parametrize Boxed<T>
over a lifetime like so: Boxed<'a, T>
. To do this change, we'd have to bump the major version of futures-lite
since it's a breaking change. My other reservation is that this might be a sort of niche case that makes Boxed<'static, T>
a bit uglier than it is right now.
Another solution, which isn't a breaking change, would be to change the return type of boxed()
and boxed_local()
functions so that they return a lifetime-parametrized type. See here for a proof of concept: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=344083ee923760405baffb7e6ba961d5
I published futures-lite v1.1.0 that parametrizes boxed()
and boxed_local()
over a lifetime.
Now you can keep writing async fn
. The trick is then to call such an function, box its returned future, and then call it. For example:
let f = self.do_if(branches).boxed_local();
f.await?;
So the idea is that the caller of the function boxes the future rather than the function itself.
Hey Stjepang, thanks for your attention so far.
I gave this a try and it seems to work. Thanks! But I'm confused about why the let f
indirection is necessary. If I combine the two lines such that they say boxed_local().await?
the compiler still complains about the recursive calls... but not with the f
temporary.
If I combine the two lines such that they say
boxed_local().await?
the compiler still complains about the recursive calls... but not with thef
temporary.
I'm a bit surprised by that too. It's probably because the compiler has a rather simplistic check for recursive async fn calls, and doesn't understand that foo().boxed_local().await
is okay.
Even { let f = foo().boxed_local(); f }.await
works fine, so it seems the compiler demands that the recursive call and await
must be in different statements.
I hope this gets improved in the future...