Why Fn trait doesn't constrain generic param

Just trying to understand why does following code fail with the type parameter 'R' is not constrained by the impl trait, self type, or predicates, looks pretty constrained to me.

trait Tr {}

struct W<F>(F);

// Why it doesn't work?
impl<F,I,R> Tr for W<F>
    where F: FnOnce(I) -> R
{}

To work with Fns in my generic code I have to wrap them all, which is a major inconvenience:

// Workaround
struct Fun<F,I,R>(F, PhantomData<fn(I)->R>);

impl<F,I,R> Tr for W<Fun<F,I,R>>
    where F: FnOnce(I) -> R
{}

In theory, one type can implement both FnOnce(I) -> R1 and FnOnce(I) -> R2 for different types R1 and R2, which would make your first implementation ambiguous.

That's what I don't understand, how single type can implement both FnOnce(I) -> R1 and FnOnce(I) -> R2 ? It can't, FnOnce is a closure or fn if nothing is captured and both arguments and return values are set for it.

I don’t think that is possible, actually, since the following is not ambiguous:

struct Fun<F, R>(F)
where
    F: FnOnce() -> R;

playground

However, it is possible for a single type to implement FnOnce with different argument types, e.g. with lifetimes:

pub fn f() -> impl for<'a> FnOnce(&'a str) -> &'a str {
    |x| x
}

playground.
Here return type of f implements FnOnce(I)->R for a potentially infinite number of I, R pairs.

2 Likes

R is not constrained because I is not constrained. If I was constrained, R would be constrained too.

That is,[1] you can't implement both

  • FnOnce(I) -> R1
  • FnOnce(I) -> R2

But you can certainly implement both

  • FnOnce(I1) -> R1
  • FnOnce(I2) -> R2

Which I think is what @itim was getting at.


  1. Given the current unstable Fn traits (which might be hard to evolve past) ↩︎

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.