[impl Trait] Should we allow `impl Trait` after `->` in `fn` types or parentheses sugar?
nikomatsakis opened this issue · 9 comments
RFC 1951 disallowed uses of impl Trait within Fn trait sugar or higher-ranked bounds. For example, the following is disallowed:
fn foo(f: impl Fn(impl SomeTrait) -> impl OtherTrait)
fn bar() -> (impl Fn(impl SomeTrait) -> impl OtherTrait)
This tracking issue exists to discuss -- if we were to allow them -- what semantics they ought to have. Some known concerns around the syntax are:
- Should the
()
switch from existential to universal quantification and back?- I think the general feeling here is now "no", basically because "too complex".
- If HRTB were introduced, where would we (e.g.) want
impl OtherTrait
to be bound?
For consistency, we are disallow fn(impl SomeTrait) -> impl OtherTrait
and dyn Fn(impl SomeTrait) -> impl OtherTrait
as well. When considering the questions, one should also consider what the meaning would be in those contexts.
Should we allow
impl Trait
after->
infn
types or parentheses sugar?
fn(impl SomeTrait) -> impl OtherTrait
How is impl Trait
meant to work in function pointers? Do you mean Fn
instead of fn
?
How is impl Trait meant to work in function pointers? Do you mean Fn instead of fn?
I meant fn
-- this basically gets at the heart of the question, which is the scoping of impl Trait
. In particular, one might expect that
fn foo(x: fn(impl Trait))
would desugar to
fn foo<T: Trait>(x: fn(T))
Of course -- as you righly point out -- if the T
were going to be scoped to x
-- sort of like for<T: Trait> fn(T)
-- we couldn't actually compile that with monomorphization (though we could do it with Fn
traits, potentially).
@nikomatsakis I see, thanks for the clarification!
I've done a bit of hunting, and I think that this is the most appropriate place to bring this up.
Allowing impl Trait
in this position would allow us to define async callbacks that borrow the input parameters. This can be done with macros at the moment, but not bare async fn
s due to the difficulty of expressing the higher-ranked lifetime bounds involving type parameters.
I would like to be able to write something like:
impl StructWithCallback {
// This works, but requires a macro/something to convert from `async fn` to something that has the right signature.
fn new(cb: for<'r> fn(&'r mut MyStruct) -> Pin<Box<dyn Future<Output = ()> + 'r>>) -> Self {
Self {
callback: Box::new(move |s| cb(s)),
}
}
// I believe that this would also work if we allowed `impl Future` in this position.
fn from_async_fn(cb: impl for<'r> FnMut(&'r mut MyStruct) -> (impl Future<Output = ()> + 'r)) -> Self {
Self {
callback: Box::new(move |s| Box::pin(cb(s))),
}
}
}
See also:
- context for the above, in playground form: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fce2ec77cee84e784fc56ad7f523dda8
- The journey that Goose is taking in this area:
- tag1consulting/goose#8 - initial spike based on https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343
- tag1consulting/goose#22 - current iteration, based on https://www.reddit.com/r/rust/comments/goh2be/how_to_store_an_async_future_to_a_function_with_a/
- This would also be useful in the Gotham project, where we are currently forced to pass the context object in by move, and return a tuple containing the context (which stops us from using ?). I made a spike of this a few months ago, and hit the same issue: https://github.com/alsuren/gotham/pull/3/files#r429578771
@alsuren I found a syntax / way that works:
(similar to how Box itself also works with any functions)
Would that change your stance that impl Trait is needed?
@LionsAd Unfortunately, that makes StructWithCallback generic, which means that you can't store it in a vec or hashmap. https://gist.github.com/rust-play/b89d17f00f58f071555339093d76a0a3
If you can't store it in a hashmap, then you can't store it in a routing table, so that's a non-starter for the Gotham use-case.
A possible way to avoid needing impl Trait
in this position would be to support writing fn foo(f: impl async Fn(SomeInput) -> SomeOutput)
. This would do the same lifetime elision and impl Future<Output=SomeOutput>
desugaring that async fn
does by default, so everything would fall out nicely in the wash.
https://gist.github.com/87ab93979d770314e6698a9867d1e7e5 solves this problem using a helper trait.
It might be worth waiting to see how https://github.com/gotham-rs/gotham/pull/450/files pans out, but it looks like we don't need impl Trait
in this position for the use-case I describe above.
I have a problem that is related to this issue: https://stackoverflow.com/questions/67458566/how-to-define-a-generic-function-that-takes-a-function-that-converts-a-slice-to. I want to write a generic function that takes a function that iterates a slice in different ways:
fn foo(make_iter: impl for<'a> Fn(&'a mut [i32]) -> impl Iterator<Item = &'a mut i32>) {
let mut data = [1, 2, 3, 4];
make_iter(&mut data);
}
Currently, this is not possible. Is there any way to work around this issue with stable Rust? The following attempt fails:
fn foo<'a, I: Iterator<Item = &'a mut i32>>(make_iter: impl Fn(&'a mut [i32]) -> I) {
let mut data = [1, 2, 3, 4];
make_iter(&mut data);
}
Note that data
should be generated inside the foo
function.
I can also use boxed trait object for this, but it requires an extra allocation:
fn foo(make_iter: impl for<'a> Fn(&'a mut [i32]) -> Box<dyn Iterator<Item = &'a mut i32> + 'a>) {
let mut data = [1, 2, 3, 4];
make_iter(&mut data);
}
I came up with one solution:
use std::iter::Rev;
use std::slice::IterMut;
trait MakeIter<'a> {
type Iter: Iterator<Item = &'a mut i32>;
fn make_iter(&mut self, slice: &'a mut [i32]) -> Self::Iter;
}
fn foo(mut make_iter: impl for<'a> MakeIter<'a>) {
let mut data = [1, 2, 3, 4];
make_iter.make_iter(&mut data);
}
struct Forward;
impl<'a> MakeIter<'a> for Forward {
type Iter = IterMut<'a, i32>;
fn make_iter(&mut self, slice: &'a mut [i32]) -> Self::Iter {
slice.iter_mut()
}
}
struct Backward;
impl<'a> MakeIter<'a> for Backward {
type Iter = Rev<IterMut<'a, i32>>;
fn make_iter(&mut self, slice: &'a mut [i32]) -> Self::Iter {
slice.iter_mut().rev()
}
}
fn main() {
foo(Forward);
foo(Backward);
}
But I am not sure whether it can be simplified.