Please refer to Don Bright’s original rust-lang-cheat-sheet which I based much of this content off, as I work through The Book.

Rust in a nutshell

  • Syntax similar to C
  • Ownership of memory enforced at compile time
  • Statically linked
  • Functional-ish, generic-ish, not so objecty
  • Control flow using patterns, match keyword
  • Packages: ‘cargo’ command, https://crates.io
  • Testing: cargo test, #[test]
  • Concurrency: ownership, mutability, channels, mutex, crossbeam + Rayon packages
  • Auto formatter: rustfmt filename.rs
  • Compiler engine: LLVM
  • Raw pointers, low level: unsafe{} keyword

Documentation

  • rustup doc for offline docs!
  • doc.rust-lang.org standard library, primitive types, standard macros
  • docs.rs for package documentation

Toolchain

$ rustup.sh               # install rust, see rust-lang.org for details
$ cargo new myproj        # start new executable project
$ cargo new --bin myproj  # as above
$ cargo new --lib myproj  # start new library project
$ cd myproj               # cd into the new directory
$ ls -lR                  # list our skeleton of files
src/main.rs               # main.rs, has main() entry point
Cargo.toml                # Cargo.toml defines packaging
$ $EDITOR Cargo.toml      # add dependencies and other details
$ cargo build             # downloads dependencies + builds main.rs
$ cargo build --release   # release build
$ cargo check             # make sure code compiles, without binary outputs
$ cargo update            # ignore Cargo.lock and figure out latest versions
$ cargo run               # runs program created from main.rs
$ cargo doc --open        # local web based doc
$ cargo test              # runs tests (in parallel by default)
$ cargo test -- --test-threads=1  # run tests one at a time
$ cargo test -- --nocapture       # run tests, show output
$ cargo run --example fundemo -- --argtodemo # run example with argument

Mutability basic

let x = false;           // all variable bindings are immutable by default
x = true;                // compile error: can't change an immutable binding
let mut p = false;       // "mut" designates a binding as mutable
p = true;                // ok, mutable binding can change;

Types and variables

Rust provide two compound types; arrays and tuples.

let x: bool = false; // let keyword
let k = false;       // rustc can determine some types, this is idiomatic rust
let y: char = '上';  // all chars are 4 bytes
let  = 5;          // error. identifiers must be ASCII characters
let a: i8 = -2;      // 8 bit signed integers, also i16, i32, i64
let b: u8 = 200;     // 8 bit unsigned integers, also u16, u32, u64
let n: f32 = 0.45;   // 32 bit float (automatcally converted+rounded from decimal to binary)
let n = 42.01f64;    // 64 bit float literal of the number 42.01 (approximately)
let r: [u8;3] = [3,4,5];          // array of 3 int, cannot grow
let s = [0;500];                  // array of 500 integers, each initialized to 0
let s = &r[0..2];                 // slice of array, s==&[3,4]
let s = &r[0..2][0];              // index into slice, s==3
let mut u:Vec<u8> = Vec::new();   // create empty vector of unsigned 8 bit int, can grow
let mut v = vec![3,4,5];          // initialize mutable vector using vec! macro
let w = vec![1,12,13];            // vectors can be immutable too
u.push( 2 );                      // append item to vector
u.pop();                      // vectors can return+remove last input (like a stack)
v.contains(&3);               // true if vector contains value
v.remove(1);                  // remove the nth item from a vector...
v.append(u);                  // append v with u (u becomes empty ([]), both mutable)
v.extend(w);                  // extend v with w (v owns w, w can be immutable)
v.resize(200,0);              // make vector have 200 elements, set them to 0
let x = &w[1..];              // get a slice of a vector (a view into it's elements)
print("{:?}",x);              // [12,13];
let vs = v.len();             // length of vector
let (p,d,q) = (4,5,6);        // tuple() can assign multiple variables at once
print("{}",p);                // you can use them alone after tuple assignment
let m = (4,5,"a");            // tuples can have multiple different types as elements
let (a,b) = m.0, m.2;         // tuple indexing with .0, .1, .2, etc
let (a,b) = m.p, m.q;         // error, cannot index into a tuple using a variable
let (x, y, z) = m;            // tuple destructuring into individual variables

