diff options
| author | Patrick Walton <pcwalton@mimiga.net> | 2012-08-31 16:57:37 -0700 |
|---|---|---|
| committer | Patrick Walton <pcwalton@mimiga.net> | 2012-08-31 16:58:23 -0700 |
| commit | 9eaaceb9f020faca7e2880fdc94946056c0ce4cf (patch) | |
| tree | 418038089da67aa49658394bc79a107ed053a4e0 /doc/tutorial.md | |
| parent | 329281ebcc3a6e007b33607b7376602a7a6e11ec (diff) | |
| download | rust-9eaaceb9f020faca7e2880fdc94946056c0ce4cf.tar.gz rust-9eaaceb9f020faca7e2880fdc94946056c0ce4cf.zip | |
doc: Update the tutorial
Diffstat (limited to 'doc/tutorial.md')
| -rw-r--r-- | doc/tutorial.md | 621 |
1 files changed, 180 insertions, 441 deletions
diff --git a/doc/tutorial.md b/doc/tutorial.md index 5b5623020c8..0fc56f135c9 100644 --- a/doc/tutorial.md +++ b/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): - -~~~~ -# fn a_function(_a: ()) {} -a_function(while false {}); +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: + +~~~~ +# 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. + +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 also works for function bodies. This function returns a boolean: +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. -`char` - : A character is a 32-bit Unicode code point. +`f32`, `f64` + : Floating-point types with a specific size. -`~str` - : String type. A string contains a UTF-8 encoded sequence of characters. +`char` + : 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. + +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`, `fn@()`, `fn~()`, `fn&()` +`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: - -~~~~ -let x = 3; - -fn identity_u16(n: u16) -> u16 { n } - -identity_u16(x); -~~~~ - -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 } +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: -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 x = 50; +log(error, x); // x is an int +let y = 100u; +log(error, y); // y is an uint ~~~~ -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`). - -Fields that you want to mutate must be explicitly marked `mut`. - -~~~~ -type stack = {content: ~[int], mut head: uint}; -~~~~ +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 }`. -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. +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`). -To create a new record based on the value of an existing record -you construct it using the `with` keyword: +Fields that you want to mutate must be explicitly marked `mut`. ~~~~ -# 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}; +struct Stack { + content: ~[int], + mut head: uint +} ~~~~ -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.) +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. -## 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. +## Struct patterns +Structs can be destructured in `match` patterns. The basic syntax is +`Name {fieldname: pattern, ...}`: ~~~~ -# let mypoint = {x: 0f, y: 0f}; +# struct Point { x: float, y: float } +# let mypoint = Point { x: 0.0, y: 0.0 }; match mypoint { - {x: 0f, y: y_name} => { /* Provide sub-patterns for fields */ } - {x, y} => { /* Simply bind the fields */ } + Point { x: 0.0, y: y } => { io::println(y.to_str()); } + Point { x: x, y: y } => { io::println(x.to_str() + " " + y.to_str()); } } ~~~~ -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. +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. |
