Rollup merge of #24571 - steveklabnik:editing, r=alexcrichton
A bunch of chapters, fixes an issue or two as well. r? @alexcrichton
This commit is contained in:
commit
ff12b7b308
10 changed files with 405 additions and 504 deletions
|
@ -14,7 +14,6 @@
|
||||||
* [Concurrency](concurrency.md)
|
* [Concurrency](concurrency.md)
|
||||||
* [Error Handling](error-handling.md)
|
* [Error Handling](error-handling.md)
|
||||||
* [FFI](ffi.md)
|
* [FFI](ffi.md)
|
||||||
* [Deref coercions](deref-coercions.md)
|
|
||||||
* [Syntax and Semantics](syntax-and-semantics.md)
|
* [Syntax and Semantics](syntax-and-semantics.md)
|
||||||
* [Variable Bindings](variable-bindings.md)
|
* [Variable Bindings](variable-bindings.md)
|
||||||
* [Functions](functions.md)
|
* [Functions](functions.md)
|
||||||
|
@ -30,15 +29,15 @@
|
||||||
* [Move semantics](move-semantics.md)
|
* [Move semantics](move-semantics.md)
|
||||||
* [Enums](enums.md)
|
* [Enums](enums.md)
|
||||||
* [Match](match.md)
|
* [Match](match.md)
|
||||||
* [Patterns](patterns.md)
|
|
||||||
* [Structs](structs.md)
|
* [Structs](structs.md)
|
||||||
|
* [Patterns](patterns.md)
|
||||||
* [Method Syntax](method-syntax.md)
|
* [Method Syntax](method-syntax.md)
|
||||||
* [Drop](drop.md)
|
|
||||||
* [Vectors](vectors.md)
|
* [Vectors](vectors.md)
|
||||||
* [Strings](strings.md)
|
* [Strings](strings.md)
|
||||||
|
* [Generics](generics.md)
|
||||||
* [Traits](traits.md)
|
* [Traits](traits.md)
|
||||||
* [Operators and Overloading](operators-and-overloading.md)
|
* [Operators and Overloading](operators-and-overloading.md)
|
||||||
* [Generics](generics.md)
|
* [Drop](drop.md)
|
||||||
* [if let](if-let.md)
|
* [if let](if-let.md)
|
||||||
* [Trait Objects](trait-objects.md)
|
* [Trait Objects](trait-objects.md)
|
||||||
* [Closures](closures.md)
|
* [Closures](closures.md)
|
||||||
|
@ -53,6 +52,7 @@
|
||||||
* [Casting between types](casting-between-types.md)
|
* [Casting between types](casting-between-types.md)
|
||||||
* [Associated Types](associated-types.md)
|
* [Associated Types](associated-types.md)
|
||||||
* [Unsized Types](unsized-types.md)
|
* [Unsized Types](unsized-types.md)
|
||||||
|
* [Deref coercions](deref-coercions.md)
|
||||||
* [Macros](macros.md)
|
* [Macros](macros.md)
|
||||||
* [`unsafe` Code](unsafe-code.md)
|
* [`unsafe` Code](unsafe-code.md)
|
||||||
* [Nightly Rust](nightly-rust.md)
|
* [Nightly Rust](nightly-rust.md)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
% Enums
|
% Enums
|
||||||
|
|
||||||
Finally, Rust has a "sum type", an *enum*. Enums are an incredibly useful
|
Rust has a ‘sum type’, an `enum`. Enums are an incredibly useful feature of
|
||||||
feature of Rust, and are used throughout the standard library. An `enum` is
|
Rust, and are used throughout the standard library. An `enum` is a type which
|
||||||
a type which relates a set of alternates to a specific name. For example, below
|
relates a set of alternates to a specific name. For example, below we define
|
||||||
we define `Character` to be either a `Digit` or something else. These
|
`Character` to be either a `Digit` or something else.
|
||||||
can be used via their fully scoped names: `Character::Other` (more about `::`
|
|
||||||
below).
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
enum Character {
|
enum Character {
|
||||||
|
@ -14,14 +12,14 @@ enum Character {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Most normal types are allowed as the variant components of an `enum`. Here are
|
Most types are allowed as the variant components of an `enum`. Here are some
|
||||||
some examples:
|
examples:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
struct Empty;
|
struct Empty;
|
||||||
struct Color(i32, i32, i32);
|
struct Color(i32, i32, i32);
|
||||||
struct Length(i32);
|
struct Length(i32);
|
||||||
struct Status { Health: i32, Mana: i32, Attack: i32, Defense: i32 }
|
struct Stats { Health: i32, Mana: i32, Attack: i32, Defense: i32 }
|
||||||
struct HeightDatabase(Vec<i32>);
|
struct HeightDatabase(Vec<i32>);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -30,12 +28,12 @@ In `Character`, for instance, `Digit` gives a meaningful name for an `i32`
|
||||||
value, where `Other` is only a name. However, the fact that they represent
|
value, where `Other` is only a name. However, the fact that they represent
|
||||||
distinct categories of `Character` is a very useful property.
|
distinct categories of `Character` is a very useful property.
|
||||||
|
|
||||||
As with structures, the variants of an enum by default are not comparable with
|
The variants of an `enum` by default are not comparable with equality operators
|
||||||
equality operators (`==`, `!=`), have no ordering (`<`, `>=`, etc.), and do not
|
(`==`, `!=`), have no ordering (`<`, `>=`, etc.), and do not support other
|
||||||
support other binary operations such as `*` and `+`. As such, the following code
|
binary operations such as `*` and `+`. As such, the following code is invalid
|
||||||
is invalid for the example `Character` type:
|
for the example `Character` type:
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
// These assignments both succeed
|
// These assignments both succeed
|
||||||
let ten = Character::Digit(10);
|
let ten = Character::Digit(10);
|
||||||
let four = Character::Digit(4);
|
let four = Character::Digit(4);
|
||||||
|
@ -50,98 +48,21 @@ let four_is_smaller = four <= ten;
|
||||||
let four_equals_ten = four == ten;
|
let four_equals_ten = four == ten;
|
||||||
```
|
```
|
||||||
|
|
||||||
This may seem rather limiting, but it's a limitation which we can overcome.
|
We use the `::` syntax to use the name of each variant: They’re scoped by the name
|
||||||
There are two ways: by implementing equality ourselves, or by pattern matching
|
of the `enum` itself. This allows both of these to work:
|
||||||
variants with [`match`][match] expressions, which you'll learn in the next
|
|
||||||
chapter. We don't know enough about Rust to implement equality yet, but we can
|
|
||||||
use the `Ordering` enum from the standard library, which does:
|
|
||||||
|
|
||||||
```
|
```rust,ignore
|
||||||
enum Ordering {
|
Character::Digit(10);
|
||||||
Less,
|
Hand::Digit;
|
||||||
Equal,
|
|
||||||
Greater,
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Because `Ordering` has already been defined for us, we will import it with the
|
Both variants are named `Digit`, but since they’re scoped to the `enum` name,
|
||||||
`use` keyword. Here's an example of how it is used:
|
|
||||||
|
|
||||||
```{rust}
|
Not supporting these operations may seem rather limiting, but it’s a limitation
|
||||||
use std::cmp::Ordering;
|
which we can overcome. There are two ways: by implementing equality ourselves,
|
||||||
|
or by pattern matching variants with [`match`][match] expressions, which you’ll
|
||||||
|
learn in the next section. We don’t know enough about Rust to implement
|
||||||
|
equality yet, but we’ll find out in the [`traits`][traits] section.
|
||||||
|
|
||||||
fn cmp(a: i32, b: i32) -> Ordering {
|
[match]: match.html
|
||||||
if a < b { Ordering::Less }
|
[traits]: traits.html
|
||||||
else if a > b { Ordering::Greater }
|
|
||||||
else { Ordering::Equal }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let x = 5;
|
|
||||||
let y = 10;
|
|
||||||
|
|
||||||
let ordering = cmp(x, y); // ordering: Ordering
|
|
||||||
|
|
||||||
if ordering == Ordering::Less {
|
|
||||||
println!("less");
|
|
||||||
} else if ordering == Ordering::Greater {
|
|
||||||
println!("greater");
|
|
||||||
} else if ordering == Ordering::Equal {
|
|
||||||
println!("equal");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `::` symbol is used to indicate a namespace. In this case, `Ordering` lives
|
|
||||||
in the `cmp` submodule of the `std` module. We'll talk more about modules later
|
|
||||||
in the guide. For now, all you need to know is that you can `use` things from
|
|
||||||
the standard library if you need them.
|
|
||||||
|
|
||||||
Okay, let's talk about the actual code in the example. `cmp` is a function that
|
|
||||||
compares two things, and returns an `Ordering`. We return either
|
|
||||||
`Ordering::Less`, `Ordering::Greater`, or `Ordering::Equal`, depending on
|
|
||||||
whether the first value is less than, greater than, or equal to the second. Note
|
|
||||||
that each variant of the `enum` is namespaced under the `enum` itself: it's
|
|
||||||
`Ordering::Greater`, not `Greater`.
|
|
||||||
|
|
||||||
The `ordering` variable has the type `Ordering`, and so contains one of the
|
|
||||||
three values. We then do a bunch of `if`/`else` comparisons to check which
|
|
||||||
one it is.
|
|
||||||
|
|
||||||
This `Ordering::Greater` notation is too long. Let's use another form of `use`
|
|
||||||
to import the `enum` variants instead. This will avoid full scoping:
|
|
||||||
|
|
||||||
```{rust}
|
|
||||||
use std::cmp::Ordering::{self, Equal, Less, Greater};
|
|
||||||
|
|
||||||
fn cmp(a: i32, b: i32) -> Ordering {
|
|
||||||
if a < b { Less }
|
|
||||||
else if a > b { Greater }
|
|
||||||
else { Equal }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let x = 5;
|
|
||||||
let y = 10;
|
|
||||||
|
|
||||||
let ordering = cmp(x, y); // ordering: Ordering
|
|
||||||
|
|
||||||
if ordering == Less { println!("less"); }
|
|
||||||
else if ordering == Greater { println!("greater"); }
|
|
||||||
else if ordering == Equal { println!("equal"); }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Importing variants is convenient and compact, but can also cause name conflicts,
|
|
||||||
so do this with caution. For this reason, it's normally considered better style
|
|
||||||
to `use` an enum rather than its variants directly.
|
|
||||||
|
|
||||||
As you can see, `enum`s are quite a powerful tool for data representation, and
|
|
||||||
are even more useful when they're [generic][generics] across types. Before we
|
|
||||||
get to generics, though, let's talk about how to use enums with pattern
|
|
||||||
matching, a tool that will let us deconstruct sum types (the type theory term
|
|
||||||
for enums) like `Ordering` in a very elegant way that avoids all these messy
|
|
||||||
and brittle `if`/`else`s.
|
|
||||||
|
|
||||||
[match]: ./match.html
|
|
||||||
[generics]: ./generics.html
|
|
||||||
|
|
|
@ -1,31 +1,13 @@
|
||||||
% Generics
|
% Generics
|
||||||
|
|
||||||
Sometimes, when writing a function or data type, we may want it to work for
|
Sometimes, when writing a function or data type, we may want it to work for
|
||||||
multiple types of arguments. For example, remember our `OptionalInt` type?
|
multiple types of arguments. Luckily, Rust has a feature that gives us a better
|
||||||
|
way: generics. Generics are called ‘parametric polymorphism’ in type theory,
|
||||||
|
which means that they are types or functions that have multiple forms (‘poly’
|
||||||
|
is multiple, ‘morph’ is form) over a given parameter (‘parametric’).
|
||||||
|
|
||||||
```{rust}
|
Anyway, enough with type theory, let’s check out some generic code. Rust’s
|
||||||
enum OptionalInt {
|
standard library provides a type, `Option<T>`, that’s generic:
|
||||||
Value(i32),
|
|
||||||
Missing,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If we wanted to also have an `OptionalFloat64`, we would need a new enum:
|
|
||||||
|
|
||||||
```{rust}
|
|
||||||
enum OptionalFloat64 {
|
|
||||||
Valuef64(f64),
|
|
||||||
Missingf64,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is really unfortunate. Luckily, Rust has a feature that gives us a better
|
|
||||||
way: generics. Generics are called *parametric polymorphism* in type theory,
|
|
||||||
which means that they are types or functions that have multiple forms (*poly*
|
|
||||||
is multiple, *morph* is form) over a given parameter (*parametric*).
|
|
||||||
|
|
||||||
Anyway, enough with type theory declarations, let's check out the generic form
|
|
||||||
of `OptionalInt`. It is actually provided by Rust itself, and looks like this:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
enum Option<T> {
|
enum Option<T> {
|
||||||
|
@ -34,41 +16,40 @@ enum Option<T> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `<T>` part, which you've seen a few times before, indicates that this is
|
The `<T>` part, which you’ve seen a few times before, indicates that this is
|
||||||
a generic data type. Inside the declaration of our enum, wherever we see a `T`,
|
a generic data type. Inside the declaration of our enum, wherever we see a `T`,
|
||||||
we substitute that type for the same type used in the generic. Here's an
|
we substitute that type for the same type used in the generic. Here’s an
|
||||||
example of using `Option<T>`, with some extra type annotations:
|
example of using `Option<T>`, with some extra type annotations:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
let x: Option<i32> = Some(5);
|
let x: Option<i32> = Some(5);
|
||||||
```
|
```
|
||||||
|
|
||||||
In the type declaration, we say `Option<i32>`. Note how similar this looks to
|
In the type declaration, we say `Option<i32>`. Note how similar this looks to
|
||||||
`Option<T>`. So, in this particular `Option`, `T` has the value of `i32`. On
|
`Option<T>`. So, in this particular `Option`, `T` has the value of `i32`. On
|
||||||
the right-hand side of the binding, we do make a `Some(T)`, where `T` is `5`.
|
the right-hand side of the binding, we do make a `Some(T)`, where `T` is `5`.
|
||||||
Since that's an `i32`, the two sides match, and Rust is happy. If they didn't
|
Since that’s an `i32`, the two sides match, and Rust is happy. If they didn’t
|
||||||
match, we'd get an error:
|
match, we’d get an error:
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
let x: Option<f64> = Some(5);
|
let x: Option<f64> = Some(5);
|
||||||
// error: mismatched types: expected `core::option::Option<f64>`,
|
// error: mismatched types: expected `core::option::Option<f64>`,
|
||||||
// found `core::option::Option<_>` (expected f64 but found integral variable)
|
// found `core::option::Option<_>` (expected f64 but found integral variable)
|
||||||
```
|
```
|
||||||
|
|
||||||
That doesn't mean we can't make `Option<T>`s that hold an `f64`! They just have to
|
That doesn’t mean we can’t make `Option<T>`s that hold an `f64`! They just have
|
||||||
match up:
|
to match up:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
let x: Option<i32> = Some(5);
|
let x: Option<i32> = Some(5);
|
||||||
let y: Option<f64> = Some(5.0f64);
|
let y: Option<f64> = Some(5.0f64);
|
||||||
```
|
```
|
||||||
|
|
||||||
This is just fine. One definition, multiple uses.
|
This is just fine. One definition, multiple uses.
|
||||||
|
|
||||||
Generics don't have to only be generic over one type. Consider Rust's built-in
|
Generics don’t have to only be generic over one type. Consider another type from Rust’s standard library that’s similar, `Result<T, E>`:
|
||||||
`Result<T, E>` type:
|
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
enum Result<T, E> {
|
enum Result<T, E> {
|
||||||
Ok(T),
|
Ok(T),
|
||||||
Err(E),
|
Err(E),
|
||||||
|
@ -76,9 +57,9 @@ enum Result<T, E> {
|
||||||
```
|
```
|
||||||
|
|
||||||
This type is generic over _two_ types: `T` and `E`. By the way, the capital letters
|
This type is generic over _two_ types: `T` and `E`. By the way, the capital letters
|
||||||
can be any letter you'd like. We could define `Result<T, E>` as:
|
can be any letter you’d like. We could define `Result<T, E>` as:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
enum Result<A, Z> {
|
enum Result<A, Z> {
|
||||||
Ok(A),
|
Ok(A),
|
||||||
Err(Z),
|
Err(Z),
|
||||||
|
@ -86,92 +67,58 @@ enum Result<A, Z> {
|
||||||
```
|
```
|
||||||
|
|
||||||
if we wanted to. Convention says that the first generic parameter should be
|
if we wanted to. Convention says that the first generic parameter should be
|
||||||
`T`, for 'type,' and that we use `E` for 'error.' Rust doesn't care, however.
|
`T`, for ‘type’, and that we use `E` for ‘error’. Rust doesn’t care, however.
|
||||||
|
|
||||||
The `Result<T, E>` type is intended to be used to return the result of a
|
The `Result<T, E>` type is intended to be used to return the result of a
|
||||||
computation, and to have the ability to return an error if it didn't work out.
|
computation, and to have the ability to return an error if it didn’t work out.
|
||||||
Here's an example:
|
|
||||||
|
|
||||||
```{rust}
|
## Generic functions
|
||||||
let x: Result<f64, String> = Ok(2.3f64);
|
|
||||||
let y: Result<f64, String> = Err("There was an error.".to_string());
|
|
||||||
```
|
|
||||||
|
|
||||||
This particular Result will return an `f64` if there's a success, and a
|
We can write functions that take generic types with a similar syntax:
|
||||||
`String` if there's a failure. Let's write a function that uses `Result<T, E>`:
|
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
fn inverse(x: f64) -> Result<f64, String> {
|
fn takes_anything<T>(x: T) {
|
||||||
if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }
|
// do something with x
|
||||||
|
|
||||||
Ok(1.0f64 / x)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We don't want to take the inverse of zero, so we check to make sure that we
|
The syntax has two parts: the `<T>` says “this function is generic over one
|
||||||
weren't passed zero. If we were, then we return an `Err`, with a message. If
|
type, `T`”, and the `x: T` says “x has the type `T`.”
|
||||||
it's okay, we return an `Ok`, with the answer.
|
|
||||||
|
|
||||||
Why does this matter? Well, remember how `match` does exhaustive matches?
|
Multiple arguments can have the same generic type:
|
||||||
Here's how this function gets used:
|
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
# fn inverse(x: f64) -> Result<f64, String> {
|
fn takes_two_of_the_same_things<T>(x: T, y: T) {
|
||||||
# if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }
|
// ...
|
||||||
# Ok(1.0f64 / x)
|
|
||||||
# }
|
|
||||||
let x = inverse(25.0f64);
|
|
||||||
|
|
||||||
match x {
|
|
||||||
Ok(x) => println!("The inverse of 25 is {}", x),
|
|
||||||
Err(msg) => println!("Error: {}", msg),
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `match` enforces that we handle the `Err` case. In addition, because the
|
We could write a version that takes multiple types:
|
||||||
answer is wrapped up in an `Ok`, we can't just use the result without doing
|
|
||||||
the match:
|
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust
|
||||||
let x = inverse(25.0f64);
|
fn takes_two_things<T, U>(x: T, y: U) {
|
||||||
println!("{}", x + 2.0f64); // error: binary operation `+` cannot be applied
|
// ...
|
||||||
// to type `core::result::Result<f64,collections::string::String>`
|
|
||||||
```
|
|
||||||
|
|
||||||
This function is great, but there's one other problem: it only works for 64 bit
|
|
||||||
floating point values. What if we wanted to handle 32 bit floating point as
|
|
||||||
well? We'd have to write this:
|
|
||||||
|
|
||||||
```{rust}
|
|
||||||
fn inverse32(x: f32) -> Result<f32, String> {
|
|
||||||
if x == 0.0f32 { return Err("x cannot be zero!".to_string()); }
|
|
||||||
|
|
||||||
Ok(1.0f32 / x)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Bummer. What we need is a *generic function*. Luckily, we can write one!
|
Generic functions are most useful with ‘trait bounds’, which we’ll cover in the
|
||||||
However, it won't _quite_ work yet. Before we get into that, let's talk syntax.
|
[section on traits][traits].
|
||||||
A generic version of `inverse` would look something like this:
|
|
||||||
|
|
||||||
```{rust,ignore}
|
[traits]: traits.html
|
||||||
fn inverse<T>(x: T) -> Result<T, String> {
|
|
||||||
if x == 0.0 { return Err("x cannot be zero!".to_string()); }
|
|
||||||
|
|
||||||
Ok(1.0 / x)
|
## Generic structs
|
||||||
|
|
||||||
|
You can store a generic type in a `struct` as well:
|
||||||
|
|
||||||
|
```
|
||||||
|
struct Point<T> {
|
||||||
|
x: T,
|
||||||
|
y: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let int_origin = Point { x: 0, y: 0 };
|
||||||
|
let float_origin = Point { x: 0.0, y: 0.0 };
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like how we had `Option<T>`, we use a similar syntax for `inverse<T>`.
|
Similarly to functions, the `<T>` is where we declare the generic parameters,
|
||||||
We can then use `T` inside the rest of the signature: `x` has type `T`, and half
|
and we then use `x: T` in the type declaration, too.
|
||||||
of the `Result` has type `T`. However, if we try to compile that example, we'll get
|
|
||||||
an error:
|
|
||||||
|
|
||||||
```text
|
|
||||||
error: binary operation `==` cannot be applied to type `T`
|
|
||||||
```
|
|
||||||
|
|
||||||
Because `T` can be _any_ type, it may be a type that doesn't implement `==`,
|
|
||||||
and therefore, the first line would be wrong. What do we do?
|
|
||||||
|
|
||||||
To fix this example, we need to learn about another Rust feature: traits.
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
% Match
|
% Match
|
||||||
|
|
||||||
Often, a simple `if`/`else` isn’t enough, because you have more than two
|
Often, a simple [`if`][if]/`else` isn’t enough, because you have more than two
|
||||||
possible options. Also, `else` conditions can get incredibly complicated, so
|
possible options. Also, conditions can get quite complex. Rust
|
||||||
what’s the solution?
|
has a keyword, `match`, that allows you to replace complicated `if`/`else`
|
||||||
|
|
||||||
Rust has a keyword, `match`, that allows you to replace complicated `if`/`else`
|
|
||||||
groupings with something more powerful. Check it out:
|
groupings with something more powerful. Check it out:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -20,16 +18,18 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`match` takes an expression and then branches based on its value. Each *arm* of
|
[if]: if.html
|
||||||
|
|
||||||
|
`match` takes an expression and then branches based on its value. Each ‘arm’ of
|
||||||
the branch is of the form `val => expression`. When the value matches, that arm’s
|
the branch is of the form `val => expression`. When the value matches, that arm’s
|
||||||
expression will be evaluated. It’s called `match` because of the term ‘pattern
|
expression will be evaluated. It’s called `match` because of the term ‘pattern
|
||||||
matching’, which `match` is an implementation of. There’s an [entire section on
|
matching’, which `match` is an implementation of. There’s an [entire section on
|
||||||
patterns][patterns] coming up next, that covers all the options that fit here.
|
patterns][patterns] that covers all the patterns that are possible here.
|
||||||
|
|
||||||
[patterns]: patterns.html
|
[patterns]: patterns.html
|
||||||
|
|
||||||
So what’s the big advantage here? Well, there are a few. First of all, `match`
|
So what’s the big advantage? Well, there are a few. First of all, `match`
|
||||||
enforces *exhaustiveness checking*. Do you see that last arm, the one with the
|
enforces ‘exhaustiveness checking’. Do you see that last arm, the one with the
|
||||||
underscore (`_`)? If we remove that arm, Rust will give us an error:
|
underscore (`_`)? If we remove that arm, Rust will give us an error:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
@ -37,11 +37,12 @@ error: non-exhaustive patterns: `_` not covered
|
||||||
```
|
```
|
||||||
|
|
||||||
In other words, Rust is trying to tell us we forgot a value. Because `x` is an
|
In other words, Rust is trying to tell us we forgot a value. Because `x` is an
|
||||||
integer, Rust knows that it can have a number of different values – for example,
|
integer, Rust knows that it can have a number of different values – for
|
||||||
`6`. Without the `_`, however, there is no arm that could match, and so Rust refuses
|
example, `6`. Without the `_`, however, there is no arm that could match, and
|
||||||
to compile. `_` acts like a ‘catch-all arm’. If none of the other arms match,
|
so Rust refuses to compile the code. `_` acts like a ‘catch-all arm’. If none
|
||||||
the arm with `_` will, and since we have this catch-all arm, we now have an arm
|
of the other arms match, the arm with `_` will, and since we have this
|
||||||
for every possible value of `x`, and so our program will compile successfully.
|
catch-all arm, we now have an arm for every possible value of `x`, and so our
|
||||||
|
program will compile successfully.
|
||||||
|
|
||||||
`match` is also an expression, which means we can use it on the right-hand
|
`match` is also an expression, which means we can use it on the right-hand
|
||||||
side of a `let` binding or directly where an expression is used:
|
side of a `let` binding or directly where an expression is used:
|
||||||
|
@ -59,4 +60,4 @@ let numer = match x {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Sometimes, it’s a nice way of converting things.
|
Sometimes it’s a nice way of converting something from one type to another.
|
||||||
|
|
|
@ -3,27 +3,26 @@
|
||||||
Functions are great, but if you want to call a bunch of them on some data, it
|
Functions are great, but if you want to call a bunch of them on some data, it
|
||||||
can be awkward. Consider this code:
|
can be awkward. Consider this code:
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
baz(bar(foo)));
|
baz(bar(foo)));
|
||||||
```
|
```
|
||||||
|
|
||||||
We would read this left-to right, and so we see "baz bar foo." But this isn't the
|
We would read this left-to right, and so we see ‘baz bar foo’. But this isn’t the
|
||||||
order that the functions would get called in, that's inside-out: "foo bar baz."
|
order that the functions would get called in, that’s inside-out: ‘foo bar baz’.
|
||||||
Wouldn't it be nice if we could do this instead?
|
Wouldn’t it be nice if we could do this instead?
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
foo.bar().baz();
|
foo.bar().baz();
|
||||||
```
|
```
|
||||||
|
|
||||||
Luckily, as you may have guessed with the leading question, you can! Rust provides
|
Luckily, as you may have guessed with the leading question, you can! Rust provides
|
||||||
the ability to use this *method call syntax* via the `impl` keyword.
|
the ability to use this ‘method call syntax’ via the `impl` keyword.
|
||||||
|
|
||||||
## Method calls
|
## Method calls
|
||||||
|
|
||||||
Here's how it works:
|
Here’s how it works:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
# #![feature(core)]
|
|
||||||
struct Circle {
|
struct Circle {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
|
@ -44,15 +43,23 @@ fn main() {
|
||||||
|
|
||||||
This will print `12.566371`.
|
This will print `12.566371`.
|
||||||
|
|
||||||
We've made a struct that represents a circle. We then write an `impl` block,
|
|
||||||
and inside it, define a method, `area`. Methods take a special first
|
|
||||||
parameter, of which there are three variants: `self`, `&self`, and `&mut self`.
|
We’ve made a struct that represents a circle. We then write an `impl` block,
|
||||||
You can think of this first parameter as being the `foo` in `foo.bar()`. The three
|
and inside it, define a method, `area`.
|
||||||
variants correspond to the three kinds of things `foo` could be: `self` if it's
|
|
||||||
just a value on the stack, `&self` if it's a reference, and `&mut self` if it's
|
Methods take a special first parameter, of which there are three variants:
|
||||||
a mutable reference. We should default to using `&self`, as you should prefer
|
`self`, `&self`, and `&mut self`. You can think of this first parameter as
|
||||||
borrowing over taking ownership, as well as taking immutable references
|
being the `foo` in `foo.bar()`. The three variants correspond to the three
|
||||||
over mutable ones. Here's an example of all three variants:
|
kinds of things `foo` could be: `self` if it’s just a value on the stack,
|
||||||
|
`&self` if it’s a reference, and `&mut self` if it’s a mutable reference.
|
||||||
|
Because we took the `&self` parameter to `area`, we can use it just like any
|
||||||
|
other parameter. Because we know it’s a `Circle`, we can access the `radius`
|
||||||
|
just like we would with any other struct.
|
||||||
|
|
||||||
|
We should default to using `&self`, as you should prefer borrowing over taking
|
||||||
|
ownership, as well as taking immutable references over mutable ones. Here’s an
|
||||||
|
example of all three variants:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
struct Circle {
|
struct Circle {
|
||||||
|
@ -76,20 +83,13 @@ impl Circle {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, as you may remember, the value of the area of a circle is `π*r²`.
|
|
||||||
Because we took the `&self` parameter to `area`, we can use it just like any
|
|
||||||
other parameter. Because we know it's a `Circle`, we can access the `radius`
|
|
||||||
just like we would with any other struct. An import of π and some
|
|
||||||
multiplications later, and we have our area.
|
|
||||||
|
|
||||||
## Chaining method calls
|
## Chaining method calls
|
||||||
|
|
||||||
So, now we know how to call a method, such as `foo.bar()`. But what about our
|
So, now we know how to call a method, such as `foo.bar()`. But what about our
|
||||||
original example, `foo.bar().baz()`? This is called 'method chaining', and we
|
original example, `foo.bar().baz()`? This is called ‘method chaining’, and we
|
||||||
can do it by returning `self`.
|
can do it by returning `self`.
|
||||||
|
|
||||||
```
|
```
|
||||||
# #![feature(core)]
|
|
||||||
struct Circle {
|
struct Circle {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
|
@ -124,13 +124,13 @@ fn grow(&self) -> Circle {
|
||||||
# Circle } }
|
# Circle } }
|
||||||
```
|
```
|
||||||
|
|
||||||
We just say we're returning a `Circle`. With this method, we can grow a new
|
We just say we’re returning a `Circle`. With this method, we can grow a new
|
||||||
circle to any arbitrary size.
|
circle to any arbitrary size.
|
||||||
|
|
||||||
## Static methods
|
## Static methods
|
||||||
|
|
||||||
You can also define methods that do not take a `self` parameter. Here's a
|
You can also define methods that do not take a `self` parameter. Here’s a
|
||||||
pattern that's very common in Rust code:
|
pattern that’s very common in Rust code:
|
||||||
|
|
||||||
```
|
```
|
||||||
struct Circle {
|
struct Circle {
|
||||||
|
@ -154,20 +154,19 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This *static method* builds a new `Circle` for us. Note that static methods
|
This ‘static method’ builds a new `Circle` for us. Note that static methods
|
||||||
are called with the `Struct::method()` syntax, rather than the `ref.method()`
|
are called with the `Struct::method()` syntax, rather than the `ref.method()`
|
||||||
syntax.
|
syntax.
|
||||||
|
|
||||||
## Builder Pattern
|
## Builder Pattern
|
||||||
|
|
||||||
Let's say that we want our users to be able to create Circles, but we will
|
Let’s say that we want our users to be able to create Circles, but we will
|
||||||
allow them to only set the properties they care about. Otherwise, the `x`
|
allow them to only set the properties they care about. Otherwise, the `x`
|
||||||
and `y` attributes will be `0.0`, and the `radius` will be `1.0`. Rust doesn't
|
and `y` attributes will be `0.0`, and the `radius` will be `1.0`. Rust doesn’t
|
||||||
have method overloading, named arguments, or variable arguments. We employ
|
have method overloading, named arguments, or variable arguments. We employ
|
||||||
the builder pattern instead. It looks like this:
|
the builder pattern instead. It looks like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
# #![feature(core)]
|
|
||||||
struct Circle {
|
struct Circle {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
|
@ -224,9 +223,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
What we've done here is make another struct, `CircleBuilder`. We've defined our
|
What we’ve done here is make another struct, `CircleBuilder`. We’ve defined our
|
||||||
builder methods on it. We've also defined our `area()` method on `Circle`. We
|
builder methods on it. We’ve also defined our `area()` method on `Circle`. We
|
||||||
also made one more method on `CircleBuilder`: `finalize()`. This method creates
|
also made one more method on `CircleBuilder`: `finalize()`. This method creates
|
||||||
our final `Circle` from the builder. Now, we've used the type system to enforce
|
our final `Circle` from the builder. Now, we’ve used the type system to enforce
|
||||||
our concerns: we can use the methods on `CircleBuilder` to constrain making
|
our concerns: we can use the methods on `CircleBuilder` to constrain making
|
||||||
`Circle`s in any way we choose.
|
`Circle`s in any way we choose.
|
||||||
|
|
|
@ -21,6 +21,8 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `one`.
|
||||||
|
|
||||||
# Multiple patterns
|
# Multiple patterns
|
||||||
|
|
||||||
You can match multiple patterns with `|`:
|
You can match multiple patterns with `|`:
|
||||||
|
@ -35,6 +37,8 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `one or two`.
|
||||||
|
|
||||||
# Ranges
|
# Ranges
|
||||||
|
|
||||||
You can match a range of values with `...`:
|
You can match a range of values with `...`:
|
||||||
|
@ -48,7 +52,21 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Ranges are mostly used with integers and single characters.
|
This prints `one through five`.
|
||||||
|
|
||||||
|
Ranges are mostly used with integers and `char`s:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let x = '💅';
|
||||||
|
|
||||||
|
match x {
|
||||||
|
'a' ... 'j' => println!("early letter"),
|
||||||
|
'k' ... 'z' => println!("late letter"),
|
||||||
|
_ => println!("something else"),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This prints `something else`
|
||||||
|
|
||||||
# Bindings
|
# Bindings
|
||||||
|
|
||||||
|
@ -64,6 +82,8 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `got a range element 1`.
|
||||||
|
|
||||||
# Ignoring variants
|
# Ignoring variants
|
||||||
|
|
||||||
If you’re matching on an enum which has variants, you can use `..` to
|
If you’re matching on an enum which has variants, you can use `..` to
|
||||||
|
@ -83,6 +103,8 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `Got an int!`.
|
||||||
|
|
||||||
# Guards
|
# Guards
|
||||||
|
|
||||||
You can introduce ‘match guards’ with `if`:
|
You can introduce ‘match guards’ with `if`:
|
||||||
|
@ -102,6 +124,8 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `Got an int!`
|
||||||
|
|
||||||
# ref and ref mut
|
# ref and ref mut
|
||||||
|
|
||||||
If you want to get a [reference][ref], use the `ref` keyword:
|
If you want to get a [reference][ref], use the `ref` keyword:
|
||||||
|
@ -114,6 +138,8 @@ match x {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `Got a reference to 5`.
|
||||||
|
|
||||||
[ref]: references-and-borrowing.html
|
[ref]: references-and-borrowing.html
|
||||||
|
|
||||||
Here, the `r` inside the `match` has the type `&i32`. In other words, the `ref`
|
Here, the `r` inside the `match` has the type `&i32`. In other words, the `ref`
|
||||||
|
@ -130,7 +156,7 @@ match x {
|
||||||
|
|
||||||
# Destructuring
|
# Destructuring
|
||||||
|
|
||||||
If you have a compound data type, like a `struct`, you can destructure it
|
If you have a compound data type, like a [`struct`][struct], you can destructure it
|
||||||
inside of a pattern:
|
inside of a pattern:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -146,6 +172,8 @@ match origin {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[struct]: structs.html
|
||||||
|
|
||||||
If we only care about some of the values, we don’t have to give them all names:
|
If we only care about some of the values, we don’t have to give them all names:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -161,6 +189,8 @@ match origin {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `x is 0`.
|
||||||
|
|
||||||
You can do this kind of match on any member, not just the first:
|
You can do this kind of match on any member, not just the first:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -176,6 +206,8 @@ match origin {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This prints `y is 0`.
|
||||||
|
|
||||||
This ‘destructuring’ behavior works on any compound data type, like
|
This ‘destructuring’ behavior works on any compound data type, like
|
||||||
[tuples][tuples] or [enums][enums].
|
[tuples][tuples] or [enums][enums].
|
||||||
|
|
||||||
|
@ -187,10 +219,10 @@ This ‘destructuring’ behavior works on any compound data type, like
|
||||||
Whew! That’s a lot of different ways to match things, and they can all be
|
Whew! That’s a lot of different ways to match things, and they can all be
|
||||||
mixed and matched, depending on what you’re doing:
|
mixed and matched, depending on what you’re doing:
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
match x {
|
match x {
|
||||||
Foo { x: Some(ref name), y: None } => ...
|
Foo { x: Some(ref name), y: None } => ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Patterns are very powerful. Make good use of them.
|
Patterns are very powerful. Make good use of them.
|
||||||
|
|
|
@ -1,36 +1,34 @@
|
||||||
% Strings
|
% Strings
|
||||||
|
|
||||||
Strings are an important concept for any programmer to master. Rust's string
|
Strings are an important concept for any programmer to master. Rust’s string
|
||||||
handling system is a bit different from other languages, due to its systems
|
handling system is a bit different from other languages, due to its systems
|
||||||
focus. Any time you have a data structure of variable size, things can get
|
focus. Any time you have a data structure of variable size, things can get
|
||||||
tricky, and strings are a re-sizable data structure. That being said, Rust's
|
tricky, and strings are a re-sizable data structure. That being said, Rust’s
|
||||||
strings also work differently than in some other systems languages, such as C.
|
strings also work differently than in some other systems languages, such as C.
|
||||||
|
|
||||||
Let's dig into the details. A *string* is a sequence of Unicode scalar values
|
Let’s dig into the details. A ‘string’ is a sequence of Unicode scalar values
|
||||||
encoded as a stream of UTF-8 bytes. All strings are guaranteed to be
|
encoded as a stream of UTF-8 bytes. All strings are guaranteed to be a valid
|
||||||
validly encoded UTF-8 sequences. Additionally, strings are not null-terminated
|
encoding of UTF-8 sequences. Additionally, unlike some systems languages,
|
||||||
and can contain null bytes.
|
strings are not null-terminated and can contain null bytes.
|
||||||
|
|
||||||
Rust has two main types of strings: `&str` and `String`.
|
Rust has two main types of strings: `&str` and `String`. Let’s talk about
|
||||||
|
`&str` first. These are called ‘string slices’. String literals are of the type
|
||||||
|
`&'static str`:
|
||||||
|
|
||||||
The first kind is a `&str`. These are called *string slices*. String literals
|
```rust
|
||||||
are of the type `&str`:
|
let string = "Hello there."; // string: &'static str
|
||||||
|
|
||||||
```{rust}
|
|
||||||
let string = "Hello there."; // string: &str
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This string is statically allocated, meaning that it's saved inside our
|
This string is statically allocated, meaning that it’s saved inside our
|
||||||
compiled program, and exists for the entire duration it runs. The `string`
|
compiled program, and exists for the entire duration it runs. The `string`
|
||||||
binding is a reference to this statically allocated string. String slices
|
binding is a reference to this statically allocated string. String slices
|
||||||
have a fixed size, and cannot be mutated.
|
have a fixed size, and cannot be mutated.
|
||||||
|
|
||||||
A `String`, on the other hand, is a heap-allocated string. This string
|
A `String`, on the other hand, is a heap-allocated string. This string is
|
||||||
is growable, and is also guaranteed to be UTF-8. `String`s are
|
growable, and is also guaranteed to be UTF-8. `String`s are commonly created by
|
||||||
commonly created by converting from a string slice using the
|
converting from a string slice using the `to_string` method.
|
||||||
`to_string` method.
|
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
let mut s = "Hello".to_string(); // mut s: String
|
let mut s = "Hello".to_string(); // mut s: String
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
|
|
||||||
|
@ -54,8 +52,78 @@ fn main() {
|
||||||
Viewing a `String` as a `&str` is cheap, but converting the `&str` to a
|
Viewing a `String` as a `&str` is cheap, but converting the `&str` to a
|
||||||
`String` involves allocating memory. No reason to do that unless you have to!
|
`String` involves allocating memory. No reason to do that unless you have to!
|
||||||
|
|
||||||
That's the basics of strings in Rust! They're probably a bit more complicated
|
## Indexing
|
||||||
than you are used to, if you come from a scripting language, but when the
|
|
||||||
low-level details matter, they really matter. Just remember that `String`s
|
Because strings are valid UTF-8, strings do not support indexing:
|
||||||
allocate memory and control their data, while `&str`s are a reference to
|
|
||||||
another string, and you'll be all set.
|
```rust,ignore
|
||||||
|
let s = "hello";
|
||||||
|
|
||||||
|
println!("The first letter of s is {}", s[0]); // ERROR!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
Usually, access to a vector with `[]` is very fast. But, because each character
|
||||||
|
in a UTF-8 encoded string can be multiple bytes, you have to walk over the
|
||||||
|
string to find the nᵗʰ letter of a string. This is a significantly more
|
||||||
|
expensive operation, and we don’t want to be misleading. Furthermore, ‘letter’
|
||||||
|
isn’t something defined in Unicode, exactly. We can choose to look at a string as
|
||||||
|
individual bytes, or as codepoints:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let hachiko = "忠犬ハチ公";
|
||||||
|
|
||||||
|
for b in hachiko.as_bytes() {
|
||||||
|
print!("{}, ", b);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
|
||||||
|
for c in hachiko.chars() {
|
||||||
|
print!("{}, ", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
```
|
||||||
|
|
||||||
|
This prints:
|
||||||
|
|
||||||
|
```text
|
||||||
|
229, 191, 160, 231, 138, 172, 227, 131, 143, 227, 131, 129, 229, 133, 172,
|
||||||
|
忠, 犬, ハ, チ, 公,
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, there are more bytes than `char`s.
|
||||||
|
|
||||||
|
You can get something similar to an index like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# let hachiko = "忠犬ハチ公";
|
||||||
|
let dog = hachiko.chars().nth(1); // kinda like hachiko[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
This emphasizes that we have to go through the whole list of `chars`.
|
||||||
|
|
||||||
|
## Concatenation
|
||||||
|
|
||||||
|
If you have a `String`, you can concatenate a `&str` to the end of it:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let hello = "Hello ".to_string();
|
||||||
|
let world = "world!";
|
||||||
|
|
||||||
|
let hello_world = hello + world;
|
||||||
|
```
|
||||||
|
|
||||||
|
But if you have two `String`s, you need an `&`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let hello = "Hello ".to_string();
|
||||||
|
let world = "world!".to_string();
|
||||||
|
|
||||||
|
let hello_world = hello + &world;
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because `&String` can automatically coerece to a `&str`. This is a
|
||||||
|
feature called ‘[`Deref` coercions][dc]’.
|
||||||
|
|
||||||
|
[dc]: deref-coercions.html
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
% Structs
|
% Structs
|
||||||
|
|
||||||
Structs are a way of creating more complex datatypes. For example, if we were
|
Structs are a way of creating more complex data types. For example, if we were
|
||||||
doing calculations involving coordinates in 2D space, we would need both an `x`
|
doing calculations involving coordinates in 2D space, we would need both an `x`
|
||||||
and a `y` value:
|
and a `y` value:
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
There’s a lot going on here, so let’s break it down. We declare a struct with
|
There’s a lot going on here, so let’s break it down. We declare a `struct` with
|
||||||
the `struct` keyword, and then with a name. By convention, structs begin with a
|
the `struct` keyword, and then with a name. By convention, `struct`s begin with
|
||||||
capital letter and are also camel cased: `PointInSpace`, not `Point_In_Space`.
|
a capital letter and are camel cased: `PointInSpace`, not `Point_In_Space`.
|
||||||
|
|
||||||
We can create an instance of our struct via `let`, as usual, but we use a `key:
|
We can create an instance of our struct via `let`, as usual, but we use a `key:
|
||||||
value` style syntax to set each field. The order doesn't need to be the same as
|
value` style syntax to set each field. The order doesn’t need to be the same as
|
||||||
in the original declaration.
|
in the original declaration.
|
||||||
|
|
||||||
Finally, because fields have names, we can access the field through dot
|
Finally, because fields have names, we can access the field through dot
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
% Traits
|
% Traits
|
||||||
|
|
||||||
Do you remember the `impl` keyword, used to call a function with method
|
Do you remember the `impl` keyword, used to call a function with [method
|
||||||
syntax?
|
syntax][methodsyntax]?
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
# #![feature(core)]
|
|
||||||
struct Circle {
|
struct Circle {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
|
@ -18,11 +17,12 @@ impl Circle {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[methodsyntax]: method-syntax.html
|
||||||
|
|
||||||
Traits are similar, except that we define a trait with just the method
|
Traits are similar, except that we define a trait with just the method
|
||||||
signature, then implement the trait for that struct. Like this:
|
signature, then implement the trait for that struct. Like this:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
# #![feature(core)]
|
|
||||||
struct Circle {
|
struct Circle {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
|
@ -41,20 +41,13 @@ impl HasArea for Circle {
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, the `trait` block looks very similar to the `impl` block,
|
As you can see, the `trait` block looks very similar to the `impl` block,
|
||||||
but we don't define a body, just a type signature. When we `impl` a trait,
|
but we don’t define a body, just a type signature. When we `impl` a trait,
|
||||||
we use `impl Trait for Item`, rather than just `impl Item`.
|
we use `impl Trait for Item`, rather than just `impl Item`.
|
||||||
|
|
||||||
So what's the big deal? Remember the error we were getting with our generic
|
|
||||||
`inverse` function?
|
|
||||||
|
|
||||||
```text
|
|
||||||
error: binary operation `==` cannot be applied to type `T`
|
|
||||||
```
|
|
||||||
|
|
||||||
We can use traits to constrain our generics. Consider this function, which
|
We can use traits to constrain our generics. Consider this function, which
|
||||||
does not compile, and gives us a similar error:
|
does not compile, and gives us a similar error:
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
fn print_area<T>(shape: T) {
|
fn print_area<T>(shape: T) {
|
||||||
println!("This shape has an area of {}", shape.area());
|
println!("This shape has an area of {}", shape.area());
|
||||||
}
|
}
|
||||||
|
@ -66,11 +59,11 @@ Rust complains:
|
||||||
error: type `T` does not implement any method in scope named `area`
|
error: type `T` does not implement any method in scope named `area`
|
||||||
```
|
```
|
||||||
|
|
||||||
Because `T` can be any type, we can't be sure that it implements the `area`
|
Because `T` can be any type, we can’t be sure that it implements the `area`
|
||||||
method. But we can add a *trait constraint* to our generic `T`, ensuring
|
method. But we can add a ‘trait constraint’ to our generic `T`, ensuring
|
||||||
that it does:
|
that it does:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
# trait HasArea {
|
# trait HasArea {
|
||||||
# fn area(&self) -> f64;
|
# fn area(&self) -> f64;
|
||||||
# }
|
# }
|
||||||
|
@ -83,10 +76,9 @@ The syntax `<T: HasArea>` means `any type that implements the HasArea trait`.
|
||||||
Because traits define function type signatures, we can be sure that any type
|
Because traits define function type signatures, we can be sure that any type
|
||||||
which implements `HasArea` will have an `.area()` method.
|
which implements `HasArea` will have an `.area()` method.
|
||||||
|
|
||||||
Here's an extended example of how this works:
|
Here’s an extended example of how this works:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
# #![feature(core)]
|
|
||||||
trait HasArea {
|
trait HasArea {
|
||||||
fn area(&self) -> f64;
|
fn area(&self) -> f64;
|
||||||
}
|
}
|
||||||
|
@ -144,10 +136,10 @@ This shape has an area of 3.141593
|
||||||
This shape has an area of 1
|
This shape has an area of 1
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, `print_area` is now generic, but also ensures that we
|
As you can see, `print_area` is now generic, but also ensures that we have
|
||||||
have passed in the correct types. If we pass in an incorrect type:
|
passed in the correct types. If we pass in an incorrect type:
|
||||||
|
|
||||||
```{rust,ignore}
|
```rust,ignore
|
||||||
print_area(5);
|
print_area(5);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -157,11 +149,11 @@ We get a compile-time error:
|
||||||
error: failed to find an implementation of trait main::HasArea for int
|
error: failed to find an implementation of trait main::HasArea for int
|
||||||
```
|
```
|
||||||
|
|
||||||
So far, we've only added trait implementations to structs, but you can
|
So far, we’ve only added trait implementations to structs, but you can
|
||||||
implement a trait for any type. So technically, we _could_ implement
|
implement a trait for any type. So technically, we _could_ implement `HasArea`
|
||||||
`HasArea` for `i32`:
|
for `i32`:
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
trait HasArea {
|
trait HasArea {
|
||||||
fn area(&self) -> f64;
|
fn area(&self) -> f64;
|
||||||
}
|
}
|
||||||
|
@ -181,102 +173,57 @@ It is considered poor style to implement methods on such primitive types, even
|
||||||
though it is possible.
|
though it is possible.
|
||||||
|
|
||||||
This may seem like the Wild West, but there are two other restrictions around
|
This may seem like the Wild West, but there are two other restrictions around
|
||||||
implementing traits that prevent this from getting out of hand. First, traits
|
implementing traits that prevent this from getting out of hand. The first is
|
||||||
must be `use`d in any scope where you wish to use the trait's method. So for
|
that if the trait isn’t defined in your scope, it doesn’t apply. Here’s an
|
||||||
example, this does not work:
|
example: the standard library provides a [`Write`][write] trait which adds
|
||||||
|
extra functionality to `File`s, for doing file I/O. By default, a `File`
|
||||||
|
won’t have its methods:
|
||||||
|
|
||||||
```{rust,ignore}
|
[write]: ../std/io/trait.Write.html
|
||||||
mod shapes {
|
|
||||||
use std::f64::consts;
|
|
||||||
|
|
||||||
trait HasArea {
|
```rust,ignore
|
||||||
fn area(&self) -> f64;
|
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
|
||||||
}
|
let result = f.write("whatever".as_bytes());
|
||||||
|
# result.unwrap(); // ignore the erorr
|
||||||
struct Circle {
|
|
||||||
x: f64,
|
|
||||||
y: f64,
|
|
||||||
radius: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasArea for Circle {
|
|
||||||
fn area(&self) -> f64 {
|
|
||||||
consts::PI * (self.radius * self.radius)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let c = shapes::Circle {
|
|
||||||
x: 0.0f64,
|
|
||||||
y: 0.0f64,
|
|
||||||
radius: 1.0f64,
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{}", c.area());
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now that we've moved the structs and traits into their own module, we get an
|
Here’s the error:
|
||||||
error:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
error: type `shapes::Circle` does not implement any method in scope named `area`
|
error: type `std::fs::File` does not implement any method in scope named `write`
|
||||||
|
|
||||||
|
let result = f.write(b”whatever”);
|
||||||
|
^~~~~~~~~~~~~~~~~~
|
||||||
```
|
```
|
||||||
|
|
||||||
If we add a `use` line right above `main` and make the right things public,
|
We need to `use` the `Write` trait first:
|
||||||
everything is fine:
|
|
||||||
|
|
||||||
```{rust}
|
```rust,ignore
|
||||||
# #![feature(core)]
|
use std::io::Write;
|
||||||
mod shapes {
|
|
||||||
use std::f64::consts;
|
|
||||||
|
|
||||||
pub trait HasArea {
|
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
|
||||||
fn area(&self) -> f64;
|
let result = f.write("whatever".as_bytes());
|
||||||
}
|
# result.unwrap(); // ignore the erorr
|
||||||
|
|
||||||
pub struct Circle {
|
|
||||||
pub x: f64,
|
|
||||||
pub y: f64,
|
|
||||||
pub radius: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasArea for Circle {
|
|
||||||
fn area(&self) -> f64 {
|
|
||||||
consts::PI * (self.radius * self.radius)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use shapes::HasArea;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let c = shapes::Circle {
|
|
||||||
x: 0.0f64,
|
|
||||||
y: 0.0f64,
|
|
||||||
radius: 1.0f64,
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{}", c.area());
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will compile without error.
|
||||||
|
|
||||||
This means that even if someone does something bad like add methods to `int`,
|
This means that even if someone does something bad like add methods to `int`,
|
||||||
it won't affect you, unless you `use` that trait.
|
it won’t affect you, unless you `use` that trait.
|
||||||
|
|
||||||
There's one more restriction on implementing traits. Either the trait or the
|
There’s one more restriction on implementing traits. Either the trait or the
|
||||||
type you're writing the `impl` for must be inside your crate. So, we could
|
type you’re writing the `impl` for must be defined by you. So, we could
|
||||||
implement the `HasArea` type for `i32`, because `HasArea` is in our crate. But
|
implement the `HasArea` type for `i32`, because `HasArea` is in our code. But
|
||||||
if we tried to implement `Float`, a trait provided by Rust, for `i32`, we could
|
if we tried to implement `Float`, a trait provided by Rust, for `i32`, we could
|
||||||
not, because both the trait and the type aren't in our crate.
|
not, because neither the trait nor the type are in our code.
|
||||||
|
|
||||||
One last thing about traits: generic functions with a trait bound use
|
One last thing about traits: generic functions with a trait bound use
|
||||||
*monomorphization* (*mono*: one, *morph*: form), so they are statically
|
‘monomorphization’ (mono: one, morph: form), so they are statically dispatched.
|
||||||
dispatched. What's that mean? Check out the chapter on [trait
|
What’s that mean? Check out the chapter on [trait objects][to] for more details.
|
||||||
objects](trait-objects.html) for more.
|
|
||||||
|
|
||||||
## Multiple trait bounds
|
[to]: trait-objects.html
|
||||||
|
|
||||||
|
# Multiple trait bounds
|
||||||
|
|
||||||
You’ve seen that you can bound a generic type parameter with a trait:
|
You’ve seen that you can bound a generic type parameter with a trait:
|
||||||
|
|
||||||
|
@ -299,10 +246,10 @@ fn foo<T: Clone + Debug>(x: T) {
|
||||||
|
|
||||||
`T` now needs to be both `Clone` as well as `Debug`.
|
`T` now needs to be both `Clone` as well as `Debug`.
|
||||||
|
|
||||||
## Where clause
|
# Where clause
|
||||||
|
|
||||||
Writing functions with only a few generic types and a small number of trait
|
Writing functions with only a few generic types and a small number of trait
|
||||||
bounds isn't too bad, but as the number increases, the syntax gets increasingly
|
bounds isn’t too bad, but as the number increases, the syntax gets increasingly
|
||||||
awkward:
|
awkward:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -318,7 +265,7 @@ fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
|
||||||
The name of the function is on the far left, and the parameter list is on the
|
The name of the function is on the far left, and the parameter list is on the
|
||||||
far right. The bounds are getting in the way.
|
far right. The bounds are getting in the way.
|
||||||
|
|
||||||
Rust has a solution, and it's called a '`where` clause':
|
Rust has a solution, and it’s called a ‘`where` clause’:
|
||||||
|
|
||||||
```
|
```
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -389,84 +336,9 @@ This shows off the additional feature of `where` clauses: they allow bounds
|
||||||
where the left-hand side is an arbitrary type (`i32` in this case), not just a
|
where the left-hand side is an arbitrary type (`i32` in this case), not just a
|
||||||
plain type parameter (like `T`).
|
plain type parameter (like `T`).
|
||||||
|
|
||||||
## Our `inverse` Example
|
# Default methods
|
||||||
|
|
||||||
Back in [Generics](generics.html), we were trying to write code like this:
|
There’s one last feature of traits we should cover: default methods. It’s
|
||||||
|
|
||||||
```{rust,ignore}
|
|
||||||
fn inverse<T>(x: T) -> Result<T, String> {
|
|
||||||
if x == 0.0 { return Err("x cannot be zero!".to_string()); }
|
|
||||||
|
|
||||||
Ok(1.0 / x)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If we try to compile it, we get this error:
|
|
||||||
|
|
||||||
```text
|
|
||||||
error: binary operation `==` cannot be applied to type `T`
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because `T` is too generic: we don't know if a random `T` can be
|
|
||||||
compared. For that, we can use trait bounds. It doesn't quite work, but try
|
|
||||||
this:
|
|
||||||
|
|
||||||
```{rust,ignore}
|
|
||||||
fn inverse<T: PartialEq>(x: T) -> Result<T, String> {
|
|
||||||
if x == 0.0 { return Err("x cannot be zero!".to_string()); }
|
|
||||||
|
|
||||||
Ok(1.0 / x)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You should get this error:
|
|
||||||
|
|
||||||
```text
|
|
||||||
error: mismatched types:
|
|
||||||
expected `T`,
|
|
||||||
found `_`
|
|
||||||
(expected type parameter,
|
|
||||||
found floating-point variable)
|
|
||||||
```
|
|
||||||
|
|
||||||
So this won't work. While our `T` is `PartialEq`, we expected to have another `T`,
|
|
||||||
but instead, we found a floating-point variable. We need a different bound. `Float`
|
|
||||||
to the rescue:
|
|
||||||
|
|
||||||
```
|
|
||||||
# #![feature(std_misc)]
|
|
||||||
use std::num::Float;
|
|
||||||
|
|
||||||
fn inverse<T: Float>(x: T) -> Result<T, String> {
|
|
||||||
if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
|
|
||||||
|
|
||||||
let one: T = Float::one();
|
|
||||||
Ok(one / x)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We've had to replace our generic `0.0` and `1.0` with the appropriate methods
|
|
||||||
from the `Float` trait. Both `f32` and `f64` implement `Float`, so our function
|
|
||||||
works just fine:
|
|
||||||
|
|
||||||
```
|
|
||||||
# #![feature(std_misc)]
|
|
||||||
# use std::num::Float;
|
|
||||||
# fn inverse<T: Float>(x: T) -> Result<T, String> {
|
|
||||||
# if x == Float::zero() { return Err("x cannot be zero!".to_string()) }
|
|
||||||
# let one: T = Float::one();
|
|
||||||
# Ok(one / x)
|
|
||||||
# }
|
|
||||||
println!("the inverse of {} is {:?}", 2.0f32, inverse(2.0f32));
|
|
||||||
println!("the inverse of {} is {:?}", 2.0f64, inverse(2.0f64));
|
|
||||||
|
|
||||||
println!("the inverse of {} is {:?}", 0.0f32, inverse(0.0f32));
|
|
||||||
println!("the inverse of {} is {:?}", 0.0f64, inverse(0.0f64));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Default methods
|
|
||||||
|
|
||||||
There's one last feature of traits we should cover: default methods. It's
|
|
||||||
easiest just to show an example:
|
easiest just to show an example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -477,8 +349,8 @@ trait Foo {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Implementors of the `Foo` trait need to implement `bar()`, but they don't
|
Implementors of the `Foo` trait need to implement `bar()`, but they don’t
|
||||||
need to implement `baz()`. They'll get this default behavior. They can
|
need to implement `baz()`. They’ll get this default behavior. They can
|
||||||
override the default if they so choose:
|
override the default if they so choose:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -506,3 +378,43 @@ default.baz(); // prints "We called baz."
|
||||||
let over = OverrideDefault;
|
let over = OverrideDefault;
|
||||||
over.baz(); // prints "Override baz!"
|
over.baz(); // prints "Override baz!"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Inheritance
|
||||||
|
|
||||||
|
Sometimes, implementing a trait requires implementing another trait:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
trait Foo {
|
||||||
|
fn foo(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
trait FooBar : Foo {
|
||||||
|
fn foobar(&self);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementors of `FooBar` must also implement `Foo`, like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# trait Foo {
|
||||||
|
# fn foo(&self);
|
||||||
|
# }
|
||||||
|
# trait FooBar : Foo {
|
||||||
|
# fn foobar(&self);
|
||||||
|
# }
|
||||||
|
struct Baz;
|
||||||
|
|
||||||
|
impl Foo for Baz {
|
||||||
|
fn foo(&self) { println!("foo"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FooBar for Baz {
|
||||||
|
fn foobar(&self) { println!("foobar"); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If we forget to implement `Foo`, Rust will tell us:
|
||||||
|
|
||||||
|
```text
|
||||||
|
error: the trait `main::Foo` is not implemented for the type `main::Baz` [E0277]
|
||||||
|
```
|
||||||
|
|
|
@ -1,37 +1,58 @@
|
||||||
% Vectors
|
% Vectors
|
||||||
|
|
||||||
A *vector* is a dynamic or "growable" array, implemented as the standard
|
A ‘vector’ is a dynamic or ‘growable’ array, implemented as the standard
|
||||||
library type [`Vec<T>`](../std/vec/) (Where `<T>` is a [Generic](./generics.md)
|
library type [`Vec<T>`][vec]. That `<T>` is a [generic][generic], meaning we
|
||||||
statement). Vectors always allocate their data on the heap. Vectors are to
|
can have vectors of any type. Vectors always allocate their data on the heap.
|
||||||
[slices][slices] what [`String`][string] is to `&str`. You can
|
You can create them with the `vec!` macro:
|
||||||
create them with the `vec!` macro:
|
|
||||||
|
|
||||||
```{rust}
|
```rust
|
||||||
let v = vec![1, 2, 3]; // v: Vec<i32>
|
let v = vec![1, 2, 3, 4, 5]; // v: Vec<i32>
|
||||||
```
|
```
|
||||||
|
|
||||||
[slices]: primitive-types.html#slices
|
(Notice that unlike the `println!` macro we’ve used in the past, we use square
|
||||||
[string]: strings.html
|
brackets `[]` with `vec!` macro. Rust allows you to use either in either situation,
|
||||||
|
|
||||||
(Notice that unlike the `println!` macro we've used in the past, we use square
|
|
||||||
brackets `[]` with `vec!`. Rust allows you to use either in either situation,
|
|
||||||
this is just convention.)
|
this is just convention.)
|
||||||
|
|
||||||
There's an alternate form of `vec!` for repeating an initial value:
|
There’s an alternate form of `vec!` for repeating an initial value:
|
||||||
|
|
||||||
```
|
```
|
||||||
let v = vec![0; 10]; // ten zeroes
|
let v = vec![0; 10]; // ten zeroes
|
||||||
```
|
```
|
||||||
|
|
||||||
You can get the length of, iterate over, and subscript vectors just like
|
## Accessing elements
|
||||||
arrays. In addition, (mutable) vectors can grow automatically:
|
|
||||||
|
|
||||||
```{rust}
|
To get the value at a particular index in the vector, we use `[]`s:
|
||||||
let mut nums = vec![1, 2, 3]; // mut nums: Vec<i32>
|
|
||||||
|
|
||||||
nums.push(4);
|
```rust
|
||||||
|
let v = vec![1, 2, 3, 4, 5];
|
||||||
|
|
||||||
println!("The length of nums is now {}", nums.len()); // Prints 4
|
println!("The third element of v is {}", v[2]);
|
||||||
```
|
```
|
||||||
|
|
||||||
Vectors have many more useful methods.
|
The indices count from `0`, so the third element is `v[2]`.
|
||||||
|
|
||||||
|
## Iterating
|
||||||
|
|
||||||
|
Once you have a vector, you can iterate through its elements with `for`. There
|
||||||
|
are three versions:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
for i in &v {
|
||||||
|
println!("A reference to {}", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in &mut v {
|
||||||
|
println!("A mutable reference to {}", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in v {
|
||||||
|
println!("Take ownership of the vector and its element {}", i);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Vectors have many more useful methods, which you can read about in [their
|
||||||
|
API documentation][vec].
|
||||||
|
|
||||||
|
[vec]: ../std/vec/index.html
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue