Skip to content

Conflicting implementations with fn_traits and From #142037

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

Open
Nobody-alias-N opened this issue Jun 4, 2025 · 7 comments
Open

Conflicting implementations with fn_traits and From #142037

Nobody-alias-N opened this issue Jun 4, 2025 · 7 comments
Labels
A-trait-system Area: Trait system C-bug Category: This is a bug. F-unboxed_closures `#![feature(unboxed_closures)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Nobody-alias-N
Copy link

#![feature(unboxed_closures)]
#![feature(fn_traits)]
#![allow(unused)]
use std::rc::Rc;

struct MyFn {
    cb: Rc<dyn Fn()>,
}

impl FnOnce<()> for MyFn {
    type Output = ();

    extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
        (self.cb)()
    }
}

impl FnMut<()> for MyFn {
    extern "rust-call" fn call_mut(&mut self, _args: ()) {
        (self.cb)()
    }
}

// // Uncomment this to see the conflict From<F> implementation makes
// impl Fn<()> for MyFn {
//     extern "rust-call" fn call(&self, _args: ()) {
//         (self.cb)()
//     }
// }

// When MyFn implements Fn<()>
// From<F> will also 
// impl From<MyFn> for MyFn {}
impl<F> From<F> for MyFn
where
    F: Fn() + 'static, 
{
    fn from(f: F) -> Self {
        MyFn {
            cb: Rc::new(f),
        }
    }
}
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 4, 2025
@lolbinarycat lolbinarycat added A-trait-system Area: Trait system T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. F-unboxed_closures `#![feature(unboxed_closures)]` C-bug Category: This is a bug. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Jun 4, 2025
@theemathas
Copy link
Contributor

Here's a reproduction of this without using the Fn traits:

struct Thing;

trait Trait {}

impl<T: Trait> From<T> for Thing {
    fn from(_: T) -> Thing {
        Thing
    }
}

// Uncomment this to see the conflict
// impl Trait for Thing {}

I think this is working as intended. Rust is using the fact that Thing doesn't implement Trait to justify that the From<T> for Thing impl does not include a From<Thing> for Thing impl.

@zachs18
Copy link
Contributor

zachs18 commented Jun 5, 2025

I think this is expected, and unrelated to fn_traits.

Example without fn_traits:

#![allow(unused)]
struct MyFoo;

impl Clone for MyFoo {
    fn clone(&self) -> Self { unimplemented!() }
}

// // Uncomment this to see the conflict From<F> implementation makes
// impl Copy for MyFoo {}

// When MyFn implements Copy
// From<F> will also 
// impl From<MyFoo> for MyFoo {}
impl<F> From<F> for MyFoo
where
    F: Copy, 
{
    fn from(f: F) -> Self { unimplemented!() }
}

when MyFoo implements Copy, then the blanket impl<T> From<T> for T in core conflicts with the blanket impl<F> From<F> for MyFoo where F: Copy, because <MyFoo as From<MyFoo>> is now ambiguous. The compiler allows the original code, because no other crates could make non-semver-breaking changes that would break it:

  • Only this crate and core could ever implement From<MyFoo> for MyFoo or Copy for MyFoo, since From and Copy are in core, and MyFoo is in this crate
  • In the original code, this crate does not contain an impl Copy for MyFoo (and core does not contain an applicable blanket impl Copy for covering MyFoo), so the compiler knows that impl<F: Copy> From<F> for MyFoo cannot conflict.
  • Adding impl Copy for MyFoo to this crate makes the From impl conflict, so the compiler gives an error.

Also, (though I don't think it noticably affects the logic in this case) the Fn* traits are #[fundamental], meaning that adding a new impl of Fn to an existing type is a breaking change.

@Nobody-alias-N
Copy link
Author

You are absolutely right!
Thank you! Both of you,
for replying so quickly and
putting in your effort :)

@Nobody-alias-N
Copy link
Author

Nobody-alias-N commented Jun 5, 2025

I was hoping you could, nudge me
and everyone else who hits this wall
in the right direction.
Since I met this amount of enthusiasm,
it feels only right, if I try to share what
I think I would find easy.

@Nobody-alias-N
Copy link
Author

// Hypothetical solution, which does not exist
#![allow(unused)]
struct MyFoo;


trait Marked {}
impl Marked for MyFoo { }


impl Clone for MyFoo {
    fn clone(&self) -> Self { unimplemented!() }
}


impl<F> From<F> for MyFoo
where
    F: Clone + !Marked,
{
    fn from(f: F) -> Self { unimplemented!() }
}


// Not to be allowed to be used alone
// impl<F: !Marked> From<F> for MyFoo {} // disallowed

// Using !Marked to be limited to the crate
// where Marked is defined or even more strictly
// to the module.

@Nobody-alias-N
Copy link
Author

I think even with those limitations
this feature would make a lot of
implementations easily achievable
for everyone, who currently can
not find easy alternative

@Nobody-alias-N
Copy link
Author

From the link @zachs18 provided https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html
I can see I effectively want https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html#type-locality-and-negative-reasoning
That also contains a link to a pr Negative bounds, which is sadly closed.
I think even the strictest version of Negative reasoning would make me happy, if someone is knowledgeable enough and has enough willpower to implement it correctly.
In the meantime I would gladly read a tutorial for the workaround, and if that does not exist I would really want Negative reasoning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-trait-system Area: Trait system C-bug Category: This is a bug. F-unboxed_closures `#![feature(unboxed_closures)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants