-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
|
||
# Extension Traits | ||
|
||
In Rust, you can't define new inherent methods for 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.
Is "foreign type" a standard term to refer to types from other crates? I feel like it can be easily misinterpreted as "types defined in C++" (similar to "foreign functions"). Is there an alternative?
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.
"Foreign type" and "foreign trait" are the terms used in the Rust reference when discussing orphan rules (see this section). So I'd say they are standard terms in this specific context.
Alternatively, we could use "new inherent methods for a type defined in another crate" as an alternative phrasing. It could get fairly verbose in the speaker notes.
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.
ack. Maybe then briefly mention in the speaker notes what these terms mean, hinting that the instructor should maybe explain them. I don't think it is a given that the audience understands coherence rules and understands why it matters where the trait is defined.
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.
Added in 17ba065
src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
Outdated
Show resolved
Hide resolved
|
||
<details> | ||
|
||
- Compile the example to show the compiler error that's emitted. |
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 should start by explaining what we want to achieve first: we want the user to be able to write something like mystr.is_palindrome()
. Then transition to the obvious solution that does not work (the code snippet above). And then say that this is why we are using a more complex solution that does work.
Otherwise it might be confusing to some audience members: we are starting a new chapter by looking at a piece of code that does not compile (the impl block above), we want it to compile (why?..), but we actually wouldn't, instead we should do something else.
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 restructured the flow a bit in 17ba065. What do you think?
Highlight how the compiler error message nudges you towards the extension | ||
trait pattern. | ||
|
||
- Explain how many type-system restrictions in Rust aim to prevent _ambiguity_. |
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.
You can raise the level of abstraction even higher.
One effective way to approach evaluating features in programming design is to ask "what if everybody did this?"
"I want to be able to add methods to someone else's type! I want to add an is_palindrome method to a string" - "Yes, but what if two people did this?"
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.
Done in 17ba065
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 <[email protected]>
…od-resolution-conflicts.md Co-authored-by: Dmitri Gribenko <[email protected]>
…od-resolution-conflicts.md Co-authored-by: Dmitri Gribenko <[email protected]>
…od-resolution-conflicts.md Co-authored-by: Dmitri Gribenko <[email protected]>
- 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.
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.
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.
No description provided.