Links
rustup docs --book
Functions
Functions and variables use snake case. Types and traits use PascalCase. All by convention. Order doesn't matter.
Looping
loop {}
is an infinite loop. break
exits the loop, continue
exits the current iteration.
Generics
Generic function with type constrained to implement Debug
and PartialOrder
traits:
or
Pattern Matching
Match is an expression.
Printing
print!
and println!
macros to write to stdout. There is also format!
to write to a string.
Simple types can be printed with display formatting {}
. More complex types can be printed with debug formatting {:?}
.
Display formatting requires implementing std::fmt::Display
.
{:#?}
is pretty-printing.
Named substitution
Substitution format
{variable:padding alignment minimum.maximum}
e.g. {city1:0<5.15}
References
References let you pass a way to access a value to a function without copying the value. &identifier
is a reference to the identifier
value, &Person
is a type that is a reference to the Person
type. To go from a reference to the value dereference let bob = *bob_ref
, but this happens automatically with 'autoderef'.
References are stack allocated pointers to values in the heap. They represent a borrowed value.
Dereferencing
*
is the inverse of &
.
The 'dot' operator
.
dereferences automatically.
Mutable References
There can be only one mutable reference, and it cannot be in scope with an immutable reference.
More on passing to functions and mutability
fn function_name(variable: String)
takes a String and owns it. If it doesn't return anything, then the variable dies inside the function.fn function_name(variable: &String)
borrows a String and can look at itfn function_name(variable: &mut String)
borrows a String and can change it
Stack
Whenever we hold a value (includes function arguments) we must know its size, so that the stack pointer can be moved properly.
Knowing the size of a value of a type is indicated by the marker trait Sized
.
fn f<T: Sized>(t: T) {}
Strings
String
is the dynamic heap string type. When you need to modify or own the string. str
is an immutable sequence of UTF-8 characters. A 'slice' of string data. Used behind a reference as &str
.
To make a String
from a &str
: String::from("foo")
or "foo".to_string()
or let foo: String = "Hello world".into()
Literal Strings
Modifying Strings
String
is mutated via .push_str(&str)
.
Structs
Types are PascalCase. Structs can be named tuples:
or with named fields
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
fn main() {
let age = 38;
let me = Person {
name: "Liam".to_string(),
age
};
println!("{:?}", me);
}
Struct update
Traits
Declared like an implementation, but with no function bodies. Can have a body, which becomes the default implementation. Like C# abstract classes.
You can implement external traits on internal types, and internal traits on external types, but not external traits on external types.
Traits as function parameters is a bit weird. We have to use &impl DoAThing
instead of just the trait name.
The above is syntactic sugar for (trait bound syntax):
I think I prefer the trait bound syntax.
Types
Scalar types integer, float, bool, char
Compound types tuple and array.
Tuple
Values accessed by zero based indexing.
Array
Fixed size. Stack allocated. Zero based indexing without the .
.
Can be mutated.
Can be sliced (range end is exclusive or inclusive (=i
)).
Vector
Resizable generic collection.
Can also declare with a macro:
Can slice:
Or declare with a size:
Enumerations
Values can have values, thus they are discriminated unions.
and they can have static functions and methods:
Questions
- Why does
mut
seem to do double duty? (re-assignable identifiers and mutable values)