From cd12204c25eb458319d15d24f0886d62f3f196e7 Mon Sep 17 00:00:00 2001 From: newpavlov Date: Tue, 27 Mar 2018 16:55:30 +0300 Subject: [PATCH 1/3] inherent trait impl RFC --- text/0000-inherent-trait-impl.md | 152 +++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 text/0000-inherent-trait-impl.md diff --git a/text/0000-inherent-trait-impl.md b/text/0000-inherent-trait-impl.md new file mode 100644 index 00000000000..1255eee7077 --- /dev/null +++ b/text/0000-inherent-trait-impl.md @@ -0,0 +1,152 @@ +- Feature Name: inherent-trait-impl +- Start Date: 2018-03-27 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +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. + +# Motivation +[motivation]: #motivation + +There are two similar cases where this is valuable: + +- Frequently used traits. + + Sometimes getting the right abstractions require breaking up a type's implementation into many traits, with only a few methods per + trait. Every use of such a type results in a large number of imports to ensure the correct traits are in scope. If such a type is used + frequently, then this burden quickly becomes a pain point for users of the API, + especially if users do not care about writing generic code over traits. + +- Mapping object-oriented APIs. + + When mapping these APIs to rust, base classes are usually mapped to traits: methods on those base classes will need to be callable on any + derived type. This is sub-optimal because to use a class method a user must now know which class in the hierachy defined that + method, so that they can import and use the corresponding trait. This knowledge is not required when using the same API from an + object-oriented language. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The feature is implemented using a new attribute which can be applied to trait +`impl` blocks: + +```rust +pub trait Bar { + fn bar(&self); +} + +pub trait ExtBar: Bar { + fn ext_bar(&self); +} + +impl ExtBar for T { + fn ext_bar(&self) { } +} + +pub struct Foo; + +#[inherent] +impl Bar for Foo { + fn bar(&self) { println!("foo::bar"); } +} + +// works thanks to specialization +#[inherent] +impl ExtBar for Foo { } + +impl Foo { + fn foo(&self) { println!("foo::foo"); } +} +``` + +The methods `bar` and `ext_bar` are now callable on any instance of `Foo`, +regardless of whether the `Bar` and `ExtBar` traits are currently in scope, +or even whether the `Bar` trait is publically visible. In other words if `Bar` +and `ExtBar` defined in one crate and `Foo` and another, user of `Foo` will be +able to explicitly depend only on the crate which defines `Foo` and still use +the inherent traits methods. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The `inherent` attribute in the above example makes the `impl` block equivalent to: + +```rust +impl Foo { + #[inline] + pub fn bar(&self) { ::bar(self); } + + fn foo(&self) { println!("foo::foo"); } +} +``` + +Any questions regarding coherence, visibility or syntax can be resolved by +comparing against this expansion, although the feature need not be implemented +as an expansion within the compiler. + +# Drawbacks +[drawbacks]: #drawbacks + +- Increased complexity of the language. +- Hides use of traits from users. + +# Rationale and alternatives +[alternatives]: #alternatives + +- Define inherent traits either on a type `T` or on an `impl T { .. }` block. +- Implement as part of the delegation RFC. +- Do nothing: users may choose to workaround the issue by manually performing the expansion if required. + +The most viable alternative is delegation proposal, although arguably inherent +traits and delegation solve different problems with the similar end result. +The former allows to use trait methods without importing traits and the latter +to delegate methods to the selected field. Nethertheless delegation RFC and +this RFC can be composable with each other: + +```Rust +struct Foo1; +struct Foo2; + +#[inherent] +impl T1 for Foo1 { + fn a() {} +} + +#[inherent] +impl T2 for Foo2 { + fn b() {} +} + +struct Bar { + f1: Foo1, + f2: Foo2, +} + +impl Bar { + // all methods from `T1` will be delegated as well + // though `T1` will not be implemented for `Bar` + delegate * to f1; +} + +// method `b` will be accessable on `Bar` without importing `T2` +#[inherent] +impl T2 for Bar { + delegate * to f2; +} +``` + +# Prior art +[prior-art]: #prior-art + +- https://github.com/rust-lang/rfcs/pull/2309 (previous closed PR) +- https://github.com/rust-lang/rfcs/issues/1880 +- https://github.com/rust-lang/rfcs/issues/1971 + +# Unresolved questions +[unresolved]: #unresolved-questions + +- Do we want to introduce a new keword instead of using `#[inherent]`? In other +words do we want to write `inherent impl A for B { .. }`? From d4a066997a42db5233d7cd7d25c391f8dc67bae4 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 13 Sep 2023 11:24:30 -0700 Subject: [PATCH 2/3] Remove reference to specialization Specialization isn't in stable Rust, and referencing specialization makes the explanation more complex. --- text/0000-inherent-trait-impl.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/text/0000-inherent-trait-impl.md b/text/0000-inherent-trait-impl.md index 1255eee7077..bc76e426e8a 100644 --- a/text/0000-inherent-trait-impl.md +++ b/text/0000-inherent-trait-impl.md @@ -38,14 +38,6 @@ pub trait Bar { fn bar(&self); } -pub trait ExtBar: Bar { - fn ext_bar(&self); -} - -impl ExtBar for T { - fn ext_bar(&self) { } -} - pub struct Foo; #[inherent] @@ -53,21 +45,17 @@ impl Bar for Foo { fn bar(&self) { println!("foo::bar"); } } -// works thanks to specialization -#[inherent] -impl ExtBar for Foo { } - impl Foo { fn foo(&self) { println!("foo::foo"); } } ``` -The methods `bar` and `ext_bar` are now callable on any instance of `Foo`, -regardless of whether the `Bar` and `ExtBar` traits are currently in scope, +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. In other words if `Bar` -and `ExtBar` defined in one crate and `Foo` and another, user of `Foo` will be +is defined in one crate and `Foo` in another, the user of `Foo` will be able to explicitly depend only on the crate which defines `Foo` and still use -the inherent traits methods. +the inherent trait's methods. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 6ad895c007c7685f8d7f82d58c0d69204e9166e2 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 13 Sep 2023 11:28:21 -0700 Subject: [PATCH 3/3] Clarify desugaring of `#[inherent]` --- text/0000-inherent-trait-impl.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/text/0000-inherent-trait-impl.md b/text/0000-inherent-trait-impl.md index bc76e426e8a..9d652729dc5 100644 --- a/text/0000-inherent-trait-impl.md +++ b/text/0000-inherent-trait-impl.md @@ -60,14 +60,12 @@ the inherent trait's methods. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -The `inherent` attribute in the above example makes the `impl` block equivalent to: +The `#[inherent]` attribute in the above example desugars equivalently to an inherent forwarding method (in addition to the trait impl): ```rust impl Foo { #[inline] pub fn bar(&self) { ::bar(self); } - - fn foo(&self) { println!("foo::foo"); } } ```