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

RFC: inherent trait implementation #2375

Open
wants to merge 1 commit into
base: master
from

Conversation

Projects
None yet
10 participants
@newpavlov
Copy link

newpavlov commented Mar 27, 2018

Continuation of #2309.

Fixes: #1880,#1971

This RFC allows us to write the following code:

#[inherent]
impl Bar for Foo {
    // code
}

Which allows methods from Bar trait to be used on Foo instances without having Bar in the scope.

Rendered

@newpavlov

This comment has been minimized.

Copy link
Author

newpavlov commented Mar 27, 2018

cc @nikomatsakis, @aturon

This RFC assumes that rust-lang/rust#48444 will be treated as a feature and not as a bug.

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Mar 27, 2018

To clarify: #2309 was postponed until we figure out the story for delegation. While you talk about it a bit in this new RFC, it's not clear what in this proposal changes the situation compared to when the lang team previous took up this question. Can you expand on your thinking here?

@newpavlov

This comment has been minimized.

Copy link
Author

newpavlov commented Mar 27, 2018

I was under impression that the previous RFC was closed due to the lack of reaction from @Diggsey to add requested changes, and not postponed. (ctrl+f "postpone" yields zero results)

While I agree that delegation RFC and inherent traits should be discussed together, in my opinion RFCs have orthogonal scopes and similar only in the end effect. As was shown in the text, delegation can be nicely composed with #[inhrent] attribute and there is no need to overload delegation with an additional functionality.

Yes, the new delegation RFC draft mentions inherent trait impls as a possible future extension, but I don't think that using delegate is a correct approach here. Also in my opinion inherent trait implementations can have a much bigger impact on the ecosystem (including stdlib and core) than the delegation RFC, thus it should consider as one of the main topics and not as a vague future extension.

@Diggsey

This comment has been minimized.

Copy link
Contributor

Diggsey commented Mar 27, 2018

@newpavlov I don't think that's an accurate assessment. The RFC was closed because:

We discussed this in the lang team meeting today, and everyone agreed it would be nice to provide "inherent trait methods" of some sort. However, we also felt that this feature would probably fall out naturally from a solution to the more general problem of trait and method delegation. We'd like to see another RFC for delegation before we accept a feature like this.

ie. the lang team wanted a more general solution

@scottmcm scottmcm added the T-lang label Mar 28, 2018

@newpavlov

This comment has been minimized.

Copy link
Author

newpavlov commented Mar 31, 2018

@Diggsey
Yeah, my mistake. Though by reading discussion of the new version of delegation RFC it looks like this use case is not going to "fall out naturally" any time soon.

@aturon
So should I close this RFC until lang-team decides how inherent traits and delegation will interact with each other or should I leave it to be for now?

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Apr 19, 2018

@newpavlov Sorry for the delay, was out on vacation.

Yes, I think it should probably be closed for the time being, especially since there's now a delegation RFC. Would be good to revisit after Rust 2018 ships!

@dwijnand

This comment has been minimized.

Copy link
Member

dwijnand commented Dec 10, 2018

Where's the delegation RFC? My searches failed to find anything.

@newpavlov

This comment has been minimized.

Copy link
Author

newpavlov commented Dec 10, 2018

@dwijnand #2393

@newpavlov

This comment has been minimized.

Copy link
Author

newpavlov commented Dec 13, 2018

@aturon
How about reopening this PR?


- Increased complexity of the language.
- Hides use of traits from users.

This comment has been minimized.

@Manishearth

Manishearth Mar 28, 2019

Member

The older RFC was closed because it may be preferable to do a general purpose delegation RFC. This should be mentioned and preferably addressed.

#2309 (comment)

This comment has been minimized.

@newpavlov

newpavlov Apr 2, 2019

Author

Firstly, I think that delegation and inherent impls are orthogonal to each other. Secondly, IIRC the functionality which may remove the need for inherent impls is listed as potential future extension of delegation RFC, i.e. the current delegation RFC does not solve the target issue of this proposal. And thirdly, I believe both proposals should be reviewed in parallel and pros and cons of both proposals should be weighted and compared, so I don't think "it may be preferable" is a good argument for closing competing proposals.

This comment has been minimized.

@burdges

burdges Apr 3, 2019

I do like inherent trait impls, but they are only a very minor paper cut, while delegation is extremely important, after say ATCs and const generics. Inherent trait impls are simply not worth risks of complicating delegation, so maybe the previous postponement should stand until the delegation conversation happens.

As an aside, I'd worry far more about the state of doc comments on trait impls than about inherent trait impls, as poorly documented impls create more headaches. I donno if rust-lang/rust#18701 ever got fixed, but if so then this may only be cultural now.

