[impl Trait] Should we allow impl Trait after -> in fn types or parentheses sugar?
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 OtherTraitto 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 Traitafter->infntypes or parentheses sugar?fn(impl SomeTrait) -> impl OtherTrait
How is
impl Traitmeant to work in function pointers? Do you meanFninstead offn?est31 at 2018-05-31 14:12:37
@est31
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 ofimpl Trait. In particular, one might expect thatfn foo(x: fn(impl Trait))would desugar to
fn foo<T: Trait>(x: fn(T))Of course -- as you righly point out -- if the
Twere going to be scoped tox-- sort of likefor<T: Trait> fn(T)-- we couldn't actually compile that with monomorphization (though we could do it withFntraits, potentially).Niko Matsakis at 2018-06-01 17:24:55
@nikomatsakis I see, thanks for the clarification!
est31 at 2018-06-01 22:11:04
I've done a bit of hunting, and I think that this is the most appropriate place to bring this up.
Allowing
impl Traitin 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 bareasync fns 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:
- https://github.com/tag1consulting/goose/pull/8 - initial spike based on https://users.rust-lang.org/t/how-to-store-async-function-pointer/38343
- https://github.com/tag1consulting/goose/pull/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
David Laban at 2020-05-23 21:43:14
@alsuren I found a syntax / way that works:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ef3ca927495338661420b93141c5d0b6
(similar to how Box itself also works with any functions)
Would that change your stance that impl Trait is needed?
Fabian Franz at 2020-05-26 14:44:47
@LionsAd Unfortunately, that makes StructWithCallback<T> 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 Traitin this position would be to support writingfn foo(f: impl async Fn(SomeInput) -> SomeOutput). This would do the same lifetime elision andimpl Future<Output=SomeOutput>desugaring thatasync fndoes by default, so everything would fall out nicely in the wash.David Laban at 2020-05-26 19:22:31
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 Traitin this position for the use-case I describe above.David Laban at 2020-07-01 19:22:45
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
datashould be generated inside thefoofunction.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); }EFanZh at 2021-05-14 12:01:06
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.
EFanZh at 2021-05-28 14:57:59