doc: Update the tutorial
This commit is contained in:
parent
329281ebcc
commit
9eaaceb9f0
1 changed files with 184 additions and 445 deletions
629
doc/tutorial.md
629
doc/tutorial.md
|
@ -69,61 +69,6 @@ C. As will become clear in the rest of this tutorial, it goes in quite
|
|||
a different direction, with efficient, strongly-typed and memory-safe
|
||||
support for many high-level idioms.
|
||||
|
||||
Here's a parallel game of rock, paper, scissors to whet your appetite.
|
||||
|
||||
~~~~
|
||||
use std;
|
||||
|
||||
import pipes::PortSet;
|
||||
import task::spawn;
|
||||
import iter::repeat;
|
||||
import rand::{seeded_rng, seed};
|
||||
import uint::range;
|
||||
import io::println;
|
||||
|
||||
fn main() {
|
||||
// Open a channel to receive game results
|
||||
let result_from_game = PortSet();
|
||||
let times = 10;
|
||||
let player1 = ~"graydon";
|
||||
let player2 = ~"patrick";
|
||||
|
||||
for repeat(times) {
|
||||
// Start another task to play the game
|
||||
let result = result_from_game.chan();
|
||||
do spawn |copy player1, copy player2| {
|
||||
let outcome = play_game(player1, player2);
|
||||
result.send(outcome);
|
||||
}
|
||||
}
|
||||
|
||||
// Report the results as the games complete
|
||||
for range(0, times) |round| {
|
||||
let winner = result_from_game.recv();
|
||||
println(fmt!("%s wins round #%u", winner, round));
|
||||
}
|
||||
|
||||
fn play_game(player1: ~str, player2: ~str) -> ~str {
|
||||
|
||||
// Our rock/paper/scissors types
|
||||
enum gesture {
|
||||
rock, paper, scissors
|
||||
}
|
||||
|
||||
let rng = seeded_rng(seed());
|
||||
// A small inline function for picking an RPS gesture
|
||||
let pick = || (~[rock, paper, scissors])[rng.gen_uint() % 3];
|
||||
|
||||
// Pick two gestures and decide the result
|
||||
match (pick(), pick()) {
|
||||
(rock, scissors) | (paper, rock) | (scissors, paper) => copy player1,
|
||||
(scissors, rock) | (rock, paper) | (paper, scissors) => copy player2,
|
||||
_ => ~"tie"
|
||||
}
|
||||
}
|
||||
}
|
||||
~~~~
|
||||
|
||||
## Conventions
|
||||
|
||||
Throughout the tutorial, words that indicate language keywords or
|
||||
|
@ -207,8 +152,8 @@ Rust program files are, by convention, given the extension `.rs`. Say
|
|||
we have a file `hello.rs` containing this program:
|
||||
|
||||
~~~~
|
||||
fn main(args: ~[~str]) {
|
||||
io::println(~"hello world from '" + args[0] + ~"'!");
|
||||
fn main() {
|
||||
io::println("hello world!");
|
||||
}
|
||||
~~~~
|
||||
|
||||
|
@ -221,7 +166,7 @@ If you modify the program to make it invalid (for example, by changing
|
|||
|
||||
~~~~ {.notrust}
|
||||
hello.rs:2:4: 2:16 error: unresolved name: io::print_it
|
||||
hello.rs:2 io::print_it(~"hello world from '" + args[0] + ~"'!");
|
||||
hello.rs:2 io::print_it("hello world!");
|
||||
^~~~~~~~~~~~
|
||||
~~~~
|
||||
|
||||
|
@ -237,7 +182,7 @@ declaration to appear at the top level of the file—all statements must
|
|||
live inside a function.
|
||||
|
||||
Rust programs can also be compiled as libraries, and included in other
|
||||
programs. The `use std` directive that appears at the top of a lot of
|
||||
programs. The `extern mod std` directive that appears at the top of a lot of
|
||||
examples imports the [standard library][std]. This is described in more
|
||||
detail [later on](#modules-and-crates).
|
||||
|
||||
|
@ -262,13 +207,6 @@ difference to be aware of is that the bodies of `if` statements and of
|
|||
`while` loops *have* to be wrapped in brackets. Single-statement,
|
||||
bracket-less bodies are not allowed.
|
||||
|
||||
If the verbosity of that bothers you, consider the fact that this
|
||||
allows you to omit the parentheses around the condition in `if`,
|
||||
`while`, and similar constructs. This will save you two characters
|
||||
every time. As a bonus, you no longer have to spend any mental energy
|
||||
on deciding whether you need to add braces or not, or on adding them
|
||||
after the fact when adding a statement to an `if` branch.
|
||||
|
||||
Accounting for these differences, the surface syntax of Rust
|
||||
statements and expressions is C-like. Function calls are written
|
||||
`myfunc(arg1, arg2)`, operators have mostly the same name and
|
||||
|
@ -276,14 +214,18 @@ precedence that they have in C, comments look the same, and constructs
|
|||
like `if` and `while` are available:
|
||||
|
||||
~~~~
|
||||
# fn call_a_function(_a: int) {}
|
||||
# fn it_works() {}
|
||||
# fn abort() {}
|
||||
fn main() {
|
||||
if 1 < 2 {
|
||||
while false { call_a_function(10 * 4); }
|
||||
} else if 4 < 3 || 3 < 4 {
|
||||
// Comments are C++-style too
|
||||
} else {
|
||||
/* Multi-line comment syntax */
|
||||
while true {
|
||||
/* Ensure that basic math works. */
|
||||
if 2*20 > 30 {
|
||||
// Everything is OK.
|
||||
it_works();
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
~~~~
|
||||
|
@ -291,36 +233,45 @@ fn main() {
|
|||
## Expression syntax
|
||||
|
||||
Though it isn't apparent in all code, there is a fundamental
|
||||
difference between Rust's syntax and the predecessors in this family
|
||||
of languages. A lot of things that are statements in C are expressions
|
||||
in Rust. This allows for useless things like this (which passes
|
||||
nil—the void type—to a function):
|
||||
difference between Rust's syntax and its predecessors in this family
|
||||
of languages. Many constructs that are statements in C are expressions
|
||||
in Rust. This allows Rust to be more expressive. For example, you might
|
||||
write a piece of code like this:
|
||||
|
||||
~~~~
|
||||
# fn a_function(_a: ()) {}
|
||||
a_function(while false {});
|
||||
# let item = "salad";
|
||||
let price;
|
||||
if item == "salad" {
|
||||
price = 3.50;
|
||||
} else if item == "muffin" {
|
||||
price = 2.25;
|
||||
} else {
|
||||
price = 2.00;
|
||||
}
|
||||
~~~~
|
||||
|
||||
But also useful things like this:
|
||||
But, in Rust, you don't have to repeat the name `price`:
|
||||
|
||||
~~~~
|
||||
# fn the_stars_align() -> bool { false }
|
||||
# fn something_else() -> bool { true }
|
||||
let x = if the_stars_align() { 4 }
|
||||
else if something_else() { 3 }
|
||||
else { 0 };
|
||||
# let item = "salad";
|
||||
let price = if item == "salad" { 3.50 }
|
||||
else if item == "muffin" { 2.25 }
|
||||
else { 2.00 };
|
||||
~~~~
|
||||
|
||||
This piece of code will bind the variable `x` to a value depending on
|
||||
the conditions. Note the condition bodies, which look like `{
|
||||
expression }`. The lack of a semicolon after the last statement in a
|
||||
braced block gives the whole block the value of that last expression.
|
||||
If the branches of the `if` had looked like `{ 4; }`, the above
|
||||
example would simply assign nil (void) to `x`. But without the
|
||||
semicolon, each branch has a different value, and `x` gets the value
|
||||
of the branch that was taken.
|
||||
Both pieces of code are exactly equivalent—they assign a value to `price`
|
||||
depending on the condition that holds. Note that the semicolons are omitted
|
||||
from the second snippet. This is important; the lack of a semicolon after the
|
||||
last statement in a braced block gives the whole block the value of that last
|
||||
expression.
|
||||
|
||||
This also works for function bodies. This function returns a boolean:
|
||||
Put another way, the semicolon in Rust *ignores the value of an expression*.
|
||||
Thus, if the branches of the `if` had looked like `{ 4; }`, the above example
|
||||
would simply assign nil (void) to `price`. But without the semicolon, each
|
||||
branch has a different value, and `price` gets the value of the branch that
|
||||
was taken.
|
||||
|
||||
This feature also works for function bodies. This function returns a boolean:
|
||||
|
||||
~~~~
|
||||
fn is_four(x: int) -> bool { x == 4 }
|
||||
|
@ -341,9 +292,12 @@ like the `let x = ...` example above.
|
|||
|
||||
## Identifiers
|
||||
|
||||
Rust identifiers must start with an alphabetic character or an
|
||||
underscore, and after that may contain any alphanumeric character, and
|
||||
more underscores.
|
||||
Rust identifiers follow the same rules as C; they start with an alphabetic
|
||||
character or an underscore, and after that may contain any sequence of
|
||||
alphabetic characters, numbers, or underscores. The preferred style is to
|
||||
begin function, variable, and module names with a lowercase letter, using
|
||||
underscores where they help readability, while beginning types with a capital
|
||||
letter.
|
||||
|
||||
The double-colon (`::`) is used as a module separator, so
|
||||
`io::println` means 'the thing named `println` in the module
|
||||
|
@ -365,20 +319,19 @@ a local variable that can be reassigned. Global constants can be
|
|||
defined with `const`:
|
||||
|
||||
~~~~
|
||||
use std;
|
||||
const repeat: uint = 5u;
|
||||
const repeat: int = 5;
|
||||
fn main() {
|
||||
let hi = ~"Hi!";
|
||||
let mut count = 0u;
|
||||
let hi = "Hi!";
|
||||
let mut count = 0;
|
||||
while count < repeat {
|
||||
io::println(hi);
|
||||
count += 1u;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
~~~~
|
||||
|
||||
Local variables may shadow earlier declarations, causing the
|
||||
previous variable to go out of scope.
|
||||
Local variables may shadow earlier declarations, making the earlier variables
|
||||
inaccessible.
|
||||
|
||||
~~~~
|
||||
let my_favorite_value: float = 57.8;
|
||||
|
@ -403,10 +356,10 @@ annotation:
|
|||
|
||||
~~~~
|
||||
// The type of this vector will be inferred based on its use.
|
||||
let x = ~[];
|
||||
let x = [];
|
||||
# vec::map(x, fn&(&&_y:int) -> int { _y });
|
||||
// Explicitly say this is a vector of integers.
|
||||
let y: ~[int] = ~[];
|
||||
// Explicitly say this is a vector of zero integers.
|
||||
let y: [int * 0] = [];
|
||||
~~~~
|
||||
|
||||
The basic types are written like this:
|
||||
|
@ -429,49 +382,58 @@ The basic types are written like this:
|
|||
`u8`, `u16`, `u32`, `u64`
|
||||
: Unsigned integers with a specific size.
|
||||
|
||||
`f32`, `f64`
|
||||
: Floating-point types.
|
||||
|
||||
`float`
|
||||
: The largest floating-point type efficiently supported on the target machine.
|
||||
: The largest floating-point type efficiently supported on the target
|
||||
machine.
|
||||
|
||||
`f32`, `f64`
|
||||
: Floating-point types with a specific size.
|
||||
|
||||
`char`
|
||||
: A character is a 32-bit Unicode code point.
|
||||
|
||||
`~str`
|
||||
: String type. A string contains a UTF-8 encoded sequence of characters.
|
||||
: A Unicode character (32 bits).
|
||||
|
||||
These can be combined in composite types, which will be described in
|
||||
more detail later on (the `T`s here stand for any other type):
|
||||
|
||||
`~[T]`
|
||||
: Vector type.
|
||||
`[T * N]`
|
||||
: Vector (like an array in other languages) with N elements.
|
||||
|
||||
`~[mut T]`
|
||||
: Mutable vector type.
|
||||
`[mut T * N]`
|
||||
: Mutable vector with N elements.
|
||||
|
||||
`(T1, T2)`
|
||||
: Tuple type. Any arity above 1 is supported.
|
||||
|
||||
`{field1: T1, field2: T2}`
|
||||
: Record type.
|
||||
`@T`, `~T`, `&T`
|
||||
: Pointer types.
|
||||
|
||||
`fn(arg1: T1, arg2: T2) -> T3`, `fn@()`, `fn~()`, `fn&()`
|
||||
The size of some types can vary when your program runs. Because of this, you
|
||||
don't manipulate them only by pointer, never directly. For instance, you
|
||||
can't refer to a string (`str`); instead you refer to a pointer to a string
|
||||
(`@str`, `~str`, or `&str`). These *dynamically-sized* types const of:
|
||||
|
||||
`fn(arg1: T1, arg2: T2) -> T3`
|
||||
: Function types.
|
||||
|
||||
`@T`, `~T`, `*T`
|
||||
: Pointer types.
|
||||
`str`
|
||||
: String type. A string contains a UTF-8 encoded sequence of characters.
|
||||
|
||||
`[T]`
|
||||
: Vector with unknown size (also called a slice).
|
||||
|
||||
`[mut T]`
|
||||
: Mutable vector with unknown size.
|
||||
|
||||
Types can be given names with `type` declarations:
|
||||
|
||||
~~~~
|
||||
type monster_size = uint;
|
||||
type MonsterSize = uint;
|
||||
~~~~
|
||||
|
||||
This will provide a synonym, `monster_size`, for unsigned integers. It
|
||||
will not actually create a new type—`monster_size` and `uint` can be
|
||||
used interchangeably, and using one where the other is expected is not
|
||||
a type error. Read about [single-variant enums](#single_variant_enum)
|
||||
This will provide a synonym, `MonsterSize`, for unsigned integers. It will not
|
||||
actually create a new, incompatible type—`MonsterSize` and `uint` can be used
|
||||
interchangeably, and using one where the other is expected is not a type
|
||||
error. Read about [single-variant enums](#single_variant_enum)
|
||||
further on if you need to create a type name that's not just a
|
||||
synonym.
|
||||
|
||||
|
@ -482,107 +444,46 @@ binary (`0b10010000`) base.
|
|||
|
||||
If you write an integer literal without a suffix (`3`, `-500`, etc.),
|
||||
the Rust compiler will try to infer its type based on type annotations
|
||||
and function signatures in the surrounding program. For example, here
|
||||
the type of `x` is inferred to be `u16` because it is passed to a
|
||||
function that takes a `u16` argument:
|
||||
and function signatures in the surrounding program. In the absence of any type
|
||||
annotations at all, Rust will assume that an unsuffixed integer literal has
|
||||
type `int`. It's also possible to avoid any type ambiguity by writing integer
|
||||
literals with a suffix. For example:
|
||||
|
||||
~~~~
|
||||
let x = 3;
|
||||
|
||||
fn identity_u16(n: u16) -> u16 { n }
|
||||
|
||||
identity_u16(x);
|
||||
let x = 50;
|
||||
log(error, x); // x is an int
|
||||
let y = 100u;
|
||||
log(error, y); // y is an uint
|
||||
~~~~
|
||||
|
||||
On the other hand, if the program gives conflicting information about
|
||||
what the type of the unsuffixed literal should be, you'll get an error
|
||||
message.
|
||||
|
||||
~~~~{.xfail-test}
|
||||
let x = 3;
|
||||
let y: i32 = 3;
|
||||
|
||||
fn identity_u8(n: u8) -> u8 { n }
|
||||
fn identity_u16(n: u16) -> u16 { n }
|
||||
|
||||
identity_u8(x); // after this, `x` is assumed to have type `u8`
|
||||
identity_u16(x); // raises a type error (expected `u16` but found `u8`)
|
||||
identity_u16(y); // raises a type error (expected `u16` but found `i32`)
|
||||
~~~~
|
||||
|
||||
In the absence of any type annotations at all, Rust will assume that
|
||||
an unsuffixed integer literal has type `int`.
|
||||
|
||||
~~~~
|
||||
let n = 50;
|
||||
log(error, n); // n is an int
|
||||
~~~~
|
||||
|
||||
It's also possible to avoid any type ambiguity by writing integer
|
||||
literals with a suffix. The suffixes `i` and `u` are for the types
|
||||
`int` and `uint`, respectively: the literal `-3i` has type `int`,
|
||||
while `127u` has type `uint`. For the fixed-size integer types, just
|
||||
suffix the literal with the type name: `255u8`, `50i64`, etc.
|
||||
|
||||
Note that, in Rust, no implicit conversion between integer types
|
||||
happens. If you are adding one to a variable of type `uint`, saying
|
||||
`+= 1u8` will give you a type error.
|
||||
|
||||
Floating point numbers are written `0.0`, `1e6`, or `2.1e-4`. Without
|
||||
a suffix, the literal is assumed to be of type `float`. Suffixes `f32`
|
||||
and `f64` can be used to create literals of a specific type. The
|
||||
suffix `f` can be used to write `float` literals without a dot or
|
||||
exponent: `3f`.
|
||||
a suffix, the literal is assumed to be of type `float`. Suffixes `f` (32-bit)
|
||||
and `l` (64-bit) can be used to create literals of a specific type.
|
||||
|
||||
## Other literals
|
||||
|
||||
The nil literal is written just like the type: `()`. The keywords
|
||||
`true` and `false` produce the boolean literals.
|
||||
|
||||
Character literals are written between single quotes, as in `'x'`. You
|
||||
may put non-ascii characters between single quotes (your source files
|
||||
should be encoded as UTF-8). Rust understands a number of
|
||||
character escapes, using the backslash character:
|
||||
|
||||
`\n`
|
||||
: A newline (Unicode character 10).
|
||||
|
||||
`\r`
|
||||
: A carriage return (13).
|
||||
|
||||
`\t`
|
||||
: A tab character (9).
|
||||
|
||||
`\\`, `\'`, `\"`
|
||||
: Simply escapes the following character.
|
||||
|
||||
`\xHH`, `\uHHHH`, `\UHHHHHHHH`
|
||||
: Unicode escapes, where the `H` characters are the hexadecimal digits that
|
||||
form the character code.
|
||||
Character literals are written between single quotes, as in `'x'`. Just as in
|
||||
C, Rust understands a number of character escapes, using the backslash
|
||||
character, `\n`, `\r`, and `\t` being the most common.
|
||||
|
||||
String literals allow the same escape sequences. They are written
|
||||
between double quotes (`~"hello"`). Rust strings may contain newlines.
|
||||
When a newline is preceded by a backslash, it, and all white space
|
||||
following it, will not appear in the resulting string literal. So
|
||||
this is equivalent to `~"abc"`:
|
||||
|
||||
~~~~
|
||||
let s = ~"a\
|
||||
b\
|
||||
c";
|
||||
~~~~
|
||||
between double quotes (`"hello"`). Rust strings may contain newlines.
|
||||
|
||||
## Operators
|
||||
|
||||
Rust's set of operators contains very few surprises. Binary arithmetic
|
||||
is done with `*`, `/`, `%`, `+`, and `-` (multiply, divide, remainder,
|
||||
plus, minus). `-` is also a unary prefix operator that does negation.
|
||||
plus, minus). `-` is also a unary prefix operator that does negation. As in C,
|
||||
the bit operators `>>`, `<<`, `&`, `|`, and `^` are supported.
|
||||
|
||||
Binary shifting is done with `>>` (shift right), and `<<` (shift
|
||||
left). Shift right is arithmetic if the value is signed and logical if
|
||||
the value is unsigned. Logical bitwise operators are `&`, `|`, and `^`
|
||||
(and, or, and exclusive or), and unary `!` for bitwise negation (or
|
||||
boolean negation when applied to a boolean value).
|
||||
Note that, if applied an integer value, `!` inverts all the bits.
|
||||
|
||||
The comparison operators are the traditional `==`, `!=`, `<`, `>`,
|
||||
`<=`, and `>=`. Short-circuiting (lazy) boolean operators are written
|
||||
|
@ -605,46 +506,13 @@ the logical bitwise operators have higher precedence — in C, `x & 2 > 0`
|
|||
comes out as `x & (2 > 0)`, in Rust, it means `(x & 2) > 0`, which is
|
||||
more likely to be what you expect (unless you are a C veteran).
|
||||
|
||||
## Attributes
|
||||
|
||||
Every definition can be annotated with attributes. Attributes are meta
|
||||
information that can serve a variety of purposes. One of those is
|
||||
conditional compilation:
|
||||
|
||||
~~~~
|
||||
#[cfg(windows)]
|
||||
fn register_win_service() { /* ... */ }
|
||||
~~~~
|
||||
|
||||
This will cause the function to vanish without a trace during
|
||||
compilation on a non-Windows platform, much like `#ifdef` in C.
|
||||
|
||||
Attributes are always wrapped in hash-braces (`#[attr]`). Inside the
|
||||
braces, a small minilanguage is supported, whose interpretation
|
||||
depends on the attribute that's being used. The simplest form is a
|
||||
plain name (as in `#[test]`, which is used by the [built-in test
|
||||
framework](#testing)). A name-value pair can be provided using an `=`
|
||||
character followed by a literal (as in `#[license = "BSD"]`, which is
|
||||
a valid way to annotate a Rust program as being released under a
|
||||
BSD-style license). Finally, you can have a name followed by a
|
||||
comma-separated list of nested attributes, as in this
|
||||
[crate](#modules-and-crates) metadata declaration:
|
||||
|
||||
~~~~ {.ignore}
|
||||
#[link(name = "std",
|
||||
vers = "0.1",
|
||||
url = "http://rust-lang.org/src/std")];
|
||||
~~~~
|
||||
|
||||
An attribute without a semicolon following it applies to the
|
||||
definition that follows it. When terminated with a semicolon, it
|
||||
applies to the module or crate in which it appears.
|
||||
|
||||
## Syntax extensions
|
||||
|
||||
The compiler defines a few built-in syntax extensions. The most useful
|
||||
one is `fmt!`, a sprintf-style text formatter that is expanded
|
||||
at compile time.
|
||||
*Syntax extensions* are special syntax that is not built into the language,
|
||||
but are instead provided by the libraries. To make it clear when a syntax
|
||||
extension is being used, their names all end with `!`. The standard library
|
||||
defines a few syntax extensions. The most useful one is `fmt!`, a
|
||||
`sprintf`-style text formatter that is expanded at compile time.
|
||||
|
||||
~~~~
|
||||
io::println(fmt!("%s is %d", ~"the answer", 42));
|
||||
|
@ -656,16 +524,7 @@ don't match the types of the arguments.
|
|||
|
||||
[pf]: http://en.cppreference.com/w/cpp/io/c/fprintf
|
||||
|
||||
All syntax extensions look like `extension_name!`. Another built-in one is
|
||||
`env!`, which will look up its argument as an environment variable at
|
||||
compile-time.
|
||||
|
||||
~~~~
|
||||
io::println(env!("PATH"));
|
||||
~~~~
|
||||
|
||||
It is possible for the user to define new syntax extensions, within certain
|
||||
limits. These are called [macros](#macros).
|
||||
You can define your own syntax extensions via macros.
|
||||
|
||||
# Control structures
|
||||
|
||||
|
@ -694,30 +553,24 @@ end of the block:
|
|||
fn signum(x: int) -> int {
|
||||
if x < 0 { -1 }
|
||||
else if x > 0 { 1 }
|
||||
else { return 0; }
|
||||
else { return 0 }
|
||||
}
|
||||
~~~~
|
||||
|
||||
The `return` and its semicolon could have been left out without
|
||||
changing the meaning of this function, but it illustrates that you
|
||||
will not get a type error in this case, although the last arm doesn't
|
||||
have type `int`, because control doesn't reach the end of that arm
|
||||
(`return` is jumping out of the function).
|
||||
|
||||
## Pattern matching
|
||||
|
||||
Rust's `match` construct is a generalized, cleaned-up version of C's
|
||||
`switch` construct. You provide it with a value and a number of arms,
|
||||
each labelled with a pattern, and it will execute the arm that matches
|
||||
the value.
|
||||
`switch` construct. You provide it with a value and a number of *arms*,
|
||||
each labelled with a pattern, and the code will attempt to match each pattern
|
||||
in order. For the first one that matches, the arm is executed.
|
||||
|
||||
~~~~
|
||||
# let my_number = 1;
|
||||
match my_number {
|
||||
0 => io::println(~"zero"),
|
||||
1 | 2 => io::println(~"one or two"),
|
||||
3 to 10 => io::println(~"three to ten"),
|
||||
_ => io::println(~"something else")
|
||||
0 => io::println("zero"),
|
||||
1 | 2 => io::println("one or two"),
|
||||
3..10 => io::println("three to ten"),
|
||||
_ => io::println("something else")
|
||||
}
|
||||
~~~~
|
||||
|
||||
|
@ -725,10 +578,10 @@ There is no 'falling through' between arms, as in C—only one arm is
|
|||
executed, and it doesn't have to explicitly `break` out of the
|
||||
construct when it is finished.
|
||||
|
||||
The part to the left of each arm is called the pattern. Literals are
|
||||
valid patterns, and will match only their own value. The pipe operator
|
||||
The part to the left of the arrow `=>` is called the *pattern*. Literals are
|
||||
valid patterns and will match only their own value. The pipe operator
|
||||
(`|`) can be used to assign multiple patterns to a single arm. Ranges
|
||||
of numeric literal patterns can be expressed with `to`. The underscore
|
||||
of numeric literal patterns can be expressed with `..`. The underscore
|
||||
(`_`) is a wildcard pattern that matches everything.
|
||||
|
||||
The patterns in an match arm are followed by a fat arrow, `=>`, then an
|
||||
|
@ -740,10 +593,10 @@ commas are optional.
|
|||
# let my_number = 1;
|
||||
match my_number {
|
||||
0 => {
|
||||
io::println(~"zero")
|
||||
io::println("zero")
|
||||
}
|
||||
_ => {
|
||||
io::println(~"something else")
|
||||
io::println("something else")
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
@ -758,10 +611,11 @@ you use the matching to get at the contents of data types. Remember
|
|||
that `(float, float)` is a tuple of two floats:
|
||||
|
||||
~~~~
|
||||
fn angle(vec: (float, float)) -> float {
|
||||
match vec {
|
||||
(0f, y) if y < 0f => 1.5 * float::consts::pi,
|
||||
(0f, y) => 0.5 * float::consts::pi,
|
||||
use float::consts::pi;
|
||||
fn angle(vector: (float, float)) -> float {
|
||||
match vector {
|
||||
(0f, y) if y < 0f => 1.5 * pi,
|
||||
(0f, y) => 0.5 * pi,
|
||||
(x, y) => float::atan(y / x)
|
||||
}
|
||||
}
|
||||
|
@ -828,88 +682,6 @@ For more involved iteration, such as going over the elements of a
|
|||
collection, Rust uses higher-order functions. We'll come back to those
|
||||
in a moment.
|
||||
|
||||
## Failure
|
||||
|
||||
The `fail` keyword causes the current [task](#tasks) to fail. You use
|
||||
it to indicate unexpected failure, much like you'd use `abort` in a
|
||||
C program or a fatal exception in a C++ program.
|
||||
|
||||
There is no way for the current task to resume execution after
|
||||
failure; failure is nonrecoverable. It is, however, possible for
|
||||
*another* task to handle the failure, allowing the program to continue
|
||||
running.
|
||||
|
||||
`fail` takes an optional argument specifying the reason for the
|
||||
failure. It must have type `~str`.
|
||||
|
||||
In addition to the `fail` statement, the following circumstances cause
|
||||
task failure:
|
||||
|
||||
* Accessing an out-of-bounds element of a vector.
|
||||
|
||||
* An assertion failure.
|
||||
|
||||
* Integer division by zero.
|
||||
|
||||
* Running out of memory.
|
||||
|
||||
## Assertions
|
||||
|
||||
The keyword `assert`, followed by an expression with boolean type,
|
||||
will check that the given expression results in `true`, and cause a
|
||||
failure otherwise. It is typically used to double-check things that
|
||||
*should* hold at a certain point in a program. `assert` statements are
|
||||
always active; there is no way to build Rust code with assertions
|
||||
disabled.
|
||||
|
||||
~~~~
|
||||
let mut x = 100;
|
||||
while (x > 10) { x -= 10; }
|
||||
assert x == 10;
|
||||
~~~~
|
||||
|
||||
## Logging
|
||||
|
||||
Rust has a built-in logging mechanism, using the `log` statement.
|
||||
Logging is polymorphic—any type of value can be logged, and the
|
||||
runtime will do its best to output a textual representation of the
|
||||
value.
|
||||
|
||||
~~~~
|
||||
log(warn, ~"hi");
|
||||
log(error, (1, ~[2.5, -1.8]));
|
||||
~~~~
|
||||
|
||||
The first argument is the log level (levels `debug`, `info`, `warn`,
|
||||
and `error` are predefined), and the second is the value to log. By
|
||||
default, you *will not* see the output of that first log statement,
|
||||
which has `warn` level. The environment variable `RUST_LOG` controls
|
||||
which log level is used. It can contain a comma-separated list of
|
||||
paths for modules that should be logged. For example, running `rustc`
|
||||
with `RUST_LOG=rustc::front::attr` will turn on logging in its
|
||||
attribute parser. If you compile a program named `foo.rs`, its
|
||||
top-level module will be called `foo`, and you can set `RUST_LOG` to
|
||||
`foo` to enable `warn`, `info` and `debug` logging for the module.
|
||||
|
||||
Turned-off `log` statements impose minimal overhead on the code that
|
||||
contains them, because the arguments to `log` are evaluated lazily.
|
||||
So except in code that needs to be really, really fast,
|
||||
you should feel free to scatter around debug logging statements, and
|
||||
leave them in.
|
||||
|
||||
Three macros that combine text-formatting (as with `fmt!`) and logging
|
||||
are available. These take a string and any number of format arguments,
|
||||
and will log the formatted string:
|
||||
|
||||
~~~~
|
||||
# fn get_error_string() -> ~str { ~"boo" }
|
||||
warn!("only %d seconds remaining", 10);
|
||||
error!("fatal: %s", get_error_string());
|
||||
~~~~
|
||||
|
||||
Because the macros `debug!`, `warn!`, and `error!` expand to calls to `log`,
|
||||
their arguments are also lazily evaluated.
|
||||
|
||||
# Functions
|
||||
|
||||
Like all other static declarations, such as `type`, functions can be
|
||||
|
@ -921,8 +693,12 @@ with the `fn` keyword, the type of arguments are specified following
|
|||
colons and the return type follows the arrow.
|
||||
|
||||
~~~~
|
||||
fn int_to_str(i: int) -> ~str {
|
||||
return ~"tube sock";
|
||||
fn repeat(string: &str, count: int) -> ~str {
|
||||
let mut result = ~"";
|
||||
for count.times {
|
||||
result += string;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
~~~~
|
||||
|
||||
|
@ -967,100 +743,63 @@ In Rust, these are annotated with the pseudo-return type '`!`':
|
|||
fn dead_end() -> ! { fail }
|
||||
~~~~
|
||||
|
||||
This helps the compiler avoid spurious error messages. For example,
|
||||
the following code would be a type error if `dead_end` would be
|
||||
expected to return.
|
||||
|
||||
~~~~
|
||||
# fn can_go_left() -> bool { true }
|
||||
# fn can_go_right() -> bool { true }
|
||||
# enum dir { left, right }
|
||||
# fn dead_end() -> ! { fail; }
|
||||
let dir = if can_go_left() { left }
|
||||
else if can_go_right() { right }
|
||||
else { dead_end(); };
|
||||
~~~~
|
||||
Using `!` in your code instead of making up a return type helps the compiler
|
||||
avoid spurious error messages.
|
||||
|
||||
# Basic datatypes
|
||||
|
||||
The core datatypes of Rust are structural records, enums (tagged
|
||||
unions, algebraic data types), and tuples. They are immutable
|
||||
by default.
|
||||
The core datatypes of Rust are structs, enums (tagged unions, algebraic data
|
||||
types), and tuples. They are immutable by default.
|
||||
|
||||
~~~~
|
||||
type point = {x: float, y: float};
|
||||
struct Point { x: float, y: float }
|
||||
|
||||
enum shape {
|
||||
circle(point, float),
|
||||
rectangle(point, point)
|
||||
enum Shape {
|
||||
Circle(Point, float),
|
||||
Rectangle(Point, Point)
|
||||
}
|
||||
~~~~
|
||||
|
||||
## Records
|
||||
## Structs
|
||||
|
||||
Rust record types are written `{field1: T1, field2: T2 [, ...]}`,
|
||||
where `T1`, `T2`, ... denote types. Record literals are written in
|
||||
the same way, but with expressions instead of types. They are quite
|
||||
similar to C structs, and even laid out the same way in memory (so you
|
||||
can read from a Rust struct in C, and vice-versa). The dot operator is
|
||||
used to access record fields (`mypoint.x`).
|
||||
Rust struct types must be declared before they are used using the `struct`
|
||||
syntax: `struct Name { field1: T1, field2: T2 [, ...] }`, where `T1`, `T2`,
|
||||
... denote types. To construct a struct, use the same syntax, but leave off
|
||||
the `struct`; for example: `Point { x: 1.0, y: 2.0 }`.
|
||||
|
||||
Structs are quite similar to C structs and are even laid out the same way in
|
||||
memory (so you can read from a Rust struct in C, and vice-versa). The dot
|
||||
operator is used to access struct fields (`mypoint.x`).
|
||||
|
||||
Fields that you want to mutate must be explicitly marked `mut`.
|
||||
|
||||
~~~~
|
||||
type stack = {content: ~[int], mut head: uint};
|
||||
~~~~
|
||||
|
||||
With such a type, you can do `mystack.head += 1u`. If `mut` were
|
||||
omitted from the type, such an assignment would result in a type
|
||||
error.
|
||||
|
||||
To create a new record based on the value of an existing record
|
||||
you construct it using the `with` keyword:
|
||||
|
||||
~~~~
|
||||
# impl {x:float, y:float} : core::cmp::Eq {
|
||||
# pure fn eq(&&other: {x:float, y:float}) -> bool {
|
||||
# self.x == other.x && self.y == other.y
|
||||
# }
|
||||
# }
|
||||
|
||||
let oldpoint = {x: 10f, y: 20f};
|
||||
let newpoint = {x: 0f with oldpoint};
|
||||
assert newpoint == {x: 0f, y: 20f};
|
||||
~~~~
|
||||
|
||||
This will create a new record, copying all the fields from `oldpoint`
|
||||
into it, except for the ones that are explicitly set in the literal.
|
||||
|
||||
Rust record types are *structural*. This means that `{x: float, y:
|
||||
float}` is not just a way to define a new type, but is the actual name
|
||||
of the type. Record types can be used without first defining them. If
|
||||
module A defines `type point = {x: float, y: float}`, and module B,
|
||||
without knowing anything about A, defines a function that returns an
|
||||
`{x: float, y: float}`, you can use that return value as a `point` in
|
||||
module A. (Remember that `type` defines an additional name for a type,
|
||||
not an actual new type.)
|
||||
|
||||
## Record patterns
|
||||
|
||||
Records can be destructured in `match` patterns. The basic syntax is
|
||||
`{fieldname: pattern, ...}`, but the pattern for a field can be
|
||||
omitted as a shorthand for simply binding the variable with the same
|
||||
name as the field.
|
||||
|
||||
~~~~
|
||||
# let mypoint = {x: 0f, y: 0f};
|
||||
match mypoint {
|
||||
{x: 0f, y: y_name} => { /* Provide sub-patterns for fields */ }
|
||||
{x, y} => { /* Simply bind the fields */ }
|
||||
struct Stack {
|
||||
content: ~[int],
|
||||
mut head: uint
|
||||
}
|
||||
~~~~
|
||||
|
||||
The field names of a record do not have to appear in a pattern in the
|
||||
same order they appear in the type. When you are not interested in all
|
||||
the fields of a record, a record pattern may end with `, _` (as in
|
||||
`{field1, _}`) to indicate that you're ignoring all other fields.
|
||||
With a value of such a type, you can do `mystack.head += 1`. If `mut` were
|
||||
omitted from the type, such an assignment would result in a type error.
|
||||
|
||||
## Struct patterns
|
||||
|
||||
Structs can be destructured in `match` patterns. The basic syntax is
|
||||
`Name {fieldname: pattern, ...}`:
|
||||
~~~~
|
||||
# struct Point { x: float, y: float }
|
||||
# let mypoint = Point { x: 0.0, y: 0.0 };
|
||||
match mypoint {
|
||||
Point { x: 0.0, y: y } => { io::println(y.to_str()); }
|
||||
Point { x: x, y: y } => { io::println(x.to_str() + " " + y.to_str()); }
|
||||
}
|
||||
~~~~
|
||||
|
||||
In general, the field names of a struct do not have to appear in the same
|
||||
order they appear in the type. When you are not interested in all
|
||||
the fields of a struct, a struct pattern may end with `, _` (as in
|
||||
`Name {field1, _}`) to indicate that you're ignoring all other fields.
|
||||
|
||||
## Enums
|
||||
|
||||
|
@ -1068,15 +807,15 @@ Enums are datatypes that have several alternate representations. For
|
|||
example, consider the type shown earlier:
|
||||
|
||||
~~~~
|
||||
# type point = {x: float, y: float};
|
||||
enum shape {
|
||||
circle(point, float),
|
||||
rectangle(point, point)
|
||||
# struct Point { x: float, y: float }
|
||||
enum Shape {
|
||||
Circle(Point, float),
|
||||
Rectangle(Point, Point)
|
||||
}
|
||||
~~~~
|
||||
|
||||
A value of this type is either a circle, in which case it contains a
|
||||
point record and a float, or a rectangle, in which case it contains
|
||||
A value of this type is either a Circle, in which case it contains a
|
||||
point struct and a float, or a Rectangle, in which case it contains
|
||||
two point records. The run-time representation of such a value
|
||||
includes an identifier of the actual form that it holds, much like the
|
||||
'tagged union' pattern in C, but with better ergonomics.
|
||||
|
|
Loading…
Add table
Reference in a new issue