Conflicting implementations of From<F: Fn()> with different return types

Playground

enum TypedFn {
    Int(Box<dyn Fn()->i32>),
    Float(Box<dyn Fn()->f32>),
    String(Box<dyn Fn()->String>),
}

impl<F: Fn()->i32 + 'static> From<F> for TypedFn {
    fn from(f: F) -> Self {
        TypedFn::Int(Box::new(f))
    }
}

impl<F: Fn()->f32 + 'static> From<F> for TypedFn {
    fn from(f: F) -> Self {
        TypedFn::Float(Box::new(f))
    }
}

impl<F: Fn()->String + 'static> From<F> for TypedFn {
    fn from(f: F) -> Self {
        TypedFn::String(Box::new(f))
    }
}


fn main() {
    let f: TypedFn = (|| 10).into();
    let f: TypedFn = (|| 10.0).into();
    let f: TypedFn = (|| "hello".to_string()).into();
}

It says these are conflicting implementations of trait From<_>, but why? These are clearly non-intersecting: if the function returns i32 it cannot return String or f32...

1 Like

The return type is an associated type, and coherence doesn't consider disjointness based on associated types.

5 Likes

Interesting. Are the parameters associated types as well?
Playground

This is how I look at it now: technically, no type can implement both Fn()->i32 and Fn()->f32, as we know that only functions and closures implement Fn, and every function or closure has the exact return type. But theoretically in the future it can become possible to implement Fn() traits for any type and thus to implement both Fn()->i32 and Fn()->f32 for one type, so that is why compiler complains.

No, but there is no disjointness when trait parameters vary. I can implement From<A> and From<B> for my struct after all. Whereas for a given set of implementation inputs,[1] you can only have one associated type. That's why disjointness based on associated types makes logical sense.

On stable, any closure / function time / function pointer which is higher-ranked over lifetimes implements some Fn* traits for multiple trait parameter types.

On unstable, you're as unrestricted with the Fn* traits as you are with any other trait, such as From.


  1. trait, concrete trait parameters, and concrete implementing type ↩ī¸Ž

2 Likes

Unless the Fn* traits change shape, which is probably impossible for backwards compatibility reasons, a type can't implement Fn<(), Output = i32> (aka Fn() -> i32) and Fn<(), Output = f32> (aka Fn() -> f32) as that would be two implementations of Fn<()> for the same type.

Just like you can't implement Deref twice, say. The fact that it has an associated type is irrelevant.

Try it on unstable.

3 Likes

So, I managed to make it work based on the comment in the linked RFC:

trait RetType<R> {
    fn into_typed_fn(self) -> TypedFn;
}

impl<R, F: Fn() -> R + RetType<R>> From<F> for TypedFn {
    fn from(f: F) -> Self {
        f.into_typed_fn()
    }
}

impl<F: Fn() -> i32 + 'static> RetType<i32> for F {
    fn into_typed_fn(self) -> TypedFn {
        TypedFn::Int(Box::new(self))
    }
}

impl<F: Fn() -> f32 + 'static> RetType<f32> for F {
    fn into_typed_fn(self) -> TypedFn {
        TypedFn::Float(Box::new(self))
    }
}

Playground

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.