auto merge of #15422 : steveklabnik/rust/guide_compound_data_types, r=brson
I'm not happy about the hand-waving around `cmp`, but I'm not sure how to get around it.
This commit is contained in:
commit
ec3efa8055
276
src/doc/guide.md
276
src/doc/guide.md
@ -991,11 +991,281 @@ generally, you want to export documentation for a full module.
|
||||
|
||||
## Compound Data Types
|
||||
|
||||
Tuples
|
||||
Rust, like many programming languages, has a number of different data types
|
||||
that are built-in. You've already done some simple work with integers and
|
||||
strings, but next, let's talk about some more complicated ways of storing data.
|
||||
|
||||
Structs
|
||||
### Tuples
|
||||
|
||||
Enums
|
||||
The first compound data type we're going to talk about are called **tuple**s.
|
||||
Tuples are an ordered list of a fixed size. Like this:
|
||||
|
||||
```rust
|
||||
let x = (1i, "hello");
|
||||
```
|
||||
|
||||
The parenthesis and commas form this two-length tuple. Here's the same code, but
|
||||
with the type annotated:
|
||||
|
||||
```rust
|
||||
let x: (int, &str) = (1, "hello");
|
||||
```
|
||||
|
||||
As you can see, the type of a tuple looks just like the tuple, but with each
|
||||
position having a type name rather than the value. Careful readers will also
|
||||
note that tuples are heterogeneous: we have an `int` and a `&str` in this tuple.
|
||||
You haven't seen `&str` as a type before, and we'll discuss the details of
|
||||
strings later. In systems programming languages, strings are a bit more complex
|
||||
than in other languages. For now, just read `&str` as "a string slice," and
|
||||
we'll learn more soon.
|
||||
|
||||
You can access the fields in a tuple through a **destructuring let**. Here's
|
||||
an example:
|
||||
|
||||
```rust
|
||||
let (x, y, z) = (1i, 2i, 3i);
|
||||
|
||||
println!("x is {}", x);
|
||||
```
|
||||
|
||||
Remember before when I said the left hand side of a `let` statement was more
|
||||
powerful than just assigning a binding? Here we are. We can put a pattern on
|
||||
the left hand side of the `let`, and if it matches up to the right hand side,
|
||||
we can assign multiple bindings at once. In this case, `let` 'destructures,'
|
||||
or 'breaks up,' the tuple, and assigns the bits to three bindings.
|
||||
|
||||
This pattern is very powerful, and we'll see it repeated more later.
|
||||
|
||||
The last thing to say about tuples is that they are only equivalent if
|
||||
the arity, types, and values are all identical.
|
||||
|
||||
```rust
|
||||
let x = (1i, 2i, 3i);
|
||||
let y = (2i, 3i, 4i);
|
||||
|
||||
if x == y {
|
||||
println!("yes");
|
||||
} else {
|
||||
println!("no");
|
||||
}
|
||||
```
|
||||
|
||||
This will print `no`, as the values aren't equal.
|
||||
|
||||
One other use of tuples is to return multiple values from a function:
|
||||
|
||||
```rust
|
||||
fn next_two(x: int) -> (int, int) { (x + 1i, x + 2i) }
|
||||
|
||||
fn main() {
|
||||
let (x, y) = next_two(5i);
|
||||
println!("x, y = {}, {}", x, y);
|
||||
}
|
||||
```
|
||||
|
||||
Even though Rust functions can only return one value, a tuple _is_ one value,
|
||||
that happens to be made up of two. You can also see in this example how you
|
||||
can destructure a pattern returned by a function, as well.
|
||||
|
||||
Tuples are a very simple data structure, and so are not often what you want.
|
||||
Let's move on to their bigger sibling, structs.
|
||||
|
||||
### Structs
|
||||
|
||||
A struct is another form of a 'record type,' just like a tuple. There's a
|
||||
difference: structs give each element that they contain a name, called a
|
||||
'field' or a 'member.' Check it out:
|
||||
|
||||
```rust
|
||||
struct Point {
|
||||
x: int,
|
||||
y: int,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let origin = Point { x: 0i, y: 0i };
|
||||
|
||||
println!("The origin is at ({}, {})", origin.x, origin.y);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
capital letter and are also camel cased: `PointInSpace`, not `Point_In_Space`.
|
||||
|
||||
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
|
||||
in the original declaration.
|
||||
|
||||
Finally, because fields have names, we can access the field through dot
|
||||
notation: `origin.x`.
|
||||
|
||||
The values in structs are immutable, like other bindings in Rust. However, you
|
||||
can use `mut` to make them mutable:
|
||||
|
||||
```rust
|
||||
struct Point {
|
||||
x: int,
|
||||
y: int,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut point = Point { x: 0i, y: 0i };
|
||||
|
||||
point.x = 5;
|
||||
|
||||
println!("The point is at ({}, {})", point.x, point.y);
|
||||
}
|
||||
```
|
||||
|
||||
This will print `The point is at (5, 0)`.
|
||||
|
||||
### Tuple Structs and Newtypes
|
||||
|
||||
Rust has another data type that's like a hybrid between a tuple and a struct,
|
||||
called a **tuple struct**. Tuple structs do have a name, but their fields
|
||||
don't:
|
||||
|
||||
|
||||
```
|
||||
struct Color(int, int, int);
|
||||
struct Point(int, int, int);
|
||||
```
|
||||
|
||||
These two will not be equal, even if they have the same values:
|
||||
|
||||
```{rust,ignore}
|
||||
let black = Color(0, 0, 0);
|
||||
let origin = Point(0, 0, 0);
|
||||
```
|
||||
|
||||
It is almost always better to use a struct than a tuple struct. We would write
|
||||
`Color` and `Point` like this instead:
|
||||
|
||||
```rust
|
||||
struct Color {
|
||||
red: int,
|
||||
blue: int,
|
||||
green: int,
|
||||
}
|
||||
|
||||
struct Point {
|
||||
x: int,
|
||||
y: int,
|
||||
z: int,
|
||||
}
|
||||
```
|
||||
|
||||
Now, we have actual names, rather than positions. Good names are important,
|
||||
and with a struct, we have actual names.
|
||||
|
||||
There _is_ one case when a tuple struct is very useful, though, and that's a
|
||||
tuple struct with only one element. We call this a 'newtype,' because it lets
|
||||
you create a new type that's a synonym for another one:
|
||||
|
||||
```
|
||||
struct Inches(int);
|
||||
struct Centimeters(int);
|
||||
|
||||
let length = Inches(10);
|
||||
|
||||
let Inches(integer_length) = length;
|
||||
println!("length is {} inches", integer_length);
|
||||
```
|
||||
|
||||
As you can see here, you can extract the inner integer type through a
|
||||
destructuring `let`.
|
||||
|
||||
### Enums
|
||||
|
||||
Finally, Rust has a "sum type", an **enum**. Enums are an incredibly useful
|
||||
feature of Rust, and are used throughout the standard library. Enums look
|
||||
like this:
|
||||
|
||||
```
|
||||
enum Ordering {
|
||||
Less,
|
||||
Equal,
|
||||
Greater,
|
||||
}
|
||||
```
|
||||
|
||||
This is an enum that is provided by the Rust standard library. An `Ordering`
|
||||
can only be _one_ of `Less`, `Equal`, or `Greater` at any given time. Here's
|
||||
an example:
|
||||
|
||||
```rust
|
||||
let x = 5i;
|
||||
let y = 10i;
|
||||
|
||||
let ordering = x.cmp(&y);
|
||||
|
||||
if ordering == Less {
|
||||
println!("less");
|
||||
} else if ordering == Greater {
|
||||
println!("greater");
|
||||
} else if ordering == Equal {
|
||||
println!("equal");
|
||||
}
|
||||
```
|
||||
|
||||
`cmp` is a function that compares two things, and returns an `Ordering`. The
|
||||
call looks a little bit strange: rather than `cmp(x, y)`, we say `x.cmp(&y)`.
|
||||
We haven't covered methods and references yet, so it should look a little bit
|
||||
foreign. Right now, just pretend it says `cmp(x, y)`, and we'll get to those
|
||||
details soon.
|
||||
|
||||
The `ordering` variable has the type `Ordering`, and so contains one of the
|
||||
three values. We can then do a bunch of `if`/`else` comparisons to check
|
||||
which one it is.
|
||||
|
||||
However, repeated `if`/`else` comparisons get quite tedious. Rust has a feature
|
||||
that not only makes them nicer to read, but also makes sure that you never
|
||||
miss a case. Before we get to that, though, let's talk about another kind of
|
||||
enum: one with values.
|
||||
|
||||
This enum has two variants, one of which has a value.:
|
||||
|
||||
```
|
||||
enum OptionalInt {
|
||||
Value(int),
|
||||
Missing
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = Value(5);
|
||||
let y = Missing;
|
||||
|
||||
match x {
|
||||
Value(n) => println!("x is {:d}", n),
|
||||
Missing => println!("x is missing!"),
|
||||
}
|
||||
|
||||
match y {
|
||||
Value(n) => println!("y is {:d}", n),
|
||||
Missing => println!("y is missing!"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This enum represents an `int` that we may or may not have. In the `Missing`
|
||||
case, we have no value, but in the `Value` case, we do. This enum is specific
|
||||
to `int`s, though. We can make it usable by any type, but we haven't quite
|
||||
gotten there yet!
|
||||
|
||||
You can have any number of values in an enum:
|
||||
|
||||
```
|
||||
enum OptionalColor {
|
||||
Color(int, int, int),
|
||||
Missing
|
||||
}
|
||||
```
|
||||
|
||||
Enums with values are quite useful, but as I mentioned, they're even more
|
||||
useful when they're generic across types. But before we get to generics, let's
|
||||
talk about how to fix this big `if`/`else` statements we've been writing. We'll
|
||||
do that with `match`.
|
||||
|
||||
## Match
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user