Expressions
In C, expressions and statements are different. Expressions have values. Statements don’t.
In Rust, if
and match
can produce values. Most of the control flow tools in C are statements. In Rust, they are all expressions.
Declarations
You may occasionally see code that seems to redeclare an existing variable, like this:
for line in file.lines() {
let line = line?;
...
}
// equivalent to
for line_result in file.lines() {
let line = line_result?;
...
}
The let
declaration creates a new, second variable, of a different type. The type of the first variable line
is Result<String, io::Error>
. The second line
is a String
. Its definition supersedes the first’s for the rest of the block. This is called shadowing and is very common in Rust programs.
When an fn
is declared inside a block, its scope is the entire block—that is, it can be used throughout the enclosing block. A nested fn
cannot access local variables or arguments that happen to be in scope.
if
and match
if
and match
match code {
0 => println!("OK"),
1 => println!("Wires Tangled"),
2 => println!("User Asleep"),
_ => println!("Unrecognized Error {}", code)
}
All blocks of an if
expression must produce values of the same type. Similarly, all arms of a match
expression must have the same type.
if let
if let
if let pattern = expr {
block1
} else {
block2
}
The given expr
either matches the pattern
, in which case block1
runs, or doesn’t match, and block2
runs.
Loops
while condition {
block
}
while let pattern = expr {
block
}
loop {
// Use loop to write infinite loops. It executes the block repeatedly forever (or until a break or return is reached or the thread panics).
block
}
for pattern in iterable {
block
}
The ..
operator produces a range, a simple struct with two fields: start
and end
. 0..20
is the same as std::ops::Range { start: 0, end: 20 }
. Ranges can be used with for
loops because Range
is an iterable type: it implements the std::iter::IntoIterator
trait.
let strings: Vec<String> = error_messages();
for s in strings { // each String is moved into s here...
println!("{}", s);
} // ...and dropped here
println!("{} error(s)", strings.len()); // error: use of moved value
Control Flow in Loops
Within the body of a loop
, you can give break
an expression, whose value becomes that of the loop:
// Each call to `next_line` returns either `Some(line)`, where
// `line` is a line of input, or `None`, if we've reached the end of
// the input. Return the first line that starts with "answer: ".
// Otherwise, return "answer: nothing".
let answer = loop {
if let Some(line) = next_line() {
if line.starts_with("answer: ") {
break line;
}
} else {
break "answer: nothing";
}
};
A loop can be labeled with a lifetime. In the following example, 'search:
is a label for the outer for
loop. Thus, break 'search
exits that loop, not the inner loop:
'search:
for room in apartment {
for spot in room.hiding_spots() {
if spot.contains(keys) {
println!("Your keys are {} in the {}.", spot, room);
break 'search;
}
}
}
A break
can have both a label and a value expression. Labels can also be used with continue
.
return
Expressions
return
Expressionsreturn
without a value is shorthand for return ()
.
We used the ?
operator to check for errors after calling a function that can fail:
let output = File::create(filename)?;
let output = match File::create(filename) {
Ok(f) => f,
Err(err) => return Err(err)
};
Expressions that don’t finish normally are assigned the special type !
, and they’re exempt from the rules about types having to match. You can see !
in the function signature of std::process::exit()
:
fn exit(code: i32) -> !
The !
means that exit()
never returns. It’s a divergent function.
You can write divergent functions of your own using the same syntax, and this is perfectly natural in some cases:
fn serve_forever(socket: ServerSocket, handler: ServerHandler) -> ! {
socket.listen();
loop {
let s = socket.accept();
handler.handle(s);
}
}
Rust then considers it an error if the function can return normally.
Function and Method Calls
.
operator is ease with the types.
Syntax for generic types:
return Vec::<i32>::with_capacity(1000); // ok, using ::<
let ramp = (0 .. n).collect::<Vec<i32>>(); // ok, using ::<
The symbol ::<...>
is affectionately known in the Rust community as the turbofish.
Fields and Elements
ame.black_pawns // struct field
coords.1 // tuple element
pieces[i] // array element
Expressions like these three are called lvalues, because they can appear on the left side of an assignment.
Extracting a slice from an array or vector is straightforward:
let second_half = &game_moves[midpoint .. end];
The ..=
operator produces end-inclusive (or closed) ranges, which do include the end value:
..= b // RangeToInclusive { end: b }
a ..= b // RangeInclusive::new(a, b)
Arithmetic, Bitwise, Comparison, and Logical Operators
Rust uses !
instead of ~
for bitwise NOT.
Bit shifting is always sign-extending on signed integer types and zero-extending on unsigned integer types. Since Rust has unsigned integers, it does not need an unsigned shift operator, like Java’s >>>
operator.
Bitwise operations have higher precedence than comparisons, unlike C, so if you write x & BIT != 0
, that means (x & BIT) != 0
, as you probably intended. This is much more useful than C’s interpretation, x & (BIT != 0)
, which tests the wrong bit!
Type Casts
Rust does not have C’s increment and decrement operators ++
and --
.
Numbers may be cast from any of the built-in numeric types to any other.
Values of type bool
or char
, or of a C-like enum
type, may be cast to any integer type.
Some casts involving unsafe pointer types are also allowed.
Values of type
&String
auto-convert to type&str
without a cast.Values of type
&Vec<i32>
auto-convert to&[i32]
.Values of type
&Box<Chessboard>
auto-convert to&Chessboard
.
These are called deref coercions, because they apply to types that implement the Deref
built-in trait. The purpose of Deref
coercion is to make smart pointer types, like Box
, behave as much like the underlying value as possible. Using a Box<Chessboard>
is mostly just like using a plain Chessboard
, thanks to Deref
.
Closures
Rust has closures, lightweight function-like values. A closure usually consists of an argument list, given between vertical bars, followed by an expression:
let is_even = |x| x % 2 == 0;
Last updated