let s = String::from("上善若水");  // String
let s2 = "水善利萬物而不爭";       // string literal, type is &str
let s3 = format!("{}{}",s2,s3);    // concatenate strings
for i in "말 한마디에 천냥 빚을 갚는다".split(" ") {print!("{}",i);} // split string
let s4 = s.get(0..2);              // Substring using indexes
let i4 = s.find('水').unwrap_or(-1); // find index of character (not a byte offset)
let hellomsg = r###"               // Multi-line with embedded quotes
 "Hello" in Chinese is 你好 ('Ni Hao')
 "Hello" in Hindi is नमस्ते ('Namaste')
"###;

usize, isize              // this is the pointer size. used in loops, vector length, etc

const MAX_SCORE: i32 = 100_000; // constant
static ORGOG: &str = "zormpf";  // static, global-ish variable
static FOOBY: i32 = 5;          // unsafely mutable
static Z_ERRMSG : [&str;2] = ["need input","need more input"]; // static strings

type Valid = bool;        // typedef ( make your own type names )

let mut v = vec![1u8,2u8,3u8];  // determine the type of expression expr by looking at rustc error
println!("{}",v.iter_mut());    // for example, if we want to know the type of v, build an error
12 |     println!("{}",v.iter_mut());   // type of v.iter_mut() is std::slice::IterMut<'_, u8>`
   |                   ^^^^^^^^^^^^ `std::slice::IterMut<'_, u8>` <- error line tells you the type

Operators

1 + 2 - 3 * 4 / 5  // arithmetic add, minus, multiply, divide
7 % 5              // modulo (remainder)
& | ^              // bitwise and, or, xor
<< >>              // leftshift, rightshift, will crash on overflow
// note that in C, overflowing << is actually undefined.
// Rust has multiple versions, each defined.
a.rotate_left(3)   // circular bit rotation, out of left -> in at right
a.wrapping_shl(3)  // this destroys the left-most bits that would cause overflow
a.overflowing_shl(4) // returns tuple (value,did_it_overflow)
a.rotate_right(4)  // circular bit rotation, wrapping around
!                  // bitwise not
a == b != c < d <= e > f >= g  // logical comparison
a && b || c ! d    // logical boolean, and, or, not

let a = 5;         // pointer + dereference example
let b = &a;        // &a is 'address of a' in computers memory
let c = *b;        // *b is contents of memory at address in b (dereference)
print("{}",c);     // 5

overloading: see struct

Run time errors, Crashing, panic, except, unwrap, Option, Result

panic!("oops");             // panic!() instantly crashes program
let v = vec![3,4,5];
let a = v[0];               // ok, normal lookup, a is 3
let b = v[12];              // will call panic! at runtime, v[12] doesn't exist
$ export RUST_BACKTRACE=1
$ cargo run    # will tell you exact line where panic occured, with call stack trace

Option - a basic way to deal with functions that might not work.

let c = v.get(12);          // this will not crash, c will instead be an Option
print!("{:?}",v.get(12));   // prints the word "None", Option can be None or Some
print!("{:?}",v.get(0));    // prints the word "Some(3)"
let e = v.get(0).unwrap();  // ok, 'unwrap' the Option returned by get(0), e is now 3
let d = v.get(12).unwrap(); // this crashes. 'unwrap' of a None Option will call panic!
let f = v.get(5).unwrap_or(&0); // unwrap_or gives a value if get() is None. f = 0

Result - is like Option but instead of Some and None, there is Ok() and Err():

// first define some function that returns Result... which means it
// returrns Err(u8) if something goes wrong, and Ok(&str) if it goes right.
fn calculate_blorg_level(some_data: &[u8]) -> Result<u8, &'static str> {
    match some_data[0] {
    	255=>Err("we cannot calculate blorg if data begins with 255"),
        _=>Ok((some_data[0]+some_data[1]*17).rotate_right(3)),
    }
}

// now we want to use this calculation function.
// sometimes we need to catch whether the Result is Err() or OK()
fn space_transmit_function() {
	match calculate_blorg_level([1,2,3]) {
		Err(why)=>{println!("Bad blorg, space transmit inoperative: {}",why);},
		Ok(n) => { begin_space_transmission(n); }
        }
}

// but other times we want to use the calculation function
// but it's OK if we don't have a perfect OK() result, we can
// assume it's 5 and use unwrap_or(5) if we get an Err()
fn pizza_transmit_function() {
	let m = calculate_blorg_level([7,8,9]).unwrap_or(5);
	set_pizza_delivery_level(m);
}

