- Day 1: From nvm to rustup
- Day 2: From npm to cargo
- Day 3: Setting up VS Code
- Day 4: Hello World (and your first two WTFs)
- Day 5: Borrowing & Ownership
- Day 6: Strings, part 1
- Day 7: Syntax and Language, part 1
- Day 8: Language Part 2: From objects and classes to HashMaps and structs
- Day 9: Language Part 3: Class Methods for Rust Structs (+ enums!)
- → Day 10: From Mixins to Traits
- Day 11: The Module System
- Day 12: Strings, Part 2
- Day 13: Results & Options
- Day 14: Managing Errors
- Day 15: Closures
- Day 16: Lifetimes, references, and
- Day 17: Arrays, Loops, and Iterators
- Day 18: Async
- Day 19: Starting a large project
- Day 20: CLI Arguments & Logging
- Day 21: Building and Running WebAssembly
- Day 22: Using JSON
- Day 23: Cheating The Borrow Checker
- Day 24: Crates & Tools
The code in this series can be found at vinodotdev/node-to-rust
This guide is not a comprehensive Rust tutorial. This guide tries to balance technical accuracy with readability and errs on the side of “gets the point across” vs being 100% correct. If you have a correction or think something needs deeper clarification, send a note on Twitter at @jsoverson, @vinodotdev, or join our Discord channel.
The above declares a class named
mixin() function uses
Object.assign() to add all the properties from
Person’s prototype, thus making them available to every instance of
Person. It’s a useful pattern. It’s an option that sidesteps long prototype chains or general-purpose classes.
Traits are just a collection of methods.
Stretching our TrafficLight example
In the previous days, we added a
get_state() method to our
TrafficLight struct. We’re rapidly becoming experts in lighting management. It’s a perferct time to start adding functionality for every light we have. The first light to add is a simple household bulb. It doesn’t do much. It’s either on or off.
There should be no surprises in the implementation.
Now let’s make a generic
print_state() function. We want a function that prints the state of any light we pass to it.
But what do we take in? We can’t take in an arbitrary list of types like we can in TypeScript. We can’t write
Yesterday I talked about enums as one way Rust deals with the lack of union types. Another way is with traits. The difference is what we’re looking for, the type or a subset of the behavior.
In this example, we don’t actually care what kind of light we take in. We only want to query for its name and state. We only want a subset of behavior.
That’s where traits come in.
Trait definitions start with the
trait keyword and are structured similarly to
impls. They consist of methods that look almost identical to what we’d write in an actual
impl. The one major difference is that you can write trait methods that are missing a body.
Trait methods can include a method body which acts as a default implementation. Implementers can choose to override the default implementation.
Let’s call this trait
Light and started filling it out with a
To implement a trait we use
impl block like we did for our
struct. This time though, we write
impl [trait] for [struct] and we’re limited to the methods available on the trait.
Now we can start to implement a
print_state() function. To accept an argument that implements a trait you write
When we try to migrate our
get_state() methods over to the trait, we run into a snag. Each of the light’s state has different types. Since we are printing them with the debug formatter right now, your first thought might be to translate what we just did like this:
But that won’t work. Rust complains with the error
impl `Trait` not allowed outside of function and method return types.
error[E0562]: `impl Trait` not allowed outside of function and method return types --> crates/day-10/traits/src/main.rs:17:27 | 17 | fn get_state(&self) -> impl std::fmt::Debug; | ^^^^^^^^^^^^^^^^^^^^ For more information about this error, try `rustc --explain E0562`.
But… we are trying to use it as a method return type… What gives?
To use traits here we need to use
dyn [trait]. Using
dyn [trait] vs
impl [trait] is a matter of whether or not Rust needs or is able to know a value’s concrete type at compile time. We can’t use
impl std::fmt::Debug here because every implementation might return a different actual type. Using
dyn is like crossing a barrier where you trade optimizations for flexibility. Once a value crosses the
dyn barrier, it loses its type information and is essentially just a blob of data and a pointer to the methods on a trait.
So we change our signature and implementations to:
Rust must know the size of everything at compile time. It can’t do that with
dyn [trait]values because they don’t have a concrete type. With no known size, it’s “unsized.” What is sized is a reference. A reference to a
dyn [trait], i.e.
&dyn [trait]is OK.
Our full code now looks like this:
[snipped] Traffic light's state is : Red House light's state is : false
The output isn’t stellar but we can work on that another time. Our code is getting pretty big to sit in one file now. It’s time to start cutting it up.
Traits are everywhere in Rust and it’s worth reading Rust code on Github or in the standard library. Some languages (Go, notably) are very straightforward and clear. There is generally one “right” way to do something. Rust is anything but that. There are 800 different ways to do everything and its important to read existing code rather than work in a vacuum.
Having 800 ways to do any one thing makes Rust the spiritual successor to perl. Don’t say that out loud though. You won’t make any friends.
Tomorrow we’ll go over Rust’s module system. It’s straightforward once you “get it,” but you’re coming from node.js. Node has the simplest module system that I’ve ever used. Once you get over the first few Rust module WTFs, it won’t stand in your way again.