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 traits #2309

Closed
wants to merge 1 commit into from
Closed

Conversation

Diggsey
Copy link
Contributor

@Diggsey Diggsey commented Jan 22, 2018

Provides a mechanism to declare "inherent traits" for a type defined in the same crate. Methods on these traits are callable on instances of the specified type without needing to import the trait.

See #1880

Rendered

@Diggsey Diggsey changed the title Inherent traits RFC: Inherent traits Jan 22, 2018
@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jan 22, 2018
# Unresolved questions
[unresolved]: #unresolved-questions

- Syntax bike-shedding
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps #[delegate(Bar)] ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me "delegation" has always been about making a trait impl for some type reuse the code from some other type's impl of the same trait, and the presence of delegation would be completely unobservable to client code. impl to another type. So "delegating to a trait", in a way that changes whether or not clients need an import, feels to me like a category error at best.

Though I'm certainly not a fan of #[include(Trait)]. That looks so similar to a C/C++ #include that I had to pause and think to figure out what the RFC was actually proposing.

Copy link
Contributor

@Ixrec Ixrec Jan 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I assume the bar() method signature in the Reference-level Explanation is meant to appear in rustdoc. And presumably, you could replace the usage of the "inherent impl" feature with writing out those methods by hand, without clients being able to observe the change in any way.

If I'm interpreting that right, perhaps the "correct"/least misleading/most guessable-by-newbies syntax would be a macro rather than an attribute?

impl Foo {
    inherent_impl!(Bar)
    fn foo(&self) { println!("foo::foo"); }
}

Incidentally, even if everyone else prefers an attribute, I'd still prefer that the attribute be placed inside the impl block rather than on top of it, simply because we're not modifying the impl block as a whole, we're adding to it.

Copy link
Contributor

@Centril Centril Jan 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me "delegation" has always been about making a trait impl for some type reuse the code from some other type's impl of the same trait

To me too =) But I thought it felt sufficiently similar in spirit that it might work?
I'm not particularly fond of #[include(Bar)] (but could certainly live with it..).

If I'm interpreting that right, perhaps the "correct"/least misleading/most guessable-by-newbies syntax would be a macro rather than an attribute?

That would have to be a very magic and special macro since they don't have access to the trait definition otherwise.

Copy link