Note our Result can return two different things, a u8 or a str, but the u8 will be wrapped inside Ok() while the str would be wrapped inside an Err(). Not ‘catching’ a Result when you call calcualte_blorg_level() will show an error at compile time.

More examples with options:

let x = do_somthing_that_might_not_work(); // Option can help handle errors
match x {
	Some(x)=>println!("OK!"),
	None=>println!("sad face"),
}

if let Some(x) = do_something_that_might_not_work() {
	println("OK!");
} // if None, do nothing.

Option in particular can prevent the use of null pointers, preventing crashes one might see in C/C++.

struct Owlgr {
	name: String,
	fiznozz: Option<String>     // in C++ this might be a *char which could init as NULL
}

let owls = [Owlgr{name:"Harry".to_string(),fiznozz:None},
            Owlgr{name:"Tom".to_string(),fiznozz:Some("Zoozle".to_string())}];

for owl in &owls {
	match &owl.fiznozz {
		None=>println!("Owlgr named {} has no fiznozz!",owl.name),
		Some(x)=>println!("Owlgr named {} has fiznozz of {}",owl.name,x),
	}
    }
}

// note that we did not have to check for null pointers, nor derefernece any
// pointer. if we forgot to check for None, the compiler would give an error.

Note that there are no Exceptions. panic/Option/Result/multi-value-return are used instead.

Printing

println!("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ");
print!("Hi, the answer is {} ",42);           // variables replace {}

let v = vec![1,2,3];
println!( "v[0] is {:?} {}", v, v[0] )        // {:?} can print lots of special types
println!("{:02x?}",v);                        // {:02x?} can print 2 digit hex of vector
let s = format!( "x coord={}", p.X )          // print to string
s2 := fmt.Sprintf( "{e}", 17.0 )              // another way to print to string
println!("hex:{:x} bin:{:b} sci:{:e}",17,17,17.0); // hexadecimal, binary, etc.
// C-ish style results in:  "hex:11     bin:10001    sci:1.7e1"
println!("dec:{:#04} hex:{:#06x} bin:{:08b} sci:{:09e}",17,17,17,17.0);
// Pad with zeros: "dec:0017 hex:0x0011 bin:00010001 sci:00001.7e1"
println!(" {:.40} ", 1.0f32/3.0f32 );  // print 40 digits of precision for floating point
//  "0.3333333432674407958984375000000000000000"
println!(" {:>4} {:>4} ", 232, 8 );    // pad as columns, width 4 spaces, align right
//  " 232    8"
let mut s=String::new();               // build string, concatenate over lines
s.push_str(&format!("{} {} ",1,2));
s.push_str(&format!("{} {} ",3,4));
println!("{}",s);                      // 1 2 3 4

println!("\u{2766}");                  // ❦  unicode floral heart, character hex 2766

// derive Debug can make your own structs, enums, and unions printable by {:?}
#[derive(Debug)]
struct Wheel{radius:i8}
println!("{:?}",vec![Wheel{radius:4},Wheel{radius:3},Wheel{radius:2}]);
// [Wheel { radius: 4 }, Wheel { radius: 3 }, Wheel { radius: 2 }]

// If you want to customize your debug output, you can implement Debug yourself
use std::fmt;
struct Wheel{radius:i8}
impl fmt::Debug for Wheel {
  fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result{
    write!(f, "輪:徑[{}]", self.radius)
}}
println!("{:?}",vec![Wheel{radius:4},Wheel{radius:3},Wheel{radius:2}]);
// [輪:徑[4], 輪:徑[3], 輪:徑[2]]

// fmt::Display makes your own structs and enums printable with ordinary {} symbol
impl fmt::Display for Wheel{
  fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result{
    write!(f, "W[{}]", self.radius)
   }
}

// enums example
pub enum Apple{PinkLady,HoneyCrisp}
impl fmt::Display for Apple {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self { Apple::PinkLady=>write!(f, "Ap:PLad"),
                     Apple::HoneyCrisp=>write!(f, "Ap:HonCr"),
		     }
    }
}
//todo enum variants, containing data

loop, while and for

3 kinds of loops.

