Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[impl Trait] Should we allow impl Trait after -> in fn types or parentheses sugar? #45994

Open
nikomatsakis opened this issue Nov 15, 2017 · 9 comments

Comments

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Nov 15, 2017

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.

@est31
Copy link
Contributor

@est31 est31 commented May 31, 2018

Should we allow impl Trait after -> in fn 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?

@nikomatsakis
Copy link
Contributor Author

@nikomatsakis nikomatsakis commented Jun 1, 2018

@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 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).

@est31
Copy link
Contributor

@est31 est31 commented Jun 1, 2018

@nikomatsakis I see, thanks for the clarification!

@alsuren
Copy link
Contributor

@alsuren alsuren commented May 23, 2020

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 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:

@LionsAd
Copy link

@LionsAd LionsAd commented May 26, 2020

@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?

@alsuren
Copy link
Contributor

@alsuren alsuren commented May 26, 2020

@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.

@alsuren
Copy link
Contributor

@alsuren alsuren commented Jul 1, 2020

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.

@EFanZh
Copy link
Contributor

@EFanZh EFanZh commented May 14, 2021

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);
}
@EFanZh
Copy link
Contributor

@EFanZh EFanZh commented May 28, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants