-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Extension traits #2812
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
base: main
Are you sure you want to change the base?
Extension traits #2812
Conversation
767976e
to
8a1673c
Compare
9ad9fa7
to
9b50d50
Compare
9b50d50
to
408962c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking good!
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good! I only have a couple of minor comments, but on the whole I like it :)
src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
Show resolved
Hide resolved
…nding-foreign-types.md Co-authored-by: Dmitri Gribenko <gribozavr@gmail.com>
…od-resolution-conflicts.md Co-authored-by: Dmitri Gribenko <gribozavr@gmail.com>
…od-resolution-conflicts.md Co-authored-by: Dmitri Gribenko <gribozavr@gmail.com>
…od-resolution-conflicts.md Co-authored-by: Dmitri Gribenko <gribozavr@gmail.com>
- The extended trait may, in a newer version, add a new trait method with the | ||
same name of our extension method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- The extended trait may, in a newer version, add a new trait method with the | |
same name of our extension method. | |
- Another extension trait may, in a newer version, add a new trait method with the | |
same name as our extension method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both cases are actually possible, so I reworded the paragraph to account for both in 17ba065
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might want to change the examples in this section to not be on &str
, since implementing a trait on a reference leads to confusing behavior around method resolution (which I talk about in more detail in one of the comments here). I think things would be less confusing if we used a non-reference type like i32
or struct Foo
.
Now `StrExt::trim_ascii` is invoked, rather than the inherent method, since | ||
`&mut self` has a higher priority than `&self`, the one used by the inherent | ||
method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is an accurate explanation of what's happening here. The reference explicitly states (in the info box in that section) that &self
methods have higher priority than &mut self
methods.
I think the reason why the &mut self
version gets higher priority here is that the receiver expression is &mut &str
. If I'm understanding the reference's explanation of method resolution correctly, this means that when it builds the list of candidate receiver types, &mut &str
is the first candidate type in the list. It's then choosing between the inherent &str
method and the &mut &str
method coming from the trait, and the latter wins because it's the actual type of the expression &mut " dad "
.
I think the confusion here is because we're implementing the trait on on &str
, which is already a reference type. If I change the trait to be implemented on str
directly (i.e. impl StrExt for str
), when when I change the method to take &mut self
the inherent method still gets called (exmple in the playground). Part of the reason for this is because when we do (&mut " dad ")
we're not getting a &mut str
, we're getting a &mut &str
.
I think things would be a lot less ambiguous if we were demonstrating this on a regular, non-reference type such as i32
or struct Foo
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking at it closely!
Re-reading through it, and cross-referencing with the RFC, I agree with your interpretation as to why things play out as they do in terms of precedence. I'll rework the example to something simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, I'd be happy if we explained a basic rule that people can remember, and simply mention that while the Rust language has rules to disambiguate in other cases, the rules are quite complex to remember and apply, and we'd rather not write code that depends on them.
Point the students to the Rust reference for more information on | ||
[method resolution][2]. An explanation with more extensive examples can be | ||
found in [an open PR to the Rust reference][3]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point the students to the Rust reference for more information on | |
[method resolution][2]. An explanation with more extensive examples can be | |
found in [an open PR to the Rust reference][3]. | |
Point the students to the Rust reference for more information on | |
[method resolution][2]. |
I think we can just link to the reference, I don't think linking to an open PR is necessary. Eventually the things in that PR will (hopefully) land, so just linking to the reference is enough imo.
src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
Show resolved
Hide resolved
- The compiler rejects the code because it cannot determine which method to | ||
invoke. Neither `Ext1` nor `Ext2` has a higher priority than the other. | ||
|
||
To resolve this conflict, you must specify which trait you want to use. For | ||
example, you can call `Ext1::is_palindrome("dad")` or | ||
`Ext2::is_palindrome("dad")`. | ||
|
||
For methods with more complex signatures, you may need to use a more explicit | ||
[fully-qualified syntax][1]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably worth showing the full syntax to students, since sometimes Trait::method(foo)
isn't enough. Specifically, if the compiler can't infer the type of foo
then it won't be able to resolve which type's trait implementation to use. In those cases you'd have to write <Type as Trait>::method(foo)
. That syntax can be surprising for people new to the language (I know I was confused the first time I saw it), so I think showing it here (and explaining when it's necessary) would be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, that would be a good thing for the instructor to demonstrate. Please add a bullet point with the prompt for the instructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me. The note below is stylistic and relates only to the speaker notes, so feel free to treat it as a mild preference at most.
the same type may define a method with a name that conflicts with your own | ||
extension method. | ||
|
||
Survey the class: what do the students think will happen in the example above? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the approach of surveying the class is something specific to the instructor and situation. I would do this without prompting for small-group, in-person instruction, but for a large or shy group, or when using Zoom or Meet, it's not a very effective strategy (it results in lots of empty air).
So, it's a very minor point and not worth changing, but I think the question in the comment is sufficient here, and does not need repeating in the notes. Instructors will prompt from the comment if appropriate, or address it in a manner appropriate to the context and their teaching style.
- Start by explaining the terminology. | ||
|
||
A Rust item (be it a trait or a type) is referred to as: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Start by explaining the terminology. | |
A Rust item (be it a trait or a type) is referred to as: | |
- A Rust item (be it a trait or a type) is referred to as: |
Let's keep the speaker notes concise.
introduce explicit disambiguation. | ||
|
||
Rust has decided to avoid the issue altogether by forbidding the definition of | ||
new inherent methods on foreign types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new inherent methods on foreign types. | |
new inherent methods on foreign types. | |
- Other languages (e.g, Kotlin, C#, Swift) allow adding methods to existing types, often called "extension methods." This leads to different trade-offs in terms of potential ambiguities and the need for global reasoning. |
- There are entire libraries aimed at extending foundational traits with new | ||
functionality. | ||
|
||
[`itertools`] provides a wide range of iterator adapters and utilities via the | ||
[`Itertools`] trait. [`futures`] provides [`FutureExt`] to extend the | ||
[`Future`] trait. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- There are entire libraries aimed at extending foundational traits with new | |
functionality. | |
[`itertools`] provides a wide range of iterator adapters and utilities via the | |
[`Itertools`] trait. [`futures`] provides [`FutureExt`] to extend the | |
[`Future`] trait. | |
- There are entire crates that extend standard library traits with new functionality. | |
- `itertools` crate provides the `Itertools` trait that extends `Iterator`. It adds many iterator adapters, such as `interleave` and `unique`. It provides new algorithmic building blocks for iterator pipelines built with method chaining. | |
- `futures` crate provides the `FutureExt` trait, which extends the `Future` trait with new combinators and helper methods. |
More structure with a nested list + a bit more content.
<details> | ||
|
||
- The trait you are extending may, in a newer version, add a new trait method | ||
with the same name of your extension method. Or another extension trait for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with the same name of your extension method. Or another extension trait for | |
with the same name as your extension method. Or another extension trait for |
|
||
To resolve this conflict, you must specify which trait you want to use. For | ||
example, you can call `Ext1::is_palindrome("dad")` or | ||
`Ext2::is_palindrome("dad")`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`Ext2::is_palindrome("dad")`. | |
`Ext2::is_palindrome("dad")`. Demonstrate this syntax and that the updated code compiles. |
- The compiler rejects the code because it cannot determine which method to | ||
invoke. Neither `Ext1` nor `Ext2` has a higher priority than the other. | ||
|
||
To resolve this conflict, you must specify which trait you want to use. For | ||
example, you can call `Ext1::is_palindrome("dad")` or | ||
`Ext2::is_palindrome("dad")`. | ||
|
||
For methods with more complex signatures, you may need to use a more explicit | ||
[fully-qualified syntax][1]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, that would be a good thing for the instructor to demonstrate. Please add a bullet point with the prompt for the instructor.
- [Extension Traits](idiomatic/leveraging-the-type-system/extension-traits.md) | ||
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md) | ||
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md) | ||
- [Should I Define An Extension Trait?](idiomatic/leveraging-the-type-system/extension-traits/should-i-define-an-extension-trait.md) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could improve the flow if we moved the "should I" section after the "extending other traits" section.
The "should I" section discusses ideas that apply to all usages of this pattern. (It would also flow even better if you apply my suggestions about API coherence when adding multiple methods, because the "extending other traits" section would introduce that idea first)
@LukeMathWalker I left another round of comments, but please don't be discouraged - this section looks very good and the comments are mostly minor. Please work through resolving the comments, rebase the PR to resolve conflicts with main, and merge. You have my LGTM. |
No description provided.