for i in 0..10 { print!("{},",x) };        // 0,1,2,3,4,5,6,7,8,9
for i in 0..10.rev() { print!("{},",x) };  // 9,8,7,6,5,4,3,2,1,0
for i in (0..10).step_by(2)      ;         // 0 2 4 6 8
for i in (0..10).skip(1).step_by(2);       // 1 3 5 7 9
for i in (0..10).rev().step_by(2);         // 9 7 5 3 1
for i in (0..=10).rev().step_by(2);        // 10 8 6 4 2 0
for i in (0..=10).step_by(2)     ;         // 0 2 4 6 8 10
for i in (0..9).rev().step_by(2) ;         // 8 6 4 2 0
for i in (0..9).step_by(2)       ;         // 0 2 4 6 8
for i in (0..10).cycle().skip(5).take(10)  // 5 6 7 8 9 0 1 2 3 4
let v = vec![1, 35, 64, 36, 26];	   // vector to iterate
for n in v { println!("{}",n) }		   // ordinary vector iterate
for (i, n) in v.iter().enumerate() {       // iterate with index and item
	println!("{},{} ", i, n);
}

let mut i = 10;				// while
while i > 0 {
	println("{}",i);
	i -= 2;   // if i actually goes negative, it will panic, subtraction overflow
}

let mut i = 0;                          // loop
loop { i=i+1; if i<10 { break; } };	// plain loop
let x = loop { i=i+1; if i>=10 { break i; } } // loop that returns value, x = 10

// While Let, can be used in situations where we expect Option::Some() for several iterations,
// but we expect a Option::None() to be at the end of the iterations. For example:
let mut x = (0..12).filter(|x| x%2==0);
while let Some(i) = x.next() {print!("{}:",i);}
// 0:2:4:6:8:10:   // prints the even numbers between 0 and 12

Concurrency, parallel processing

extern crate rayon;
use rayon::prelude::*;
fn main() {
    let mut v = Vec::new();  // create a vector of floats, to multiply each by 0.9
    for i in 0..1024*1280 { v.push(i as f32); }
    v.iter_mut().for_each(     |x| *x = *x * 0.9 ); // single thread version
    v.par_iter_mut().for_each( |x| *x = *x * 0.9 ); // multiple threads version

    very_slow_function1(); // two single threaded functions that take a long time
    very_slow_function2();

    rayon::join( || very_slow_function1()    // run them in parallel if appropriate
    		 || very_slow_function2() );
}
$ $EDITOR Cargo.toml  # add rayon dependency
[package]
name = "beatrixpotter"
version = "0.1.1"
authors = ["flopsy mopsy <ra@bb.it>"]
[dependencies]
rayon = "1.0.0"

$ cargo run          # installs rayon, runs program
channels: todo
mutex: todo
ARC: todo

Functions and closures

fn add( a:i8, b:i8 ) -> i32 { b + a }  // 'return' keyword optional
fn getcodes( a:i8, b:i32) -> (char,i32) { return ('s',a+b); } // multi return
let (x, s) = getcodes( 3, 56 );     // multi return via tuples
fn mulby6( a:i8, b:i8=5 ) -> i16 {} // error. Rust circa 2019 has no default parameters.
fn f(t:i8) {          // nesting functions is OK
  fn g(u:i8) { u*5 }
  let a = t + g(2);
}

// function pointers
fn addtwo(t:i8)->i8{t+2}; // simple function, adds 2 to argument.
println!("{}",addtwo(5)); // prints 7
let fp = addtwo;          // fp = function pointer to addtwo function
println!("{}",fp(5));     // now we can call fp() just like we called addtwo
fn f<F>(fp: F) where F: Fn(i8)->i8 { println!("{}",fp(1)) }
// 'where F:Fn' lets us build a function that can accept another function as an argument
f(fp);  // call function f, passing a pointer to the 'addtwo' function. result=3

// closures
let c = |x| x + 2;    // define a closure, which is kinda like a lambda function
fn f<F>(fp: F) where F: Fn(i8)->i8 { println!("{}",fp(1)) }  // f takes a function as an argument
f(c);                 // a closure can be passed, like a function pointer, result = 3
let value = 5;        // a closure can also read values outside its scope
f(|x| x * value);     // and a closure can be anonymous, without a name. result = 5

for i in 0..4.filter(|x| x>1) // closures are used often with iterators (see below)
print!("{} ",i)               // 2 3  (0 1 2 3 filtered to only values greater than 1)

