Expressions
Blocks and Semicolons
When you see expected type '()'
, look for a missing semicolon first.
Empty statements are allowed in blocks. They consist of a stray semicolon all by itself.
Declarations
Syntax: The simplest kind of declaration is a
let
declaration, which declares local variables:let name: type = expr;
The type and initializer are optional. The semicolon is required.
Term: An item declaration is a declaration that could appear globally in a program or module, such as a
fn
,struct
, oruse
.
When a fn
is declared inside a block, its scope is the entire block (no TDZ)--that is, it can be used through the enclosing block. But a nested fn
cannot access local variables or arguments that happen to be in scope. (the alternative to nested function are closure, which do have access to enclosing scope).
if
and match
Expressions used as conditions in if
expressions must be of type bool
.
An if
expression with no else
block behaves exactly as though it had an empty else
block.
Syntax: The general form of a
match
expression is:match value { pattern => expr, ... }
if let
Syntax: The last
if
form is theif let
expression:if let pattern = expr { block1 } else { block2 }
It's never strictly necessary to use if let
, because match
can do everything if let
can do.
Shorthand: An
if let
expression is shorthand for amatch
with just one pattern:match expr { pattern => { block1 } _ => { block2 } }
Loops
Loops are expressions in Rust, but they don't produce useful values.
The value of a loop is
()
.
Operator: The
..
operator produces a range of typestd::ops::Range
. A range is a simple struct with two fields:start
andend
.
Ranges can be used with for
loops because Range
is an iterable type.
Term: An iterator type is a type that implements the
std::iter::IntoIterator
trait.
Iterating over a mut
reference provides a mut
reference to each element:
#![allow(unused)] fn main() { let mut strings: Vec<String> = vec![ "what's".to_string(), "my".to_string(), "line?".to_string(), ]; for rs in &mut strings { rs.push('\n'); } println!("{}", strings.join("")); }
A loop can be labeled with a lifetime. In the below example, 'search:
is a label for the outer for
loop. Thus break 'search
exits that loop, not the inner loop (break
s can also be used with continue
):
#![allow(unused)] fn main() { '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; } } } }
return
Expressions
Shorthand: A
return
without a value is shorthand forreturn ()
.
Why Rust Has Loop
Expressions that don't finish normally are assigned the special type !
, and they're exempt from the rules about types having to match.
Term: A function that never returns--that is, returns
!
--is called a divergent function.
An example of a divergent funtion is std::process::exit
, which has the following type signature:
fn exit(code: i32) -> !;
Function and Method Calls
The difference between static and nonstatic methods is the same as in OO languages: nonstatic methods are called on values (like my_vec.len()
), and static methods are called on types themselves (like Vec::new()
).
It's considered good style to omit types whenever they can be inferred.
Fields and Elements
Fields of a struct are accessed using the familiar .
operator. Tuples are the same, except that their fields have numbers rather than names.
Square brackets access the elements of an array, a slice, or a vector
Reference Operators
Operator: The unary
*
operator is used to access the referent of a reference.
The *
operator is only necessary when we want to read or write the entire value that the reference points to.
Arithmetic, Bitwise, Comparison, and Logical Operators
Warning: Dividing an integer by zero trigger a panic, even in releases builds.
Integers have a method a.checked_div(b)
that returns Option<I>
and never panics. If ever there's the slightest possibility that an integer will be divided by zero, use checked_div
.
There is no unary +
operator.
Assignment
Syntax: Rust does not support chained assignment.
a = b = c
will not work.
Syntax: Rust does not have increment and decrement operators:
++
and--
.
Type Casts
Casting an integer to another integer type is always well-defined. Converting to a narrow type results in truncation.
Casting a large floating-point value to an integer type that is too small to represent it can lead to undefined behavior. (this might no longer be true in newer versions of Rust).
Implicit behavior: Values of type
&String
auto-convert to type&str
without a cast.
Implicit behavior: Values of type
&Vec<i32>
auto-convert to&[i32]
.
Implicit behavior: Values of type
&Box<T>
auto-convert to&T
.
Term: The above implicit behaviors are called deref coercions, because they apply to types that implement the
Deref
built-in trait. The purpose ofDeref
coercion is to make smart pointer types, likeBox
, behave as much like the underlying value as possible.