```

Any questions regarding coherence, visibility or syntax can be resolved by
comparing against this expansion, although the feature need not be implemented

This comment has been minimized.

@Manishearth

Manishearth Mar 28, 2019

Member

I think this should be explicitly specced out, especially since this expansion actually introduces an ambiguity in method calls.


# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

This comment has been minimized.

@Manishearth

Manishearth Mar 28, 2019

Member

This should be fleshed out, including:

  • What impl blocks it can be applied to (can the trait be foreign?)
  • Whether it works with #[fundamental]
  • Does it error if you have an inherent method with the same name?

This comment has been minimized.

@burdges

burdges Mar 28, 2019

I'd think it should error if you have an inherent method with the same name, like if you had two inherent methods with the same name.

This comment has been minimized.

@newpavlov

newpavlov Apr 1, 2019

Author

What impl blocks it can be applied to (can the trait be foreign?)

Yes, it can. One of the main use-cases for inherent trait implementations is implementation of crates defined in external crates.

Whether it works with #[fundamental]

I am not sure why it should not, though I can't say I fully understand #[fundamental] semantics.

Does it error if you have an inherent method with the same name?

Initially I though it should error, but as I wrote below probably it may be better to issue a warning and shadow inherent trait methods by true inherent methods.

I will try to update the text based on your review! Thanks!

[drawbacks]: #drawbacks

- Increased complexity of the language.
- Hides use of traits from users.

This comment has been minimized.

@Manishearth

Manishearth Mar 28, 2019

Member

If this works the way I think it does, this actually changes our trait stability story, and that's a major drawback.

Currently, adding a defaulted method to a trait is not a major breaking change, at worst it will cause ambiguity errors, so it's considered minor.

Now, a dependency can add a method to a trait you #[inherent] impl, which can clash with a method of your own, causing your build to fail in a way that requires you to change your API to fix. We're largely okay with builds failing due to new ambiguities (clashing method names across traits, adding something to a module that's glob imported, etc) and such things are categorized as minor, which basically means it's fine to do as long as the fallout isn't too much. With this RFC, adding a defaulted method has the potential to break a library user in a way that requires them to rename a method in their public API, causing a breaking change for them.

This should be explored and addressed in this RFC, and as a bare minimum should be called out in this section.

(One "fix" is to only allow local traits)

This comment has been minimized.

@Havvy

Havvy Mar 28, 2019

Contributor

Another fix is to list out the method names that are inherent, although that becomes quite a burden for e.g. iterator methods.

This comment has been minimized.

@burdges

burdges Mar 28, 2019

I'm unsure if the existing situation should be classified as so minor because you interact with many types almost exclusively through their traits. As an example, there are definitely traits with a new() method that defies the convention of being inherent new(), so those traits adding new() created exactly the same breakage you describe here.

As a fix, I'd suggest #[inherent] being a method attribute for items inside an impl, so

impl TraitWIthBadMethodNames for MyType {
    fn new() -> Self { .. }  // Not inherent

    #[inherent]
    fn new_plus() -> Self;  // Inherent but default body used

    #[inherent]
    fn foo() { .. }  // Inherent with body supplied here
}

And #[inherent] applied to the impl is equivalent to it being applied to all methods and associated types and constants.

This comment has been minimized.

@Manishearth

Manishearth Mar 28, 2019

Member

As an example, there are definitely traits with a new() method that defies the convention of being inherent new(), so those traits adding new() created exactly the same breakage you describe here.

Yes, but this breakage is easily fixed by using UFCS. This is not true for breakage caused in the world of #[inherent].

The "minor" terminology comes from the API evolution RFC, such changes may cause crates to stop compiling, however:

  • This can be fixed with a trivial change local to the crate
  • The fix does not break upstream crates

Without this notion of "minor" being allowed, crates wouldn't be able to add anything new (types, traits, functions, or methods) without it being considered a breaking change.

Furthermore, the API evolution RFC mentions in the trait method case that you should check to ensure the fallout isn't too great; which is where your new() example falls short: a trait adding a new() method would probably have lots of fallout.

In other words, when I use the term "minor" here, I'm using a precisely defined term from another RFC. Whether or not it is actually "minor" is irrelevant, I'm talking about what we do and don't consider breaking, which we have specced in terms of this major/minor categorization.

This comment has been minimized.

@newpavlov

newpavlov Apr 1, 2019

Author

Will it be possible to implement shadowing of inherent trait methods by true inherent methods? Compiler will warn on clashing inherent names while building crate which uses #[inherent], but will use true inherent method by default and for trait method you will have to use explicit Trait::foo(value). This way we will avoid code breakage on "minor" upstream changes.

Though I think in practice such collisions should be extremely rare.

@burdges

This comment has been minimized.

Copy link

burdges commented Mar 28, 2019

How are polymorphic traits handled? I'd presume

#[inherent]
impl<'de> Deserialize<'de> for MyType { .. }

works roughly like

impl MyType {
    fn deserialize<'de,D>(deserializer: D) -> Result<Self, D::Error>
    where D: Deserializer<'de> { ... }
}

Yet, how should separate impl blocks be handled?

#[inherent]
impl Borrow<str> for MyType { }

#[inherent]
impl Borrow<[u8]> for MyType { }

We'd likely want roughly

impl MyType {
    fn borrow<R>(&self) -> R
    where Self: Borrow<R>
    { self.borrow() }
}
@newpavlov

This comment has been minimized.

Copy link
Author

newpavlov commented Apr 1, 2019

@burdges
I think that initial implementation should simply forbid inherent implementations for polymorphic traits. Later we could do an extension, though I don't see how it can be done nicely right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.