type ZFillCallback = fn(bottom:u32,top:u32)->u32;  // typedef of a function

fn maximum(t:i8,...) {} // error, can't have variable number of arguments. see Macros! below

Unit tests, integration tests

Test functions are marked with the #[test] attribute.

Unit tests should be placed in the same file as the code being tested.

./src/lib.rs:

pub fn process(v:&mut Vec<u8>)->&Vec<u8>{ v.update(|x| f(x)) } // main function called by users
fn f(x:u8)->u8 { x*x }   // small piece of our code, to test in unit testing

#[cfg(test)]        // cfg -> section will only compiled during 'cargo test'
mod tests {         // namespace helper
    use super::*;   // bring in our functions above
    #[test]         // next function will be a single test
    fn test_f() { assert!(f(4)==16); } // test f() by itself (unit)
}

Integration tests, for overall crate, lives under ./tests/*.rs

./tests/file.rs:         // will only be built dring 'cargo test'
extern crate mypackage;  // include package we are testing
#test                    // treat next function as a test
fn bigtest() {           // not a unit test. instead, test overall code
	let mut d = vec![1,2,3];               // set up some typical data users would have
	let expected_results = vec![1,4,9];    // some results we expect
	assert!(process(d)==expected_results); // test what a user would typically call, process()
}
$ cargo test           # test build, will include cfg(test) sections
-> test_f passed       # cargo reports on passed tests
-> test_bigtest failed # cargo reports on failed tests

Documentation

rust-doc and cargo doc allow automatic building of html documentation for code. precede documentation of your code with three slashmarks instead of the normal two slashmarks, like so:

/// blorg() returns the blorgification of input x
/// # Details
/// this code implements the krishnamurthi procedure
/// for blimfication of zorgonautic primes
/// # Arguments
/// * `x` - typically a square number
/// # Safety
/// Cannot panic unless x overflows u64
/// # Example
///     let n = blorg(36);
fn blorg(x:u64)->u64 {
   x+x*x
}

Then run rust-doc or cargo doc and view the result.

$ cargo doc --open
$ firefox target/doc/cratename/index.html

If, conditionals, patterns, match, control flow


fn zorgnaught_level() -> i32 { 3 }

let (zoogle, noogle, poogle) = (3,4,5);

let x = zorgnaught_level();

if x == zoogle {                   // normal if else, like C, Pascal, etc
	print!("zoogle")
} else if x == noogle {
	print!("noogle")
} else if x == poogle {
	print!("poogle")
} else {
	print!("unknown zorgnaught level");
}

match x {                     // match... mostly works on literals not variables
	3 => print!("zoogle"),    // "=>" signifies a branch or leg of the match
	4 => print!("noogle"),    // have as many=> legs as you want
	5 => print!("poogle"),    // end each leg with a comma ,
	_ => print!("unknown zorgnaught level"), // underscore _ will match anything not previously matched
}

 // match... can work on enums too
pub enum Zorglvl{Zoogle,Noogle,Poogle}

fn zorgnaught_level2() -> Zorglvl { Zorglvl::Zoogle }

let x = zorgnaught_level2();

match x {
	Zorglvl::Zoogle => print!("zoogle"),    // "=>" signifies a branch or leg of the match
	Zorglvl::Noogle => print!("noogle"),    // have as many=> legs as you want
	Zorglvl::Poogle => print!("poogle"),    // end each leg with a comma ,
        // don't need underscore, enum checks all, covers all
}

let x = [1i8,2i8];                    // match patterns can be structs, enums, arrays, etc
let y = match x {                     // match can 'return a result' to y
        [1,0] => "1,0",               // match a specific array
        [2,0]|[4,0] => "2,0 or 4,0",  // |, binary or, can be used in patterns
        [_,2] => "ends with 2",       // [_,2] will match [0,2] [1,2] [2,2], [3,2], etc
        _ => "other"
};
println!("{}",y);                     // "ends with 2"


let m = 3;	 		     // match patterns can only be constant, but you can
let n = 4;                           // do similar things with "match guard", an if statement
let y = match (n,m) {                // which is inside of a => arm. First, we match tuple (n,m)
        (0,0) => "ok",               //  this leg matches any tuple with first element 0, return ok
        (3,_) if n%5==0 => "ok",     //  this leg matches when first element=3, and second divisible by 5
	(_,_) if m%3==0 => "ok",     //  this leg matches any tuple where the second element is divisble by 3
        _ => "stop",                 //  this leg matches anything else.
};

let hour = get_24hr_time();          // patterns can be integer ranges (x..=y)
ampm = match hour {                  // however it has to be inclusive
	0..=12 => "am"               // 1..12 is not ok, 1..=12 is ok.
	13..=23 => "pm"
	_=> "unknown"
};

let x = 5i32;                          // we can use the match value in match arms,
let y = match x-1 {                    // this is also called "binding with @", like so:
	0..=9 => 7,                    // since x is 5, x-1 is 4 and 4 is in 0..=9, so y=7
	m @ 10..=19 => m*2,            // if x-1 was 14, m becomes 14, so y would be 28
	_=> -1,                        // if x-1 was >=20, y would become -1
};

let mut v = vec![0;4096];                // match also works with Result<>
match File::open("/dev/random") {
	Ok(f)=>f.read(&v),
	Err(why)=>println!("file open failed, {}",why),
}

let v = vec![1,2,3];			// match works with Option too
let n = 1;
match v.get(n) {
	Some(x) => println!("nth item of v is {}",x),
	None => println!("v has no nth item for n={}",n),
};

let condition = true;
let number = if condition {     // if let, to conditionally assign
    5
}
else {
    "six"
}

See also: while let, if let.

Ownership, Borrowing, References, Lifetimes

Each value has exactly one owner. A value can be ‘moved’ from one owner to another. When the owner goes out of scope, the value is dropped; Rust implicitly calls the function drop on the heap value (such as a String) which takes care of returning the memory used.

This pattern of managing resource allocation around lifetime is known as Resource Acquistion Is Initialisation (RAII) in C++ circles.

// stack memory copies
let a = 5;
let b = a;  // ok
let c = a;  // ok

// heap memory moves
let s1 = String::new();
let s2 = s1;  // 'move' of ownership from s1 to s2
let s3 = s1;  // error. cannot "move" s1 again, s2 already owns it

// functions have same semantics as assignments
let s = String::from("gday");
takes_ownership(s);
let x = 5;
makes_copy(x);

fn takes_ownership(s: String) {   // heap memory is moved
    println!("{}", s);
}

fn makes_copy(n: i32) {   // stack memory is copied
    println!("{}", n);
}


// heap memory + function call
let s = String::from("gday");
let b = a;  // 'move' of ownership from a to b
fn f(t:String) { } // function takes ownership of the variable passed as t
f(a); // error. f(a) would move a into f(), but a was already moved into b

As seen above, s1 is moved to s2. This is how move semantics work. A String uses both stack memory (a pointer to heap, length and capacity) and heap memory (the actual string bytes). When s2 = s1 is evaluated, Rust copies the stack memory from s1 into s2, then marks s1 as invalid. This also highlights how Rust never copies heap memory automatically (i.e. never deep copies) and can always be trusted to be inexpensive in terms of runtime cost.

Borrowing is an alternative to moving. It is done with References & (memory addresses)

// heap memory, using borrows and references instead of moves
let a = String::from("☪☯ॐ✡γ⚕✝");
let b = &a;  // this is borrowing, not moving, a to b
let c = &a;  // it is OK to have more than one borrower
println!("{}",a);    // ☪☯ॐ✡γ⚕✝
println!("{:p}",&a); // 0x7ffcffb6b278
println!("{:p}",b);  // 0x7ffcffb6b278  // b and c hold the address of a
println!("{:p}",c);  // 0x7ffcffb6b278
println!("{:p}",&b); // 0x7ffcffb6b290  // b and c are distinct variables
println!("{:p}",&c); // 0x7ffcffb6b298  // with their own addresses

However borrowing has special rules regarding mutability.

In a block, only one of these statements can be true for a given resource R:

  • One or more references to R
  • Exactly one mutable reference to R

In other words, you can either have lots of readable references, or a single writable reference. This is a big point of struggle for new rustaceans. Don’t try and implement a doubly linked list (which requires multiple mutable reference to the same piece of memory). Embrace the fact Rust is forcefully helping you avoid data races.


// error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable
let mut a = 5;
let b = &a;
let c = &mut a;

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
let mut s = String::from("hello");
let r1 = &s;      // fine
let r2 = &s;      // fine
let r3 = &mut s;  // BIG problem

Lifetimes: TODO: read chapter 10

Resources are destroyed, (their heap memory is freed), at the end of a ‘scope’. Their owners are also destroyed. That is the point of ownership - so that resources won’t be accessed after they are destroyed, which is the source of a huge number of errors in C programs.

Borrowed resources are not destroyed when the borrowed reference itself goes out of scope. However the borrow cannot “outlive” the destruction of the original resource nor it’s owner.

Arrays, Slices, Ranges

array

let arr: [u8; 4] = [1, 2, 3, 4]; // immutable array. cant change size.
let mut arrm: [u8; 4] = [1,2,3,4]; // mutable array. cant change size.
let n = arr.len();     // length of array = 4 items
let s1 = &arr[0..2];   // slice of underlying array
let n2 = s1.len();     // length of slice = 2 items
println!("{:?}",s1);   // 1 2, contents of slice
let s2 = &arr[1..];    // slice until end
println!("{:?}",s2);   // 2 3 4 contents of slice until end
let sm = &mut arrm[0..2];  // mutable slice
sm[0] = 11;                // change element of mutable slice,
println!("{:?}",sm);       // 11 2 3 4
                           // underlying array was changed
println!("{:?}",arrm);     // 11 2 3 4

let z = [1,6,1,8,0,0,3];
println!("{:?}",z[0..4]);   // error - not a slice
^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
println!("{:?}",&z[0..4]);  // OK to take a slice
// 1,6,1,8

// pass array to function
fn dostuff(x:&mut [u8]) {
	x[0] = 5;
	println!("{:?}  {}",x,x.len()); // 5 2 3 4   4
}

fn main() {
	let mut arr: [u8; 4] = [1, 2, 3, 4];
	dostuff( &mut arr );
}

Structs

struct Wheel{ r:i8, s:i8};  // basic struct, like C, pascal, etc
struct badWheel{ mut r: i8, mut h: i8, }; // error, mut keyword doesnt work inside struct
let w = Wheel{r:5,s:7};   // new wheel, radius 5, spokes 7, immutable binding
w.r = 6; // error, cannot mutably borrow field of immutable binding
let mut mw = Wheel{r:5,s:7}; //  new mutable wheel. fields inherit mutability of struct;
mw.r = 6;  // ok

impl Wheel {              // impl -> implement methods for struct, kinda like a C++ class
        fn new(r: isize) -> Wheel { Wheel { r:r, s:4 } }  // our own default
        fn dump(    &self) { println!("{} {}",self.r,self.s); }  // immutable self
	fn badgrow(    &self) { self.s += 4; } // error, cannot mutably borrow field of immutable binding
        fn  okgrow(&mut self) { self.s += 4; } // ok, mutable self
};
w.dump();    // ok , w is immutable, self inside dump() is immutable
w.okgrow();  // error, w is immutable, self inside okgrow() is mutable
             //  cannot borrow immutable local variable `w` as mutable
mw.dump();   // ok, mw is mutable, self inside dump is immutable.
mw.okgrow(); // ok, mw is mutable, self inside grow() is mutable.

#[derive(Default,Copy,Clone,Debug,PartialEq)] // automatic implementations
struct Moo {x:u8,y:u8,z:u8,}
let m1:Moo=Default::default();            // Default, x=0 y=0 z=0
let m2:Moo=Moo{x:3,..Default::default()}; // Default, x=3 y=0 z=0
let (mut n,mut k)=(M{x:-1,y:-1,z:-1},Default::default());
n=k;                          // Copy
vec![M{x:0,y:1,z:2};42];      // Clone
println!("{:?}",n);           // Debug, formatter
if n==k {println!("hi");};    // PartialEq, operator overloading

// customized operator overloading
impl PartialEq for Wheel{ fn eq(&self,o:&Wheel)->bool {self.r==o.r&&self.s==o.s} }
if mw == w { print!("equal wheels"); }

#[derive(Debug)]                       // Initialize one struct from another
struct Apple {color:(u8,u8,u8),price:f32};
let a = Apple{color:(100,0,0),price:0.2};
let b = Apple{color:(9,12,38),..a };      // this is called "struct update"

Enums

enum Fruit { Apple, Banana, Pear }
let x = call_some_function( Fruit::Apple );
enum Errs { ErrOK = 0, ErrFile = 1, ErrFire = 2, ErrBadCap = 3 }  // enums can have integers

enum Blorg {     // enums can have different types as members
 Flarg(u8),
 Blarg(u32),
 Norg(String),
 Florg(bool)
}

let x = Blorg::Flarg(1); // enums can be detected with a match
match x {
    Blorg::Flarg(1) => println!("x is a Flarg with value of 1!"),
    Blorg::Flarg(_) => println!("x is a Flarg with non-1 value"),
    Blorg::Norg(_) => println!("x is a Norg"),
    _ => println!("neither Flarg nor Norg"),
}

// Enums can also derive traits, kind of like structs
#[derive(Clone,Debug,PartialEq)]  // cannot derive Default on enum
enum ColorMapData {
    OneByteColor(Vec<u8>),
    FourByteColor(Vec<u32>),
}
// Enums can also have methods, kind of like structs
impl ColorMapData {
  fn description(&self)->String {
    let (numcolors,numbytes) = match self {
      ColorMapData::OneByteColor(x) => (x.len(),"1"),
      ColorMapData::FourByteColor(x) => (x.len(),"4"),
    };
    format!("ColorMap with {} colors, {} bytes per color",numcolors,numbytes)
  }
}
// Enums can be used to create variables
let ca = ColorMapData::FourByteColor(vec![0xFFAA32FFu32,0x00AA0011,0x0000AA00]);
println!("{}",ca.description()); // ColorMap with 3 colors, 4 bytes per color
let mut cb = ColorMapData::OneByteColor(vec![0,1,3,9,16]);
println!("{}",cb.description()); // ColorMap with 5 colors, 1 bytes per color

Collections, Key-value pairs, Sets

HashMap, aka associative array / key-value store / map

let mut m = HashMap::new();
m.insert('a', 1);                   // key is 'a', value is 1
let b = m[&'a'];                    // [] lookup, this crashes at runtime if 'a' is not in map
let c = m.get(&'a').unwrap_or(&-1); // .get(), c == -1 if a is not in map. no crash.
match m.get(&'a') {                 // deal with map get() lookup using Match + Option
    Some(x)=>println!("a found in map, value is {}",x),
    None=>println!("a not found in map"),
}
if let Some(x) = m.get(&'a') {     // deal with map get() lookup using if let + Option
    println!("a found in map, value is {}",x);
}  // if 'a' is not in map, do nothing

*m.get_mut(&'a').unwrap() += 2;  // change a value inside a map

There are no hashmap literals, but you can make your own macro like Shepmaster, on StackOverflow, click here

HashSet

use std::collections::HashSet;
let mut squares = HashSet::new();
squares.insert(0);
squares.insert(4);
let b = squares.contains(&4);   // b==true

Macros

Does not act like a preprocessor. It replaces items in the abstract syntax tree

macro_rules! hello {
    ($a:ident) => ($a = 5)
}
let mut a = 0;
println!("{}",a); // 0
hello!(a);
println!("{}",a); // 5

macro_rules! bellana {
    ($a:expr,$b:expr) => ($a + $b)
}
println!("{:?}",bellana!(5,(9*2))); // 23

macro_rules! maximum {
    ($x:expr) => ($x);
    ($x:expr, $($y:expr),+) => ( std::cmp::max($x, maximum!($($y),+)) )
}
maximum!(1,2,3,4);   // 4

macro_rules! dlog {
    ($loglevel:expr, $($s:expr),*) => (
        if DLOG_LEVEL>=$loglevel { println!($($s),+); }
    )
}
let DLOG_LEVEL=5;
dlog!(4,"program is running, dlog:{}",DLOG_LEVEL);  // "program is running, dlog:5"

/*
designators:
block   // rust block, like {}.        expr    // expressions
ident   // variable/function names.    item    //
pat     // pattern.                    path    // rust path
stmt    // statement.                  tt      // token tree
ty      // type.                       vis     // visibility qualifier
*/

Little programs

Guess a number

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess");

        let mut guess = String::new();
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("too small!"),
            Ordering::Greater => println!("too big!"),
            Ordering::Equal => {
                println!("you got it!");
                return;
            }
        }
    }
}

Cool references