@@ -1,9 +1,9 @@
|
||||
# Traits
|
||||
|
||||
In the previous chapter we covered the basics of Rust's type and ownership system.
|
||||
In the previous chapter we covered the basics of Rust's type and ownership system.\
|
||||
It's time to dig deeper: we'll explore **traits**, Rust's take on interfaces.
|
||||
|
||||
Once you learn about traits, you'll start seeing their fingerprints all over the place.
|
||||
Once you learn about traits, you'll start seeing their fingerprints all over the place.\
|
||||
In fact, you've already seen traits in action throughout the previous chapter, e.g. `.into()` invocations as well
|
||||
as operators like `==` and `+`.
|
||||
|
||||
@@ -16,9 +16,9 @@ On top of traits as a concept, we'll also cover some of the key traits that are
|
||||
- `Sized`, to mark types with a known size
|
||||
- `Drop`, for custom cleanup logic
|
||||
|
||||
Since we'll be talking about conversions, we'll seize the opportunity to plug some of the "knowledge gaps"
|
||||
Since we'll be talking about conversions, we'll seize the opportunity to plug some of the "knowledge gaps"
|
||||
from the previous chapter—e.g. what is `"A title"`, exactly? Time to learn more about slices too!
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/04_traits/00_intro`
|
||||
- The exercise for this section is located in `exercises/04_traits/00_intro`
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
All our tests, so far, have been making assertions using `Ticket`'s fields.
|
||||
All our tests, so far, have been making assertions using `Ticket`'s fields.
|
||||
|
||||
```rust
|
||||
assert_eq!(ticket.title(), "A new title");
|
||||
@@ -38,15 +38,15 @@ error[E0369]: binary operation `==` cannot be applied to type `Ticket`
|
||||
note: an implementation of `PartialEq` might be missing for `Ticket`
|
||||
```
|
||||
|
||||
`Ticket` is a new type. Out of the box, there is **no behavior attached to it**.
|
||||
`Ticket` is a new type. Out of the box, there is **no behavior attached to it**.\
|
||||
Rust doesn't magically infer how to compare two `Ticket` instances just because they contain `String`s.
|
||||
|
||||
The Rust compiler is nudging us in the right direction though: it's suggesting that we might be missing an implementation
|
||||
The Rust compiler is nudging us in the right direction though: it's suggesting that we might be missing an implementation
|
||||
of `PartialEq`. `PartialEq` is a **trait**!
|
||||
|
||||
## What are traits?
|
||||
|
||||
Traits are Rust's way of defining **interfaces**.
|
||||
Traits are Rust's way of defining **interfaces**.\
|
||||
A trait defines a set of methods that a type must implement to satisfy the trait's contract.
|
||||
|
||||
### Defining a trait
|
||||
@@ -69,7 +69,7 @@ trait MaybeZero {
|
||||
|
||||
### Implementing a trait
|
||||
|
||||
To implement a trait for a type we use the `impl` keyword, just like we do for regular[^inherent] methods,
|
||||
To implement a trait for a type we use the `impl` keyword, just like we do for regular[^inherent] methods,
|
||||
but the syntax is a bit different:
|
||||
|
||||
```rust
|
||||
@@ -117,15 +117,15 @@ use crate::MaybeZero;
|
||||
This is not necessary if:
|
||||
|
||||
- The trait is defined in the same module where the invocation occurs.
|
||||
- The trait is defined in the standard library's **prelude**.
|
||||
The prelude is a set of traits and types that are automatically imported into every Rust program.
|
||||
- The trait is defined in the standard library's **prelude**.
|
||||
The prelude is a set of traits and types that are automatically imported into every Rust program.
|
||||
It's as if `use std::prelude::*;` was added at the beginning of every Rust module.
|
||||
|
||||
You can find the list of traits and types in the prelude in the
|
||||
You can find the list of traits and types in the prelude in the
|
||||
[Rust documentation](https://doc.rust-lang.org/std/prelude/index.html).
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/04_traits/01_trait`
|
||||
|
||||
[^inherent]: A method defined directly on a type, without using a trait, is also known as an **inherent method**.
|
||||
[^inherent]: A method defined directly on a type, without using a trait, is also known as an **inherent method**.
|
||||
|
||||
@@ -44,7 +44,7 @@ fn main() {
|
||||
|
||||
## One implementation
|
||||
|
||||
There are limitations to the trait implementations you can write.
|
||||
There are limitations to the trait implementations you can write.\
|
||||
The simplest and most straight-forward one: you can't implement the same trait twice,
|
||||
in a crate, for the same type.
|
||||
|
||||
@@ -101,7 +101,7 @@ Imagine the following situation:
|
||||
- Crate `C` provides a (different) implementation of the `IsEven` trait for `u32`
|
||||
- Crate `D` depends on both `B` and `C` and calls `1.is_even()`
|
||||
|
||||
Which implementation should be used? The one defined in `B`? Or the one defined in `C`?
|
||||
Which implementation should be used? The one defined in `B`? Or the one defined in `C`?\
|
||||
There's no good answer, therefore the orphan rule was defined to prevent this scenario.
|
||||
Thanks to the orphan rule, neither crate `B` nor crate `C` would compile.
|
||||
|
||||
@@ -111,6 +111,6 @@ Thanks to the orphan rule, neither crate `B` nor crate `C` would compile.
|
||||
|
||||
## Further reading
|
||||
|
||||
- There are some caveats and exceptions to the orphan rule as stated above.
|
||||
Check out [the reference](https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence)
|
||||
if you want to get familiar with its nuances.
|
||||
- There are some caveats and exceptions to the orphan rule as stated above.
|
||||
Check out [the reference](https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence)
|
||||
if you want to get familiar with its nuances.
|
||||
|
||||
@@ -5,11 +5,11 @@ Operator overloading is the ability to define custom behavior for operators like
|
||||
|
||||
## Operators are traits
|
||||
|
||||
In Rust, operators are traits.
|
||||
In Rust, operators are traits.\
|
||||
For each operator, there is a corresponding trait that defines the behavior of that operator.
|
||||
By implementing that trait for your type, you **unlock** the usage of the corresponding operators.
|
||||
By implementing that trait for your type, you **unlock** the usage of the corresponding operators.
|
||||
|
||||
For example, the [`PartialEq` trait](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) defines the behavior of
|
||||
For example, the [`PartialEq` trait](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) defines the behavior of
|
||||
the `==` and `!=` operators:
|
||||
|
||||
```rust
|
||||
@@ -33,9 +33,9 @@ and replace `x == y` with `x.eq(y)`. It's syntactic sugar!
|
||||
This is the correspondence for the main operators:
|
||||
|
||||
| Operator | Trait |
|
||||
|--------------------------|-------------------------------------------------------------------------|
|
||||
| ------------------------ | ----------------------------------------------------------------------- |
|
||||
| `+` | [`Add`](https://doc.rust-lang.org/std/ops/trait.Add.html) |
|
||||
| `-` | [`Sub`](https://doc.rust-lang.org/std/ops/trait.Sub.html) |
|
||||
| `-` | [`Sub`](https://doc.rust-lang.org/std/ops/trait.Sub.html) |
|
||||
| `*` | [`Mul`](https://doc.rust-lang.org/std/ops/trait.Mul.html) |
|
||||
| `/` | [`Div`](https://doc.rust-lang.org/std/ops/trait.Div.html) |
|
||||
| `%` | [`Rem`](https://doc.rust-lang.org/std/ops/trait.Rem.html) |
|
||||
@@ -47,9 +47,9 @@ while comparison ones live in the [`std::cmp`](https://doc.rust-lang.org/std/cmp
|
||||
|
||||
## Default implementations
|
||||
|
||||
The comment on `PartialEq::ne` states that "`ne` is a provided method".
|
||||
It means that `PartialEq` provides a **default implementation** for `ne` in the trait definition—the `{ ... }` elided
|
||||
block in the definition snippet.
|
||||
The comment on `PartialEq::ne` states that "`ne` is a provided method".\
|
||||
It means that `PartialEq` provides a **default implementation** for `ne` in the trait definition—the `{ ... }` elided
|
||||
block in the definition snippet.\
|
||||
If we expand the elided block, it looks like this:
|
||||
|
||||
```rust
|
||||
@@ -62,7 +62,7 @@ pub trait PartialEq {
|
||||
}
|
||||
```
|
||||
|
||||
It's what you expect: `ne` is the negation of `eq`.
|
||||
It's what you expect: `ne` is the negation of `eq`.\
|
||||
Since a default implementation is provided, you can skip implementing `ne` when you implement `PartialEq` for your type.
|
||||
It's enough to implement `eq`:
|
||||
|
||||
@@ -80,7 +80,7 @@ impl PartialEq for WrappingU8 {
|
||||
}
|
||||
```
|
||||
|
||||
You are not forced to use the default implementation though.
|
||||
You are not forced to use the default implementation though.
|
||||
You can choose to override it when you implement the trait:
|
||||
|
||||
```rust
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Derive macros
|
||||
|
||||
Implementing `PartialEq` for `Ticket` was a bit tedious, wasn't it?
|
||||
You had to manually compare each field of the struct.
|
||||
You had to manually compare each field of the struct.
|
||||
|
||||
## Destructuring syntax
|
||||
|
||||
@@ -24,7 +24,7 @@ impl PartialEq for Ticket {
|
||||
```
|
||||
|
||||
If the definition of `Ticket` changes, the compiler will error out, complaining that your
|
||||
destructuring is no longer exhaustive.
|
||||
destructuring is no longer exhaustive.\
|
||||
You can also rename struct fields, to avoid variable shadowing:
|
||||
|
||||
```rust
|
||||
@@ -55,13 +55,13 @@ You've already encountered a few macros in past exercises:
|
||||
- `assert_eq!` and `assert!`, in the test cases
|
||||
- `println!`, to print to the console
|
||||
|
||||
Rust macros are **code generators**.
|
||||
Rust macros are **code generators**.\
|
||||
They generate new Rust code based on the input you provide, and that generated code is then compiled alongside
|
||||
the rest of your program. Some macros are built into Rust's standard library, but you can also
|
||||
write your own. We won't be creating our macro in this course, but you can find some useful
|
||||
pointers in the ["Further reading" section](#further-reading).
|
||||
|
||||
### Inspection
|
||||
### Inspection
|
||||
|
||||
Some IDEs let you expand a macro to inspect the generated code. If that's not possible, you can use
|
||||
[`cargo-expand`](https://github.com/dtolnay/cargo-expand).
|
||||
@@ -81,7 +81,7 @@ struct Ticket {
|
||||
|
||||
Derive macros are used to automate the implementation of common (and "obvious") traits for custom types.
|
||||
In the example above, the `PartialEq` trait is automatically implemented for `Ticket`.
|
||||
If you expand the macro, you'll see that the generated code is functionally equivalent to the one you wrote manually,
|
||||
If you expand the macro, you'll see that the generated code is functionally equivalent to the one you wrote manually,
|
||||
although a bit more cumbersome to read:
|
||||
|
||||
```rust
|
||||
@@ -104,4 +104,4 @@ The compiler will nudge you to derive traits when possible.
|
||||
## Further reading
|
||||
|
||||
- [The little book of Rust macros](https://veykril.github.io/tlborm/)
|
||||
- [Proc macro workshop](https://github.com/dtolnay/proc-macro-workshop)
|
||||
- [Proc macro workshop](https://github.com/dtolnay/proc-macro-workshop)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# Trait bounds
|
||||
# Trait bounds
|
||||
|
||||
We've seen two use cases for traits so far:
|
||||
|
||||
- Unlocking "built-in" behaviour (e.g. operator overloading)
|
||||
- Adding new behaviour to existing types (i.e. extension traits)
|
||||
|
||||
There's a third use case: **generic programming**.
|
||||
There's a third use case: **generic programming**.
|
||||
|
||||
## The problem
|
||||
|
||||
All our functions and methods, so far, have been working with **concrete types**.
|
||||
All our functions and methods, so far, have been working with **concrete types**.\
|
||||
Code that operates on concrete types is usually straightforward to write and understand. But it's also
|
||||
limited in its reusability.
|
||||
limited in its reusability.\
|
||||
Let's imagine, for example, that we want to write a function that returns `true` if an integer is even.
|
||||
Working with concrete types, we'd have to write a separate function for each integer type we want to
|
||||
Working with concrete types, we'd have to write a separate function for each integer type we want to
|
||||
support:
|
||||
|
||||
```rust
|
||||
@@ -54,7 +54,7 @@ The duplication remains.
|
||||
|
||||
## Generic programming
|
||||
|
||||
We can do better using **generics**.
|
||||
We can do better using **generics**.\
|
||||
Generics allow us to write code that works with a **type parameter** instead of a concrete type:
|
||||
|
||||
```rust
|
||||
@@ -68,19 +68,19 @@ where
|
||||
}
|
||||
```
|
||||
|
||||
`print_if_even` is a **generic function**.
|
||||
`print_if_even` is a **generic function**.\
|
||||
It isn't tied to a specific input type. Instead, it works with any type `T` that:
|
||||
|
||||
- Implements the `IsEven` trait.
|
||||
- Implements the `Debug` trait.
|
||||
|
||||
This contract is expressed with a **trait bound**: `T: IsEven + Debug`.
|
||||
This contract is expressed with a **trait bound**: `T: IsEven + Debug`.\
|
||||
The `+` symbol is used to require that `T` implements multiple traits. `T: IsEven + Debug` is equivalent to
|
||||
"where `T` implements `IsEven` **and** `Debug`".
|
||||
|
||||
## Trait bounds
|
||||
|
||||
What purpose do trait bounds serve in `print_if_even`?
|
||||
What purpose do trait bounds serve in `print_if_even`?\
|
||||
To find out, let's try to remove them:
|
||||
|
||||
```rust
|
||||
@@ -114,9 +114,9 @@ help: consider restricting type parameter `T`
|
||||
| +++++++++++++++++
|
||||
```
|
||||
|
||||
Without trait bounds, the compiler doesn't know what `T` **can do**.
|
||||
It doesn't know that `T` has an `is_even` method, and it doesn't know how to format `T` for printing.
|
||||
From the compiler point of view, a bare `T` has no behaviour at all.
|
||||
Without trait bounds, the compiler doesn't know what `T` **can do**.\
|
||||
It doesn't know that `T` has an `is_even` method, and it doesn't know how to format `T` for printing.
|
||||
From the compiler point of view, a bare `T` has no behaviour at all.\
|
||||
Trait bounds restrict the set of types that can be used by ensuring that the behaviour required by the function
|
||||
body is present.
|
||||
|
||||
@@ -147,8 +147,8 @@ fn print_if_even<T: IsEven + Debug>(n: T) {
|
||||
|
||||
## Syntax: meaningful names
|
||||
|
||||
In the examples above, we used `T` as the type parameter name. This is a common convention when a function has
|
||||
only one type parameter.
|
||||
In the examples above, we used `T` as the type parameter name. This is a common convention when a function has
|
||||
only one type parameter.\
|
||||
Nothing stops you from using a more meaningful name, though:
|
||||
|
||||
```rust
|
||||
@@ -158,17 +158,17 @@ fn print_if_even<Number: IsEven + Debug>(n: Number) {
|
||||
```
|
||||
|
||||
It is actually **desirable** to use meaningful names when there are multiple type parameters at play or when the name
|
||||
`T` doesn't convey enough information about the type's role in the function.
|
||||
`T` doesn't convey enough information about the type's role in the function.
|
||||
Maximize clarity and readability when naming type parameters, just as you would with variables or function parameters.
|
||||
Follow Rust's conventions though: use camel case for type parameter names.
|
||||
|
||||
## The function signature is king
|
||||
|
||||
You may wonder why we need trait bounds at all. Can't the compiler infer the required traits from the function's body?
|
||||
It could, but it won't.
|
||||
The rationale is the same as for [explicit type annotations on function parameters](../02_basic_calculator/02_variables.md#function-arguments-are-variables):
|
||||
each function signature is a contract between the caller and the callee, and the terms must be explicitly stated.
|
||||
This allows for better error messages, better documentation, less unintentional breakages across versions,
|
||||
You may wonder why we need trait bounds at all. Can't the compiler infer the required traits from the function's body?\
|
||||
It could, but it won't.\
|
||||
The rationale is the same as for [explicit type annotations on function parameters](../02_basic_calculator/02_variables.md#function-arguments-are-variables):
|
||||
each function signature is a contract between the caller and the callee, and the terms must be explicitly stated.
|
||||
This allows for better error messages, better documentation, less unintentional breakages across versions,
|
||||
and faster compilation times.
|
||||
|
||||
## References
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# String slices
|
||||
|
||||
Throughout the previous chapters you've seen quite a few **string literals** being used in the code,
|
||||
Throughout the previous chapters you've seen quite a few **string literals** being used in the code,
|
||||
like `"To-Do"` or `"A ticket description"`.
|
||||
They were always followed by a call to `.to_string()` or `.into()`. It's time to understand why!
|
||||
|
||||
@@ -12,12 +12,12 @@ You define a string literal by enclosing the raw text in double quotes:
|
||||
let s = "Hello, world!";
|
||||
```
|
||||
|
||||
The type of `s` is `&str`, a **reference to a string slice**.
|
||||
The type of `s` is `&str`, a **reference to a string slice**.
|
||||
|
||||
## Memory layout
|
||||
|
||||
`&str` and `String` are different types—they're not interchangeable.
|
||||
Let's recall the memory layout of a `String` from our
|
||||
`&str` and `String` are different types—they're not interchangeable.\
|
||||
Let's recall the memory layout of a `String` from our
|
||||
[previous exploration](../03_ticket_v1/09_heap.md).
|
||||
If we run:
|
||||
|
||||
@@ -41,25 +41,25 @@ Heap: | H | e | l | l | o |
|
||||
+---+---+---+---+---+
|
||||
```
|
||||
|
||||
If you remember, we've [also examined](../03_ticket_v1/10_references_in_memory.md)
|
||||
If you remember, we've [also examined](../03_ticket_v1/10_references_in_memory.md)
|
||||
how a `&String` is laid out in memory:
|
||||
|
||||
```text
|
||||
--------------------------------------
|
||||
| |
|
||||
+----v----+--------+----------+ +----|----+
|
||||
| pointer | length | capacity | | pointer |
|
||||
| | | 5 | 5 | | |
|
||||
+----|----+--------+----------+ +---------+
|
||||
| s &s
|
||||
|
|
||||
v
|
||||
+---+---+---+---+---+
|
||||
| H | e | l | l | o |
|
||||
+---+---+---+---+---+
|
||||
--------------------------------------
|
||||
| |
|
||||
+----v----+--------+----------+ +----|----+
|
||||
| pointer | length | capacity | | pointer |
|
||||
| | | 5 | 5 | | |
|
||||
+----|----+--------+----------+ +---------+
|
||||
| s &s
|
||||
|
|
||||
v
|
||||
+---+---+---+---+---+
|
||||
| H | e | l | l | o |
|
||||
+---+---+---+---+---+
|
||||
```
|
||||
|
||||
`&String` points to the memory location where the `String`'s metadata is stored.
|
||||
`&String` points to the memory location where the `String`'s metadata is stored.\
|
||||
If we follow the pointer, we get to the heap-allocated data. In particular, we get to the first byte of the string, `H`.
|
||||
|
||||
What if we wanted a type that represents a **substring** of `s`? E.g. `ello` in `Hello`?
|
||||
@@ -100,19 +100,19 @@ Heap: | H | e | l | l | o | |
|
||||
- A pointer to the first byte of the slice.
|
||||
- The length of the slice.
|
||||
|
||||
`slice` doesn't own the data, it just points to it: it's a **reference** to the `String`'s heap-allocated data.
|
||||
`slice` doesn't own the data, it just points to it: it's a **reference** to the `String`'s heap-allocated data.\
|
||||
When `slice` is dropped, the heap-allocated data won't be deallocated, because it's still owned by `s`.
|
||||
That's why `slice` doesn't have a `capacity` field: it doesn't own the data, so it doesn't need to know how much
|
||||
That's why `slice` doesn't have a `capacity` field: it doesn't own the data, so it doesn't need to know how much
|
||||
space it was allocated for it; it only cares about the data it references.
|
||||
|
||||
## `&str` vs `&String`
|
||||
|
||||
As a rule of thumb, use `&str` rather than `&String` whenever you need a reference to textual data.
|
||||
As a rule of thumb, use `&str` rather than `&String` whenever you need a reference to textual data.\
|
||||
`&str` is more flexible and generally considered more idiomatic in Rust code.
|
||||
|
||||
If a method returns a `&String`, you're promising that there is heap-allocated UTF-8 text somewhere that
|
||||
**matches exactly** the one you're returning a reference to.
|
||||
If a method returns a `&str`, instead, you have a lot more freedom: you're just saying that *somewhere* there's a
|
||||
If a method returns a `&String`, you're promising that there is heap-allocated UTF-8 text somewhere that
|
||||
**matches exactly** the one you're returning a reference to.\
|
||||
If a method returns a `&str`, instead, you have a lot more freedom: you're just saying that _somewhere_ there's a
|
||||
bunch of text data and that a subset of it matches what you need, therefore you're returning a reference to it.
|
||||
|
||||
## References
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# `Deref` trait
|
||||
|
||||
In the previous exercise you didn't have to do much, did you?
|
||||
In the previous exercise you didn't have to do much, did you?
|
||||
|
||||
Changing
|
||||
|
||||
@@ -22,8 +22,8 @@ impl Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
was all you needed to do to get the code to compile and the tests to pass.
|
||||
Some alarm bells should be ringing in your head though.
|
||||
was all you needed to do to get the code to compile and the tests to pass.
|
||||
Some alarm bells should be ringing in your head though.
|
||||
|
||||
## It shouldn't work, but it does
|
||||
|
||||
@@ -38,7 +38,7 @@ Instead, it just works. **Why**?
|
||||
|
||||
## `Deref` to the rescue
|
||||
|
||||
The `Deref` trait is the mechanism behind the language feature known as [**deref coercion**](https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion).
|
||||
The `Deref` trait is the mechanism behind the language feature known as [**deref coercion**](https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion).\
|
||||
The trait is defined in the standard library, in the `std::ops` module:
|
||||
|
||||
```rust
|
||||
@@ -51,13 +51,13 @@ pub trait Deref {
|
||||
}
|
||||
```
|
||||
|
||||
`type Target` is an **associated type**.
|
||||
`type Target` is an **associated type**.\
|
||||
It's a placeholder for a concrete type that must be specified when the trait is implemented.
|
||||
|
||||
## Deref coercion
|
||||
|
||||
By implementing `Deref<Target = U>` for a type `T` you're telling the compiler that `&T` and `&U` are
|
||||
somewhat interchangeable.
|
||||
By implementing `Deref<Target = U>` for a type `T` you're telling the compiler that `&T` and `&U` are
|
||||
somewhat interchangeable.\
|
||||
In particular, you get the following behavior:
|
||||
|
||||
- References to `T` are implicitly converted into references to `U` (i.e. `&T` becomes `&U`)
|
||||
@@ -84,11 +84,11 @@ Thanks to this implementation and deref coercion, a `&String` is automatically c
|
||||
|
||||
## Don't abuse deref coercion
|
||||
|
||||
Deref coercion is a powerful feature, but it can lead to confusion.
|
||||
Deref coercion is a powerful feature, but it can lead to confusion.\
|
||||
Automatically converting types can make the code harder to read and understand. If a method with the same name
|
||||
is defined on both `T` and `U`, which one will be called?
|
||||
is defined on both `T` and `U`, which one will be called?
|
||||
|
||||
We'll examine later in the course the "safest" use cases for deref coercion: smart pointers.
|
||||
We'll examine later in the course the "safest" use cases for deref coercion: smart pointers.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# `Sized`
|
||||
|
||||
There's more to `&str` than meets the eye, even after having
|
||||
investigated deref coercion.
|
||||
There's more to `&str` than meets the eye, even after having
|
||||
investigated deref coercion.\
|
||||
From our previous [discussion on memory layouts](../03_ticket_v1/10_references_in_memory.md),
|
||||
it would have been reasonable to expect `&str` to be represented as a single `usize` on
|
||||
the stack, a pointer. That's not the case though. `&str` stores some **metadata** next
|
||||
to the pointer: the length of the slice it points to. Going back to the example from
|
||||
the stack, a pointer. That's not the case though. `&str` stores some **metadata** next
|
||||
to the pointer: the length of the slice it points to. Going back to the example from
|
||||
[a previous section](06_str_slice.md):
|
||||
|
||||
```rust
|
||||
@@ -38,16 +38,16 @@ What's going on?
|
||||
|
||||
## Dynamically sized types
|
||||
|
||||
`str` is a **dynamically sized type** (DST).
|
||||
A DST is a type whose size is not known at compile time. Whenever you have a
|
||||
`str` is a **dynamically sized type** (DST).\
|
||||
A DST is a type whose size is not known at compile time. Whenever you have a
|
||||
reference to a DST, like `&str`, it has to include additional
|
||||
information about the data it points to. It is a **fat pointer**.
|
||||
In the case of `&str`, it stores the length of the slice it points to.
|
||||
information about the data it points to. It is a **fat pointer**.\
|
||||
In the case of `&str`, it stores the length of the slice it points to.
|
||||
We'll see more examples of DSTs in the rest of the course.
|
||||
|
||||
## The `Sized` trait
|
||||
|
||||
Rust's `std` library defines a trait called `Sized`.
|
||||
Rust's `std` library defines a trait called `Sized`.
|
||||
|
||||
```rust
|
||||
pub trait Sized {
|
||||
@@ -59,14 +59,14 @@ A type is `Sized` if its size is known at compile time. In other words, it's not
|
||||
|
||||
### Marker traits
|
||||
|
||||
`Sized` is your first example of a **marker trait**.
|
||||
`Sized` is your first example of a **marker trait**.\
|
||||
A marker trait is a trait that doesn't require any methods to be implemented. It doesn't define any behavior.
|
||||
It only serves to **mark** a type as having certain properties.
|
||||
The mark is then leveraged by the compiler to enable certain behaviors or optimizations.
|
||||
The mark is then leveraged by the compiler to enable certain behaviors or optimizations.
|
||||
|
||||
### Auto traits
|
||||
|
||||
In particular, `Sized` is also an **auto trait**.
|
||||
In particular, `Sized` is also an **auto trait**.\
|
||||
You don't need to implement it explicitly; the compiler implements it automatically for you
|
||||
based on the type's definition.
|
||||
|
||||
@@ -74,8 +74,8 @@ based on the type's definition.
|
||||
|
||||
All the types we've seen so far are `Sized`: `u32`, `String`, `bool`, etc.
|
||||
|
||||
`str`, as we just saw, is not `Sized`.
|
||||
`&str` is `Sized` though! We know its size at compile time: two `usize`s, one for the pointer
|
||||
`str`, as we just saw, is not `Sized`.\
|
||||
`&str` is `Sized` though! We know its size at compile time: two `usize`s, one for the pointer
|
||||
and one for the length.
|
||||
|
||||
## References
|
||||
|
||||
@@ -20,13 +20,13 @@ impl Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
We've also seen that string literals (such as `"A title"`) are of type `&str`.
|
||||
We have a type mismatch here: a `String` is expected, but we have a `&str`.
|
||||
We've also seen that string literals (such as `"A title"`) are of type `&str`.\
|
||||
We have a type mismatch here: a `String` is expected, but we have a `&str`.
|
||||
No magical coercion will come to save us this time; we need **to perform a conversion**.
|
||||
|
||||
## `From` and `Into`
|
||||
|
||||
The Rust standard library defines two traits for **infallible conversions**: `From` and `Into`,
|
||||
The Rust standard library defines two traits for **infallible conversions**: `From` and `Into`,
|
||||
in the `std::convert` module.
|
||||
|
||||
```rust
|
||||
@@ -39,7 +39,7 @@ pub trait Into<T>: Sized {
|
||||
}
|
||||
```
|
||||
|
||||
These trait definitions showcase a few concepts that we haven't seen before: **supertraits** and **implicit trait bounds**.
|
||||
These trait definitions showcase a few concepts that we haven't seen before: **supertraits** and **implicit trait bounds**.
|
||||
Let's unpack those first.
|
||||
|
||||
### Supertrait / Subtrait
|
||||
@@ -78,7 +78,7 @@ pub trait From<T: Sized>: Sized {
|
||||
```
|
||||
|
||||
In other words, _both_ `T` and the type implementing `From<T>` must be `Sized`, even
|
||||
though the former bound is implicit.
|
||||
though the former bound is implicit.
|
||||
|
||||
### Negative trait bounds
|
||||
|
||||
@@ -94,23 +94,23 @@ pub struct Foo<T: ?Sized> {
|
||||
|
||||
This syntax reads as "`T` may or may not be `Sized`", and it allows you to
|
||||
bind `T` to a DST (e.g. `Foo<str>`). It is a special case, though: negative trait bounds are exclusive to `Sized`,
|
||||
you can't use them with other traits.
|
||||
you can't use them with other traits.
|
||||
|
||||
## `&str` to `String`
|
||||
|
||||
In [`std`'s documentation](https://doc.rust-lang.org/std/convert/trait.From.html#implementors)
|
||||
you can see which `std` types implement the `From` trait.
|
||||
In [`std`'s documentation](https://doc.rust-lang.org/std/convert/trait.From.html#implementors)
|
||||
you can see which `std` types implement the `From` trait.\
|
||||
You'll find that `String` implements `From<&str> for String`. Thus, we can write:
|
||||
|
||||
```rust
|
||||
let title = String::from("A title");
|
||||
```
|
||||
|
||||
We've been primarily using `.into()`, though.
|
||||
We've been primarily using `.into()`, though.\
|
||||
If you check out the [implementors of `Into`](https://doc.rust-lang.org/std/convert/trait.Into.html#implementors)
|
||||
you won't find `Into<&str> for String`. What's going on?
|
||||
|
||||
`From` and `Into` are **dual traits**.
|
||||
`From` and `Into` are **dual traits**.\
|
||||
In particular, `Into` is implemented for any type that implements `From` using a **blanket implementation**:
|
||||
|
||||
```rust
|
||||
@@ -129,7 +129,7 @@ we can write `let title = "A title".into();`.
|
||||
|
||||
## `.into()`
|
||||
|
||||
Every time you see `.into()`, you're witnessing a conversion between types.
|
||||
Every time you see `.into()`, you're witnessing a conversion between types.\
|
||||
What's the target type, though?
|
||||
|
||||
In most cases, the target type is either:
|
||||
|
||||
@@ -14,20 +14,20 @@ pub trait Deref {
|
||||
}
|
||||
```
|
||||
|
||||
They both feature type parameters.
|
||||
In the case of `From`, it's a generic parameter, `T`.
|
||||
They both feature type parameters.\
|
||||
In the case of `From`, it's a generic parameter, `T`.\
|
||||
In the case of `Deref`, it's an associated type, `Target`.
|
||||
|
||||
What's the difference? Why use one over the other?
|
||||
|
||||
## At most one implementation
|
||||
|
||||
Due to how deref coercion works, there can only be one "target" type for a given type. E.g. `String` can
|
||||
only deref to `str`.
|
||||
Due to how deref coercion works, there can only be one "target" type for a given type. E.g. `String` can
|
||||
only deref to `str`.
|
||||
It's about avoiding ambiguity: if you could implement `Deref` multiple times for a type,
|
||||
which `Target` type should the compiler choose when you call a `&self` method?
|
||||
|
||||
That's why `Deref` uses an associated type, `Target`.
|
||||
That's why `Deref` uses an associated type, `Target`.\
|
||||
An associated type is uniquely determined **by the trait implementation**.
|
||||
Since you can't implement `Deref` more than once, you'll only be able to specify one `Target` for a given type
|
||||
and there won't be any ambiguity.
|
||||
@@ -51,7 +51,7 @@ impl From<u16> for WrappingU32 {
|
||||
}
|
||||
```
|
||||
|
||||
This works because `From<u16>` and `From<u32>` are considered **different traits**.
|
||||
This works because `From<u16>` and `From<u32>` are considered **different traits**.\
|
||||
There is no ambiguity: the compiler can determine which implementation to use based on type of the value being converted.
|
||||
|
||||
## Case study: `Add`
|
||||
@@ -73,7 +73,7 @@ It uses both mechanisms:
|
||||
|
||||
### `RHS`
|
||||
|
||||
`RHS` is a generic parameter to allow for different types to be added together.
|
||||
`RHS` is a generic parameter to allow for different types to be added together.\
|
||||
For example, you'll find these two implementations in the standard library:
|
||||
|
||||
```rust
|
||||
@@ -109,10 +109,10 @@ because `u32` implements `Add<&u32>` _as well as_ `Add<u32>`.
|
||||
|
||||
### `Output`
|
||||
|
||||
`Output` represents the type of the result of the addition.
|
||||
`Output` represents the type of the result of the addition.
|
||||
|
||||
Why do we need `Output` in the first place? Can't we just use `Self` as output, the type implementing `Add`?
|
||||
We could, but it would limit the flexibility of the trait. In the standard library, for example, you'll find
|
||||
Why do we need `Output` in the first place? Can't we just use `Self` as output, the type implementing `Add`?
|
||||
We could, but it would limit the flexibility of the trait. In the standard library, for example, you'll find
|
||||
this implementation:
|
||||
|
||||
```rust
|
||||
@@ -125,9 +125,9 @@ impl Add<&u32> for &u32 {
|
||||
}
|
||||
```
|
||||
|
||||
The type they're implementing the trait for is `&u32`, but the result of the addition is `u32`.
|
||||
It would be impossible[^flexible] to provide this implementation if `add` had to return `Self`, i.e. `&u32` in this case.
|
||||
`Output` lets `std` decouple the implementor from the return type, thus supporting this case.
|
||||
The type they're implementing the trait for is `&u32`, but the result of the addition is `u32`.\
|
||||
It would be impossible[^flexible] to provide this implementation if `add` had to return `Self`, i.e. `&u32` in this case.
|
||||
`Output` lets `std` decouple the implementor from the return type, thus supporting this case.
|
||||
|
||||
On the other hand, `Output` can't be a generic parameter. The output type of the operation **must** be uniquely determined
|
||||
once the types of the operands are known. That's why it's an associated type: for a given combination of implementor
|
||||
@@ -138,7 +138,7 @@ and generic parameters, there is only one `Output` type.
|
||||
To recap:
|
||||
|
||||
- Use an **associated type** when the type must be uniquely determined for a given trait implementation.
|
||||
- Use a **generic parameter** when you want to allow multiple implementations of the trait for the same type,
|
||||
- Use a **generic parameter** when you want to allow multiple implementations of the trait for the same type,
|
||||
with different input types.
|
||||
|
||||
## References
|
||||
@@ -146,6 +146,5 @@ To recap:
|
||||
- The exercise for this section is located in `exercises/04_traits/10_assoc_vs_generic`
|
||||
|
||||
[^flexible]: Flexibility is rarely free: the trait definition is more complex due to `Output`, and implementors have to reason about
|
||||
what they want to return. The trade-off is only justified if that flexibility is actually needed. Keep that in mind
|
||||
when designing your own traits.
|
||||
|
||||
what they want to return. The trade-off is only justified if that flexibility is actually needed. Keep that in mind
|
||||
when designing your own traits.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Copying values, pt. 1
|
||||
|
||||
In the previous chapter we introduced ownership and borrowing.
|
||||
In the previous chapter we introduced ownership and borrowing.\
|
||||
We stated, in particular, that:
|
||||
|
||||
- Every value in Rust has a single owner at any given time.
|
||||
- When a function takes ownership of a value ("it consumes it"), the caller can't use that value anymore.
|
||||
|
||||
These restrictions can be somewhat limiting.
|
||||
Sometimes we might have to call a function that takes ownership of a value, but we still need to use
|
||||
These restrictions can be somewhat limiting.\
|
||||
Sometimes we might have to call a function that takes ownership of a value, but we still need to use
|
||||
that value afterward.
|
||||
|
||||
```rust
|
||||
@@ -49,8 +49,8 @@ fn example() {
|
||||
}
|
||||
```
|
||||
|
||||
Instead of giving ownership of `s` to `consumer`, we create a new `String` (by cloning `s`) and give
|
||||
that to `consumer` instead.
|
||||
Instead of giving ownership of `s` to `consumer`, we create a new `String` (by cloning `s`) and give
|
||||
that to `consumer` instead.\
|
||||
`s` remains valid and usable after the call to `consumer`.
|
||||
|
||||
## In memory
|
||||
@@ -92,7 +92,7 @@ If you're coming from a language like Java, you can think of `clone` as a way to
|
||||
|
||||
## Implementing `Clone`
|
||||
|
||||
To make a type `Clone`-able, we have to implement the `Clone` trait for it.
|
||||
To make a type `Clone`-able, we have to implement the `Clone` trait for it.\
|
||||
You almost always implement `Clone` by deriving it:
|
||||
|
||||
```rust
|
||||
@@ -103,7 +103,7 @@ struct MyType {
|
||||
```
|
||||
|
||||
The compiler implements `Clone` for `MyType` as you would expect: it clones each field of `MyType` individually and
|
||||
then constructs a new `MyType` instance using the cloned fields.
|
||||
then constructs a new `MyType` instance using the cloned fields.\
|
||||
Remember that you can use `cargo expand` (or your IDE) to explore the code generated by `derive` macros.
|
||||
|
||||
## References
|
||||
|
||||
@@ -12,7 +12,7 @@ fn example() {
|
||||
}
|
||||
```
|
||||
|
||||
It'll compile without errors! What's going on here? What's the difference between `String` and `u32`
|
||||
It'll compile without errors! What's going on here? What's the difference between `String` and `u32`
|
||||
that makes the latter work without `.clone()`?
|
||||
|
||||
## `Copy`
|
||||
@@ -26,65 +26,65 @@ pub trait Copy: Clone { }
|
||||
It is a marker trait, just like `Sized`.
|
||||
|
||||
If a type implements `Copy`, there's no need to call `.clone()` to create a new instance of the type:
|
||||
Rust does it **implicitly** for you.
|
||||
Rust does it **implicitly** for you.\
|
||||
`u32` is an example of a type that implements `Copy`, which is why the example above compiles without errors:
|
||||
when `consumer(s)` is called, Rust creates a new `u32` instance by performing a **bitwise copy** of `s`,
|
||||
when `consumer(s)` is called, Rust creates a new `u32` instance by performing a **bitwise copy** of `s`,
|
||||
and then passes that new instance to `consumer`. It all happens behind the scenes, without you having to do anything.
|
||||
|
||||
## What can be `Copy`?
|
||||
|
||||
`Copy` is not equivalent to "automatic cloning", although it implies it.
|
||||
`Copy` is not equivalent to "automatic cloning", although it implies it.\
|
||||
Types must meet a few requirements in order to be allowed to implement `Copy`.
|
||||
|
||||
First of all, it must implement `Clone`, since `Copy` is a subtrait of `Clone`.
|
||||
This makes sense: if Rust can create a new instance of a type _implicitly_, it should
|
||||
This makes sense: if Rust can create a new instance of a type _implicitly_, it should
|
||||
also be able to create a new instance _explicitly_ by calling `.clone()`.
|
||||
|
||||
That's not all, though. A few more conditions must be met:
|
||||
|
||||
1. The type doesn't manage any _additional_ resources (e.g. heap memory, file handles, etc.) beyond the `std::mem::size_of`
|
||||
bytes that it occupies in memory.
|
||||
bytes that it occupies in memory.
|
||||
2. The type is not a mutable reference (`&mut T`).
|
||||
|
||||
If both conditions are met, then Rust can safely create a new instance of the type by performing a **bitwise copy**
|
||||
If both conditions are met, then Rust can safely create a new instance of the type by performing a **bitwise copy**
|
||||
of the original instance—this is often referred to as a `memcpy` operation, after the C standard library function
|
||||
that performs the bitwise copy.
|
||||
|
||||
### Case study 1: `String`
|
||||
|
||||
`String` is a type that doesn't implement `Copy`.
|
||||
`String` is a type that doesn't implement `Copy`.\
|
||||
Why? Because it manages an additional resource: the heap-allocated memory buffer that stores the string's data.
|
||||
|
||||
Let's imagine that Rust allowed `String` to implement `Copy`.
|
||||
Let's imagine that Rust allowed `String` to implement `Copy`.\
|
||||
Then, when a new `String` instance is created by performing a bitwise copy of the original instance, both the original
|
||||
and the new instance would point to the same memory buffer:
|
||||
and the new instance would point to the same memory buffer:
|
||||
|
||||
```text
|
||||
s copied_s
|
||||
+---------+--------+----------+ +---------+--------+----------+
|
||||
| pointer | length | capacity | | pointer | length | capacity |
|
||||
| | | 5 | 5 | | | | 5 | 5 |
|
||||
+--|------+--------+----------+ +--|------+--------+----------+
|
||||
| |
|
||||
| |
|
||||
v |
|
||||
+---+---+---+---+---+ |
|
||||
| H | e | l | l | o | |
|
||||
+---+---+---+---+---+ |
|
||||
^ |
|
||||
| |
|
||||
+------------------------------------+
|
||||
s copied_s
|
||||
+---------+--------+----------+ +---------+--------+----------+
|
||||
| pointer | length | capacity | | pointer | length | capacity |
|
||||
| | | 5 | 5 | | | | 5 | 5 |
|
||||
+--|------+--------+----------+ +--|------+--------+----------+
|
||||
| |
|
||||
| |
|
||||
v |
|
||||
+---+---+---+---+---+ |
|
||||
| H | e | l | l | o | |
|
||||
+---+---+---+---+---+ |
|
||||
^ |
|
||||
| |
|
||||
+------------------------------------+
|
||||
```
|
||||
|
||||
This is bad!
|
||||
Both `String` instances would try to free the memory buffer when they go out of scope,
|
||||
Both `String` instances would try to free the memory buffer when they go out of scope,
|
||||
leading to a double-free error.
|
||||
You could also create two distinct `&mut String` references that point to the same memory buffer,
|
||||
violating Rust's borrowing rules.
|
||||
|
||||
### Case study 2: `u32`
|
||||
|
||||
`u32` implements `Copy`. All integer types do, in fact.
|
||||
`u32` implements `Copy`. All integer types do, in fact.\
|
||||
An integer is "just" the bytes that represent the number in memory. There's nothing more!
|
||||
If you copy those bytes, you get another perfectly valid integer instance.
|
||||
Nothing bad can happen, so Rust allows it.
|
||||
@@ -92,12 +92,12 @@ Nothing bad can happen, so Rust allows it.
|
||||
### Case study 3: `&mut u32`
|
||||
|
||||
When we introduced ownership and mutable borrows, we stated one rule quite clearly: there
|
||||
can only ever be *one* mutable borrow of a value at any given time.
|
||||
can only ever be _one_ mutable borrow of a value at any given time.\
|
||||
That's why `&mut u32` doesn't implement `Copy`, even though `u32` does.
|
||||
|
||||
If `&mut u32` implemented `Copy`, you could create multiple mutable references to
|
||||
If `&mut u32` implemented `Copy`, you could create multiple mutable references to
|
||||
the same value and modify it in multiple places at the same time.
|
||||
That'd be a violation of Rust's borrowing rules!
|
||||
That'd be a violation of Rust's borrowing rules!
|
||||
It follows that `&mut T` never implements `Copy`, no matter what `T` is.
|
||||
|
||||
## Implementing `Copy`
|
||||
|
||||
@@ -15,7 +15,7 @@ pub trait Drop {
|
||||
```
|
||||
|
||||
The `Drop` trait is a mechanism for you to define _additional_ cleanup logic for your types,
|
||||
beyond what the compiler does for you automatically.
|
||||
beyond what the compiler does for you automatically.\
|
||||
Whatever you put in the `drop` method will be executed when the value goes out of scope.
|
||||
|
||||
## `Drop` and `Copy`
|
||||
@@ -24,7 +24,7 @@ When talking about the `Copy` trait, we said that a type can't implement `Copy`
|
||||
manages additional resources beyond the `std::mem::size_of` bytes that it occupies in memory.
|
||||
|
||||
You might wonder: how does the compiler know if a type manages additional resources?
|
||||
That's right: `Drop` trait implementations!
|
||||
That's right: `Drop` trait implementations!\
|
||||
If your type has an explicit `Drop` implementation, the compiler will assume
|
||||
that your type has additional resources attached to it and won't allow you to implement `Copy`.
|
||||
|
||||
|
||||
@@ -6,26 +6,26 @@ so often when writing Rust code that they'll soon become second nature.
|
||||
|
||||
## Closing thoughts
|
||||
|
||||
Traits are powerful, but don't overuse them.
|
||||
Traits are powerful, but don't overuse them.\
|
||||
A few guidelines to keep in mind:
|
||||
|
||||
- Don't make a function generic if it is always invoked with a single type. It introduces indirection in your
|
||||
codebase, making it harder to understand and maintain.
|
||||
- Don't make a function generic if it is always invoked with a single type. It introduces indirection in your
|
||||
codebase, making it harder to understand and maintain.
|
||||
- Don't create a trait if you only have one implementation. It's a sign that the trait is not needed.
|
||||
- Implement standard traits for your types (`Debug`, `PartialEq`, etc.) whenever it makes sense.
|
||||
It will make your types more idiomatic and easier to work with, unlocking a lot of functionality provided
|
||||
It will make your types more idiomatic and easier to work with, unlocking a lot of functionality provided
|
||||
by the standard library and ecosystem crates.
|
||||
- Implement traits from third-party crates if you need the functionality they unlock within their ecosystem.
|
||||
- Beware of making code generic solely to use mocks in your tests. The maintainability cost of this approach
|
||||
can be high, and it's often better to use a different testing strategy. Check out the
|
||||
[testing masterclass](https://github.com/mainmatter/rust-advanced-testing-workshop)
|
||||
- Implement traits from third-party crates if you need the functionality they unlock within their ecosystem.
|
||||
- Beware of making code generic solely to use mocks in your tests. The maintainability cost of this approach
|
||||
can be high, and it's often better to use a different testing strategy. Check out the
|
||||
[testing masterclass](https://github.com/mainmatter/rust-advanced-testing-workshop)
|
||||
for details on high-fidelity testing.
|
||||
|
||||
## Testing your knowledge
|
||||
|
||||
Before moving on, let's go through one last exercise to consolidate what we've learned.
|
||||
Before moving on, let's go through one last exercise to consolidate what we've learned.
|
||||
You'll have minimal guidance this time—just the exercise description and the tests to guide you.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/04_traits/14_outro`
|
||||
- The exercise for this section is located in `exercises/04_traits/14_outro`
|
||||
|
||||
Reference in New Issue
Block a user