First pass at polymorphism improvements - #3264
Conversation
- Break things up into sections on generics, enums, and dyn. - Various non-OOP-specific slides moved into the new sections. - Add new intro slides contextualizing polymorphism and how it comes up in a static language like Rust, and introducing our 3 flavors of polymorphism. - Add slide directly comparing enums and dyn. - Add slides on enums and how they're used for polymorphism.
fw-immunant
left a comment
There was a problem hiding this comment.
Various comments inline.
|
|
||
| fn main() { | ||
| print_value(123); | ||
| // print_value("hello"); // 🛠️❌ Mismatched types! |
There was a problem hiding this comment.
The emoji here are kind of distracting IMO. Relatedly, I think we should standardize how we denote that lines in examples would cause errors if uncommented, because we do so in quite a few places. Only one of them (src/lifetimes/returning_borrows.md) uses this emoji style. Other places say "// ERROR: <explanation>", "// Error, <explanation>", or don't say that the line will generate an error explicitly at all (e.g. "// takes_u32(y);"). Within the idiomatic course there are a few variants on the emoji combination, but most places use "❌🔨" rather than "🛠️❌". Personally, I think a simple red X is enough to suggest "error"--the hammer or hammer+wrench seem to suggest something being fixed as well, which is the opposite of what these lines do.
| But Rust's type system is extremely static, meaning that by default a function's | ||
| arguments are limited to exactly the type declared in the function signature: |
There was a problem hiding this comment.
"extremely" static isn't really a thing--whether a language does typechecking before translating/running code is a binary distinction.
| But Rust's type system is extremely static, meaning that by default a function's | |
| arguments are limited to exactly the type declared in the function signature: | |
| But Rust's static type system requires us to specify upfront the types of our functions' arguments: |
We could then say "We can make this function more flexible, like the Python version, by using generics."
| - If you are coming from a dynamic language like Python, the concept of | ||
| polymorphism may be new to you because in dynamic languages everything is | ||
| inherently polymorphic. But Rust is a very statically-typed language, meaning | ||
| the compiler heavily restricts what types can be used where. |
There was a problem hiding this comment.
| - If you are coming from a dynamic language like Python, the concept of | |
| polymorphism may be new to you because in dynamic languages everything is | |
| inherently polymorphic. But Rust is a very statically-typed language, meaning | |
| the compiler heavily restricts what types can be used where. | |
| - If you are coming from a dynamically-typed language like Python, the concept | |
| of polymorphism may be new to you because in these languages types are just | |
| one part of the contract between functions and their calling context, which | |
| is left to negotiation by programmers rather than enforced by the language. | |
| But in Rust, a statically-typed language, the compiler must always be able to | |
| determine precise types for all variables, either through inference (within a | |
| function) or from explicit annotations. |
| @@ -0,0 +1,31 @@ | |||
| # Kinds of Polymorphism | |||
|
|
|||
| In Rust we have 3 different mechanism for doing polymorphism: | |||
There was a problem hiding this comment.
| In Rust we have 3 different mechanism for doing polymorphism: | |
| In Rust we have 3 different mechanisms for writing polymorphic code: |
| - Dynamic Dispatch is a tool in Object Oriented Programming that is often used | ||
| in places where one needs to care more about the behavior of a type than what | ||
| the type is. | ||
|
|
||
| In OOP languages, dynamic dispatch is often an _implicit_ process and not | ||
| something you can opt out of. |
There was a problem hiding this comment.
What's the reason for dropping this prose? In my mind this explanation is useful we assume if our audience is coming from a particularly OO-heavy background, e.g. C++ or Java, where subclassing is pervasive. But on the other hand, that difference is also covered in Fundamentals when we talk about dynamic dispatch and memory layouts of trait objects.
| # Re-exposing Traits | ||
|
|
||
| Sometimes we use an enum to abstract over multiple types that implement the same | ||
| trait, and want the enum to also re-expose the trait's interface. |
There was a problem hiding this comment.
From the perspective of "idiomatic Rust", maybe we should mention crates used to automate this pattern, e.g. enum_dispatch?
After teaching the Idiomatic course a couple of time, I have some ideas for how to iterate on and improve the polymorphism section. The main things I want to address are:
dyn), since those serve different purposes: Generics are primarily a tool for code reuse and building abstractions over types, enums anddynprovide tools for dynamically handling different types at runtime in a uniform way.dynto help students reason about which one is the best solution for any given problem.dyn. I think we can then flesh out the OOP section further by having it talk about how inheritance is used to solve certain problems and then talk about how we solve those same problems in Rust.The things that this draft PR does currently are:
dyn.I intend to teach from these slides in my next couple of classes and iterate on them more based on how that goes, but let me know if this direction makes sense or if you have ideas for further improvements or additions~! 🦀