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

Inherent impls for traits #1971

Closed
burdges opened this issue Apr 13, 2017 · 9 comments
Closed

Inherent impls for traits #1971

burdges opened this issue Apr 13, 2017 · 9 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@burdges
Copy link

burdges commented Apr 13, 2017

As written, this code works but the commented out line gives error[E0576]: cannot find method or associated constant meow in trait Foo.

pub trait Foo { }

struct Bar;

impl Foo  {
    pub fn meow() {  println!("Hello World!")  }
}

impl Foo for Bar { }

fn main() {
    Foo::meow();
    // <Bar as Foo>::meow();
}

If we want an inherent-like method meow for every type that satisfying Foo, but do not want clients modifying it, then we need a helper trait.

pub trait ImplFoo { 
    fn meow() {  println!("Bye World!")  }
}

impl<T> ImplFoo for T where T: Foo { }

fn main() {
    Bar::meow();
}

Worse, we need new help traits for every inherent-like block we want to create, so our API gets polluted with several exposed helper traits. Also this Foo::meow() looks kinda iffy.

I'd think inherent impls could simply "work as expected" for traits, so all three call sites would call the original :

impl Foo  {
    pub fn meow() {  println!("Hello World!")  }
}

For visibility purposes, these inherent trait methods could be treated as methods on an invisible wrapper struct that has the same name as the trait Foo:

struct Foo<T: Foo>(T);
impl<T: Foo> Foo<T> {
    pub fn meow() {  println!("Hello World!")  }
}

In this way, if Foo is defined in module A and meow in module B then the visibility of meow can be controlled as you would control the visibility of B::Foo.

@mark-i-m
Copy link
Member

I didn't even know you could impl Trait {...}! I am curious now about the use case for this. Can you give an example of when you might want to use this in real code?

Also, would the same purpose be achieved if we had something like

pub trait Foo {
    #[no_override] // specifies that implementors cannot override the default
    pub fn meow() {  println!("Hello World!")  }
}

@eddyb
Copy link
Member

eddyb commented Apr 14, 2017

@mark-i-m No, this is not about methods of the trait, but methods for the trait object.
For example, impl Any { fn downcast_ref<T>(&self) -> Option<&T>; } will end up with self: &Any.
Not an arbitrary implementer of the trait, but only the trait object itself gets downcast_ref.

@mark-i-m
Copy link
Member

@eddyb Thanks for the clarification! That makes a lot more sense now 😄

@burdges
Copy link
Author

burdges commented Apr 15, 2017

I see, thanks for the explanation @eddyb. In that case, the syntax I'm proposing should maybe be impl<T> Self<Foo> { } or impl<T> Inherent<Foo> { } or impl<T> impl Foo { } or such.

In some cases, one can write (&x).meow() as a work around, maybe one can even avoid the & if deref can return a trait object, except object safety becomes an inconvenience.

In that vein, Deref could make a nicer helper :

struct ImplFoo<T: Foo>(T) where T: Sized;

impl<T: Foo> Deref for T {
    type Target = ImplFoo;
    pub deref(&self) -> &ImplFoo { unsafe { transmute(self) } }
}
impl<T: Foo> DerefMut for T {
    pub deref_mut(&mut self) -> &mut ImplFoo { unsafe { transmute(self) } }
} 

impl<T: Foo> ImplFoo<T> {
    pub fn meow() {  println!("Hello World!")  }
}

At least this way the inherent impl is a real inherent impl so you can create multiple impl blocks without needing numerous healer traits. It would not for Bar::meow() though.

Also, I can improve the original trait based work around with

pub trait Foo : ImplFoo { }
impl<T> ImplFoo for T where T: Foo { .. }
pub trait ImplFoo { .. }

@burdges
Copy link
Author

burdges commented Apr 17, 2017

I suppose the syntax impl<T: Foo> T works best, assuming this all makes sense. Alternatively, one could imagine some "transparent" wrapper struct scheme, which might let you provide local impls for traits too, but I'd think delegation sounds like a more likely route to that.

@mark-i-m
Copy link
Member

I feel like impl<T: Foo> T would get confused easily with impl<T> Foo for T

@eddyb
Copy link
Member

eddyb commented Apr 17, 2017

Frankly it's correct syntax if you want to add methods to all the types that implement a trait.
This comes for free with crate-private inherent impls for out-of-crate types, so you should focus on that.

@burdges
Copy link
Author

burdges commented Apr 17, 2017

I'd think impl<T> Foo for T should normally fail, while the inherent impl<T> T where T: Foo always succeeds. Oh I see. You're worried folks would accidentally write the inherent, when they really mean the generic trait version.

A transparent wrapper scheme might look like an attribute like say #[reverse_delegate] or #[auto_wrap] on a wrapper struct restricted to the trait.

@Centril
Copy link
Contributor

Centril commented Oct 8, 2018

Closing in favor of #1880.

@Centril Centril closed this as completed Oct 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

4 participants