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:
bors 2014-07-09 02:21:37 +00:00
commit ec3efa8055

View File

@ -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