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, or use.

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 the if 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 a match 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 type std::ops::Range. A range is a simple struct with two fields: start and end.

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 (breaks 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 for return ().

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 of Deref coercion is to make smart pointer types, like Box, behave as much like the underlying value as possible.