@burdges burdges Jan 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not impl Foo { #![include_trait(Trait)] } so inside the inherent impl block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@burdges I think those two (#[ outside and #![ inside) are interchangeable, so that's the same as this proposal (modulo the naming)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Afaik, all inside attributes use #![..] while all outside attributes use #[..] but not all inside attributes can be used outside and visa versa, so I'm restated @Ixrec last proposal with a fresh coat of paint.

@Centril
Copy link
Contributor

Centril commented Jan 22, 2018

What if you only want to make a certain subset of methods inherent as in #[include(Bar::foo)]?

@Diggsey
Copy link
Contributor Author

Diggsey commented Jan 22, 2018

What if you only want to make a certain subset of methods inherent as in #[include(Bar::foo)]?

I'm not really opposed to that, but I don't know how much extra value there is in it.

@Centril
Copy link
Contributor

Centril commented Jan 22, 2018

I'm not really opposed to that, but I don't know how much extra value there is in it.

What comes to mind is that you might want to reuse the names for some methods in the trait for other purposes in the inherent impl that are not exactly like the trait impl. I assume that if #[include(Bar)] has a method foo(..) then the inherent impl can not have foo(..) in the text directly since that would amount to overloading.

@leoyvens
Copy link

leoyvens commented Jan 22, 2018

Where does the annotation go? Over the definition of the type? Over the impl of the trait? Over the inherent impl? You propose over the inherent impl, but what if there are multiple inherent impls to which the trait impl applies?

Typically if I can call foo.bar() I'd expect to also be able use a more extensive form using the name of the trait such as Bar::bar(&foo). I might also expect Foo::bar and Bar::bar to refer to the same function. Also I'd expect that if I do have Bar in scope, that would not cause a conflict with foo.bar(), but with the proposed expansion a name conflict would arise between Foo::bar and Bar::bar. I'm afraid this makes the RFC unworkable as written.

Whether this feature creates new symbols or not is not merely an implementation concern but a language specification concern which this RFC should discuss in more depth.

The motivation of the RFC is legitimate, perhaps trying to optimize the usability of trait imports would be more consistent than trying to completely get around the need of importing the trait name.

@burdges
Copy link

burdges commented Jan 22, 2018

If you want to do individual functions too then consider impl Foo { fn bar = Bar::bar; } but impl Foo { fn bar = Bar::*; } gets confusing with associated types, constants, etc.

Also, I'm unfamiliar with the rules for inherent impls for a local type coming from another crate, presumably they do not work, so no impl<F: Bar> { #![include_trait(Bar)] }, right?

@Diggsey
Copy link
Contributor Author

Diggsey commented Jan 22, 2018

@leodasvacas

Where does the annotation go? Over the definition of the type? Over the impl of the trait? Over the inherent impl? You propose over the inherent impl, but what if there are multiple inherent impls to which the trait impl applies?

This is covered in the RFC - it goes on any inherent impl. If you were to implement the same inherent method multiple times, either by including the same trait more than once, or by including traits with overlapping method names, then that would be an error.

Also I'd expect that if I do have Bar in scope, that would not cause a conflict with foo.bar(), but with the proposed expansion a name conflict would arise between Foo::bar and Bar::bar. I'm afraid this makes the RFC unworkable as written.

As you can see, there is no conflict here, the inherent method is preferred:
https://play.rust-lang.org/?gist=2168c114043ff068bff7614d841962f1&version=stable

Whether this feature creates new symbols or not is not merely an implementation concern but a language specification concern which this RFC should discuss in more depth.

Please explain how this could be a concern, and I will endeavour to address that in the RFC.

@leoyvens
Copy link

As you can see, there is no conflict here, the inherent method is preferred:

Thank you for the correction!

Please explain how this could be a concern, and I will endeavour to address that in the RFC.

Given the definitions:

pub struct Foo;
trait Bar { fn bar(self); }
impl Bar for Foo { fn bar(self) {} }
#[include(Bar)]
impl Foo {}

Is the expression Bar::bar as fn(Foo) == Foo::bar as fn(Foo) true or false?

@scottmcm
Copy link
Member

First, 👍 to being able to define methods on traits without ergonomic loss. That would definitely help me remove not-for-syntax-extension macros.

I'm surprised to see this syntax attached to an impl block, though. Why not to the type definition, like [derive] is, since it needs to be in the same crate anyway?

@Centril
Copy link
Contributor

Centril commented Jan 23, 2018

@scottmcm The generic parameters of a type definition may be applied with concrete arguments in at an impl site. The trait may not be impled for the generic impl, but impled for more specific impls, so I think this is strictly more general as proposed in the RFC.

# Rationale and alternatives
[alternatives]: #alternatives

- Do nothing: users may choose to workaround the issue by manually performing the expansion if required.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative: Allow inherent impls as long as all functions are crate-private. Inherent methods are preferred over trait methods, but warned about. So if another crate adds a trait method, it does not conflict or change behaviour here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inherent methods are preferred over trait methods, but warned about.

I don't think they are warned about (see my playground link above)?

I think making the functions crate-private would undermine the primary use-cases of this RFC. The goal is to expose trait methods as inherent methods on a type, as part of the public API of a crate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... one could allow public inherent methods and require importing them via use crate_name::impl::Type; or something similar

@crlf0710
Copy link
Member

crlf0710 commented Jan 23, 2018

👍 I've thought about this idea exactly as many times as i forgot to use rand::Rng at compilation #1.

@burdges
Copy link

burdges commented Jan 23, 2018

Right now, if you struct Foo; trait Foo {} then you get an error, but conceivably trait Foo : Bar; could actually specify that (a) impl Bar for Foo is required and (b) reexport Foo as Bar as inherent impls.

I kinda dislike overloading the meaning of trait like this, but maybe worth mentioning. I suppose the syntax roughly breaks down as "trait impls can make themselves inherent" or "inherent impls can load a trait"

An attribute inside an inherent impl might load a trait, which clarifies its inherentness, ala impl Foo { #![include_trait(Bar)] }, but errors if the trait impl does not exist. In this vein, we've discussed impl Foo { pub use Bar; } previously, but I think that notation makes sense for several things, so no go.

An attribute outside the trait impl cannot error like that, ala #[inherent] impl Bar for Foo { ... }, and maybe lets you target functions consistently, ala impl Bar for Foo { #[inherent] fn bar() ... }, but this either limits where the crate where this be done or else requires thinking in terms of specialization more. In other words, we do not want lineA and lineB below conflicting, which may get interesting.

// Crate 1
trait Bar { fn bar(self) { ... } }
#[inherent] impl<F: ...> Bar for F { ... } // LineA

// Crate 2
struct Foo;
#[inherent] impl Bar for Foo { ... }  // LineB
... let foo - Foo; x.bar() ; ...

@aturon aturon self-assigned this Jan 25, 2018
@aturon
Copy link
Member

aturon commented Jan 25, 2018

cc @sfackler

```rust
impl Foo {
#[inline]
pub fn bar(&self) { <Self as Bar>::bar(self); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unhappy with merging items from different trait impls together with each other and inherent impl items (syntactically) for reasons described in #2303 (comment).
impl Bar for Foo { ... } should still be a separate item, IMO, just marked as "inherent-like" somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petrochenkov I've read your comment a number of times and I can't really make any sense of it - this RFC does not propose allowing implementing traits via inherent methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. I see.
To be honest I read it a couple days before writing this comment and it looks like I misremembered everything.

There was a few previous iterations on this idea, some of them made methods from specially marked trait impls directly available to name resolution without having the trait in scope and without delegation, like this RFC proposes.
(Trait objects currently behave very similarly, see e.g. rust-lang/rust#41221)

@newpavlov
Copy link
Contributor

I like inherent trait implementation more compared to the "including" trait methods into type impl block.

Often types do not have their own methods and express their behavior purely through traits. For example if we'll take RustCrypto/hashes I would like to write:

struct Sha256 { .. }

// `#[inherent]` could be a keyword instead
// it only can be used if type was defined in the current crate
#[inherent]
impl FixedOutput for Sha256 { .. }

Which will allow users to use Sha256 without explicitly depending on digest (which defines hash functions related traits) crate and without explicitly importing several traits when they just want to compute hash of some data without writing any generic code. And here we do not need to write dummy impl Sha256 {}.

@aturon
Copy link
Member

aturon commented Feb 7, 2018

I agree with @newpavlov that it seems better to associate the annotation with the trait impl rather than the inherent impl block. There are a couple of reasons for this.

First, the blocks may not have the same "headers". What would this RFC do for a case like the following:

impl SomeTrait for MyType<u32> { ... }
impl SomeTrait for MyType<i32> { ... }

#[inclue(SomeTrait)]
impl<T> MyType<T> { ... }

There are lots of other bad combinations you can make. For example, you could write include annotations on multiple different inherent blocks.

Also, if you don't otherwise have inherent items, you need to write an empty block to use this feature. (Not a major point, just worth noting).

By contrast, writing #[inherent] on the trait impl, as @newpavlov suggests, has a very straightforward meaning: it effectively copies the trait impl block, minus the trait part.

If the RFC was changed in this manner, I'd be in favor of moving to FCP.

@scottmcm
Copy link
Member

scottmcm commented Feb 7, 2018

The generic parameters of a type definition may be applied with concrete arguments in at an impl site. The trait may not be impled for the generic impl, but impled for more specific impls, so I think this is strictly more general as proposed in the RFC.

One could presumably also desugar something like this:

#[inherent_trait(Foo)]
struct Bar<T, U> { ... }

to produce

impl<T, U> Bar<T, U> {
    fn foo(...) where Self : Foo { <Self as Foo>::foo(...) }
}

so that the method is always there, it's just only callable for combinations with the trait actually impl'd. (Sortof like how, iiuc, #[derive(Clone)] on a generic always emits a Clone impl, but that clone impl is bounded to Clone being implemented on the generic parameters.)

That said, I like the above #[inherent] way better, since it entirely gets out of the business of naming traits in attributes, with all the namespacing and refactoring complications that implies. (And is much more local, since the block that it's applied to has all the type parameters and resolved names and such.)

```

The method `bar` is now callable on any instance of `Foo`, regardless of whether the `Bar` trait is currently in scope, or even whether
the `Bar` trait is publically visible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contemplating this for myself, what are the other options?

Some bad ones:

  • Inherit pub from the trait: gets into all the "what's pub, really" discussions
  • Put something on the methods: confusing to have pub there suddenly, doesn't work for trait methods with default impls
  • Put something on the trait: no, traits shouldn't ever care about this

A possibility: #[inherent(A)] puts pub(A) on them. So just #[inherent] is pub, but you can use #[inherent(crate)] (or maybe #[inherent(in foo::bar)]?) to limit it if helpful.

But really, in the same crate the trait import isn't a big deal, so that's probably not worth bothering with, and just "it's always pub" (as the RFC says) is probably the way to go 👍

@Diggsey
Copy link
Contributor Author

Diggsey commented Feb 7, 2018

@aturon

Firstly, I don't think your example is problematic: the compiler will see that the trait is not implemented for all MyType<T>, and so the include is an error. The compiler already does these checks when deciding whether it's possible to call a trait method on a generic type, so I don't think it should be an issue.

Secondly, that would unnecessarily limit the feature. Here are some examples that work with this version of the RFC, but wouldn't under your suggestion:

#[derive(X)]
struct Foo;

#[include(X)]
impl Foo {}
// Crate 1
impl<T> X for T {}

// Crate 2
struct Foo;

#[include(X)]
impl Foo {}
struct Foo<T>;
impl<T> X for Foo<T> {}

#[include(X)]
impl Foo<u32> {}

The reason it makes sense to put on the inherent impl block is because of how it behaves wrt the coherence rules: the attribute is allowed precisely on those types for which you can provide an inherent impl.

@aturon
Copy link
Member

aturon commented Feb 7, 2018

@Diggsey I don't find those additional uses particularly compelling, to be honest.

My worry is that include has many more corner cases compared to inherent. The implementation of the latter is extremely straightforward, and could be done with a macro_rules macro. That also gives a very simple mental model for what it does.

@Diggsey
Copy link
Contributor Author

Diggsey commented Feb 7, 2018

My worry is that include has many more corner cases compared to inherent. The implementation of the latter is extremely straightforward, and could be done with a macro_rules macro. That also gives a very simple mental model for what it does.

Neither version can be done with a macro_rules macro, because it requires the ability to reflect on the members of a trait. (Unless you mean use the trait implementation to figure out the methods, but that doesn't work for methods with a default implementation).

Could you give an example of one of those corner cases? There exists a very straightforward implementation: for each member of the trait, define an inherent method with the same name which delegates to the trait. The checking I described previously doesn't even need to be explicit: by trying to delegate to the corresponding trait method, the compiler will already catch cases where it's not possible.

@newpavlov
Copy link
Contributor

After some thought @Diggsey example indeed could be quite useful for extension traits. In my case it's a Digest trait which is a wrapper around several traits and provides convenient helper methods.

BTW what about integration with rustdoc? Will it be able to distinguish inherent implementations or will it show duplicated methods?

@aturon
Copy link
Member

aturon commented Feb 8, 2018

Nominating for Lang Team discussion.

@aturon
Copy link
Member

aturon commented Feb 8, 2018

cc @rust-lang/lang

@aturon
Copy link
Member

aturon commented Feb 8, 2018

I'd love to move quickly on this; I think it could be really useful for the epoch release, in the sense that I think we'd want to use this in std (and other foundational libraries) to focus the docs on the "main" APIs for given types.

We discussed this briefly in the Lang Team meeting; other team members are going to try to give their thoughts soon.

@cramertj
Copy link
Member

cramertj commented Feb 8, 2018

A couple questions:

  • If I have trait Foo: Bar { ... } and #[include(Foo)], do I get Bar's methods as inherent methods, or just Foo's?
  • Currently, it's non-breaking-ish change to add defaulted methods to traits. If I have an inherent method named fn foo and #[include(Baz)], then later on someone adds a defaulted method called foo to Baz, how is this conflict resolved?

- Increased complexity of the language.

# Rationale and alternatives
[alternatives]: #alternatives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about inverting the declaration location to something like

#[inherent]
impl Foo for Bar {
    ...
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in the lang team meeting-- IMO placing the attribute on the impl "feels" better, but it doesn't seem like it would work with derived traits and generic imps (e.g. impl<T> ToString for T where T: Display { ... } -- how do I get an inherent to_string for u32?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point.


The `include` attribute is not valid on `impl <trait> for <type>` style `impl` blocks.

# Reference-level explanation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit underspecified for traits with type parameters.

@nikomatsakis
Copy link
Contributor

Some thoughts: First off, I really want something like this feature. I however am not keen on the #[include(Path)] formation, for a couple of reasons.

The first one is that we have generally avoided paths in attributes, because of the need to figure out the hygiene story there. (Maybe that concern is somewhat obsolete? I don't think so though.) But it also (to me) just doesn't quite feel like something that an attribute should be used for -- it just feels kind of non-obvious to me, and it doesn't feel "first class" enough for something that is going to be regularly used to define the type's API.

The #[inherent] attribute that @aturon proposed seems like a more obvious API to me. In general, I think it feels "tidier" to be able to take the two independent impls and have them remain independent. I'm hard put to put my finger on how to express this.

That said, I do find the idea that one might want to have a blanket impl of a trait but still make the trait "inherent" to a struct somewhat compelling.

I was wondering about other syntactic possibilities. In the past, I've considered something like struct Foo<T>: Display, which would make Display inherent to Foo, but that seems to suggest that Display must be implemented for all Foo<T>, and you may not want that (i.e., maybe you only want impl<T: Display> Display for Foo<T>.

I was just now wondering about some kind of item in the inherent impl, like:

impl Foo {
    use trait Bar; // ?
}

but that doesn't quite feel right. For one thing, use has another meaning here.

Have to ponder it.


One corner cases to consider (is this specified?)

  • What happens if there is an inherent method on the type and a trait method with the same name?

@nikomatsakis
Copy link
Contributor

Currently, it's non-breaking-ish change to add defaulted methods to traits. If I have an inherent method named fn foo and #[include(Baz)], then later on someone adds a defaulted method called foo to Baz, how is this conflict resolved?

It's worth pointing out that adding a defaulted method to a trait always has the ability to make code stop compiling. I know that @aturon and I have in the past considered that we might want to just stop doing that, at least in libstd, and instead off extension traits that must be explicitly imported.

@cramertj
Copy link
Member

cramertj commented Feb 8, 2018

@nikomatsakis

I was just now wondering about some kind of item in the inherent impl, like use trait Bar;...

I wonder if there's some potential interaction with delegation here. Perhaps something like:

struct Bar;

impl Bar {
    fn bar(&self) { ... }
}

struct Foo(Bar);

impl MyTrait for Foo {
    fn trait_method(&self) { ... }
}

impl Foo {
    delegate bar to self.0;
    delegate trait_method to trait MyTrait;
}

@theduke
Copy link

theduke commented Feb 11, 2018

I see the use case this is solving, and I've often found this annoying myself.
But my immediate reaction to this is: please don't!

The trait system is already complex enough as is, more so with specialization on the horizon.
Providing yet another , manual knob to twist that changes resolution, works in a somewhat opaque with an attribute (and is therefore somewhat bolted on to the regular language) and is orthogonal to trait inheritance is something I would advocate against. It's also a stumbling block for new users, another concept to learn.

What would be interesting is: could this be made automatic, without the need for a manual attribute?
As in: the compiler automatically checks all traits that are implemented for the type (including parent traits), and methods/constants defined by traits are resolved without the need for a manual use.
In case of conflicts, implicitly imported traits have always take precedence.

@newpavlov
Copy link
Contributor

newpavlov commented Feb 11, 2018

It's also a stumbling block for new users, another concept to learn.

I would say it's another way around, implementing inherent traits will allow users to use most of the types functionality without knowing much about trait hierarchies surrounding those types, by just copy-pasting examples. So we'll move some difficulty from initial stages of learning Rust at the cost of a slightly higher final point of the learning curve.

What would be interesting is: could this be made automatic, without the need for a manual attribute?

This approach will be a disaster in my opinion. Not only I think it's not automatically decidable (don't forget about possible generic trait implementations in third party crates), but adds a lot of implicit magic, instead of perfectly explicit attribution of types/impls. Requirement for explicit use of traits was added to Rust for a good reason.

@porky11
Copy link

porky11 commented Feb 15, 2018

I also thought about something like this in the last time.
I would let it implemented implicitely, without the need of a macro, but I forgot about the posibility, that multiple Traits can contain the same function names.
Couldn't this be implemented manually with proc macros, when they are stable?

@tapeinosyne
Copy link

tapeinosyne commented Feb 18, 2018

@aturon
I'd love to move quickly on this; I think it could be really useful for the epoch release, in the sense that I think we'd want to use this in std (and other foundational libraries) to focus the docs on the "main" APIs for given types.

I have no strong opinions on inherent traits, but I'd be somewhat suspicious of pursuing changes to the use system for the benefit of documentation tools. Although inherent traits could be of some immediate help, I feel that Rustdoc not being able to bring the right APIs into focus speaks to an underlying issue – lack of editorial control – that should be addressed through Rustdoc first.

(To be clear, mine is not an argument against the RFC or Rustdoc; I just worry that by conveniently sidestepping a toolchain issue with a language change we may end up neglecting an opportunity to improve the tool at hand.)

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Mar 1, 2018

We discussed this in the @rust-lang/lang meeting. @cramertj will be writing up more, but I wanted to leave one oddball observation I made. Thanks to specialization, it is presently legal to write an "empty impl" that duplicates a blanket impl. So I could write something like:

impl<T> Foo for T { fn bar() { } }
impl Foo for u32 { }

This is true even though bar is not declared default, precisely because this new impl doesn't actually try to override anything, it just inherits all its items from another impl. If we adopted the #[inherent_impl] attribute, this could be a way for inherent impls to "inherit" from other blanket impls.

However, this is probably kinda' confusing, and that particular ability of specialization (to write an empty impl like that) is also a bit surprising, so perhaps not the best idea. But worth recording for posterity. =)

@withoutboats
Copy link
Contributor

However, this is probably kinda' confusing, and that particular ability of specialization (to write an empty impl like that) is also a bit surprising, so perhaps not the best idea. But worth recording for posterity. =)

I actually think its nice. I don't think its unclear what this code does:

#[inherent] impl ToString for MyType { }

The mechanisms that allow it to work are quite surprising, but that doesn't make it hard to learn how to use it. It can make a language feel deeper and more compelling when code you understand how to use has a surprising way that it is implemented, without negatively impacting the learning curve.

@cramertj
Copy link
Member

cramertj commented Mar 1, 2018

@rfcbot fcp close

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.

@rfcbot
Copy link
Collaborator

rfcbot commented Mar 1, 2018

Team member @cramertj has proposed to close this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Mar 1, 2018
@nikomatsakis
Copy link
Contributor

@withoutboats

I actually think its nice.

True confessions: I kind of like it too.

Though I still want delegation to be easy.

@newpavlov
Copy link
Contributor

newpavlov commented Mar 1, 2018

I also like #[inherent] impl Trait for MyType { } approach. Although "empty impl" will be quite surprising when first encountered, but I think it's a neat application of specialization rules and a good solution for inherent extension traits.

@aturon
Copy link
Member

aturon commented Mar 1, 2018

I'm signing off to close this due to:

  • The interaction with delegation
  • The fact that, for std, we can "just" do this by hand where we feel it'd be most beneficial for the epoch release, and convert to this feature later.

@scottmcm
Copy link
Member

scottmcm commented Mar 2, 2018

(I have no problem with close, but today's discussion crystallized something for me, so I wanted to write it down. Here goes...)

I think there's two parts to this that are both interesting:

  1. From an organization and code sharing perspective, it'd be nice to have an easy way to put the real implementations of things in places that might not be exactly how they're exposed publicly, but to not need to repeat a bunch of stuff in the exporting. (Some form of delegation seems great for this.)

  2. Some traits are such a fundamental part of the type that perhaps they ought to be called out as part of the type's definition and treated specially (exact definition TBD). And the traits are still meaningful and important, so it feels odd copying the methods as inherent. A notable difference with this one is that it can't necessarily do everything the delegation one can, but that's ok.
    As an example of this, take BufReader. While it's not hard to use std::io::BufRead along with it, it just feels kinda silly that BufReader's entire purpose in life is to offer BufRead and yet somehow there's still something more you need to do to be able to call its methods. (There are probably some traits where even though this could be done, it probably never would, like Hash.)

@rfcbot rfcbot added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Mar 3, 2018
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 3, 2018

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Mar 3, 2018
@SoniEx2
Copy link

SoniEx2 commented Mar 4, 2018

here's an alternative: macros.

pseudocode:

macro_rules! inherent ( (ty, trait, tt) => (impl ty { tt } impl trait for ty { tt }) );

use it like:

inherent!(PointType, PointTrait, {
  fn x(&self) -> i32 {
    self.x
  }
  fn y(&self) -> i32 {
    self.y
  }
});

@rfcbot
Copy link
Collaborator

rfcbot commented Mar 13, 2018

The final comment period is now complete.

@Centril
Copy link
Contributor

Centril commented Mar 13, 2018

Closing as the FCP is now complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.