Skip to content

Basis

Ref: Rust语言圣经

1. Hello, World!

  • println! is a macro defined in std::fmt.
fn greet_world() {
     let southern_germany = "Grüß Gott!";
     let chinese = "世界,你好";
     let english = "World, hello";
     let regions = [southern_germany, chinese, english];
     for region in regions.iter() {
             println!("{}", &region);
     }
 }

 fn main() {
     greet_world();
 }

2. Variables

2.1 bindings

  • declare variable bindings and initialize them

  • immutable by default; declare mut for mutable ones

    differs from const: can be initialized later; type annotation is not necessary

  • shadowing: re-declare bindings

fn main() {
    let (a, mut b): (bool,bool) = (true, false);
    // a = true,不可变; b = false,可变
    println!("a = {:?}, b = {:?}", a, b);
    b = true;
    assert_eq!(a, b);
}
fn main() {
    let mut x = 5_i32;
    let mut x: i32 = x + 1;
    let y;

    x *= 2;
    y = x.pow(2);

    println!("x is {}, y is {}", x, y);

    const Z_NUM: i32 = 1000;
    println!("z^2 is {}", Z_NUM.pow(2));
}

2.2 Types

2.2.1 Tips

  • NO implicit conversion!

    i32 for integers by default

  • overflow/nan checking:

    // pub fn overflowing_add_signed(self, rhs: i32) -> (u32, bool)
    assert_eq!((u32::MAX - 2).overflowing_add_signed(4), (1, true));
    
    let x = (-42.0_f32).sqrt();
    if x.is_nan() {
        println!("Error!")
    }
    
  • type conversion:

    let b: u16 = 100;
    (b as i32)
    

  • Comparing by traits : std::cmp::PartialEq ; operators can be used to trigger them.

    eq assumes: symmetry, transitivity and reflexivity

    #![allow(unused)]
    fn main() {
    enum BookFormat {
        Paperback,
        Hardback,
        Ebook,
    }
    
    struct Book {
        isbn: i32,
        format: BookFormat,
    }
    
    impl PartialEq for Book {
        fn eq(&self, other: &Self) -> bool {
            self.isbn == other.isbn
        }
    }
    
    let b1 = Book { isbn: 3, format: BookFormat::Paperback };
    let b2 = Book { isbn: 3, format: BookFormat::Ebook };
    let b3 = Book { isbn: 10, format: BookFormat::Paperback };
    
    assert!(b1 == b2);
    assert!(b1 != b3);
    }
    
  • range: 1..5 , 1..=5

    for i in 1..=5 {
        println!("{}",i);
    }
    
  • use num lib:

    # in Cargo.toml
    [dependencies]
    num = "0.4.0"
    num-bigint = "0.4"
    
    use num::{complex::Complex, traits::Pow};
    use num_bigint::{BigInt, ToBigInt};
    
    fn main() {
        let a = Complex { re: 2.1, im: -1.2 };
        let b = Complex::new(11.1, 22.2);
        let result = a + b;
        println!("{} + {}i", result.re, result.im);
    
        // explicit type annotation needed for calling impls
        let x = 0x7fffffff_i32.to_bigint().unwrap();
        let y = 0x7fffffff_i32.to_bigint().unwrap();
        let z: BigInt = x + y + 2;
        println!("{}", z.pow(4_u32));
    }
    
  • Unicode supported

    let x: char = '我';
    println!("{}", std::mem::size_of_val(x)); // 4
    
  • unit type () as a placeholder

  • &str is immutable; use String for mutable strings:

    fn main() {
        let mut s = String::from("hello");
        s.push_str(", world!");
        println!("{}", s); // hello, world!
    }
    
  • enum can have data:

    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    
  • None in Option<T> and match

    enum Option<T> {
        Some(T),
        None,
    }
    
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1), // i takes the value inside Some!
        }
    }
    
  • array

    let a: [i32; 5] = [1, 2, 3, 4, 5];
    let b = [3; 5]; // [3,3,3,3,3]
    

2.2.2 Vector

let v: Vec<i32> = Vec::with_capacity(10);
// get returns Option<&T>
match v.get(2) {
    Some(third) => println!("The 3rd one is {}", third),
    None => println!("404 Not Found"),
}

2.2.3 HashMap

  • insert may transfer the ownership if the copy trait is not implemented
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert("key01", 1);
let number: Option<&i32> = map.get("1");
for (key, value) in &map {
    println!("{}: {}", key, value);
}
let number: &mut i32 = map.entry("100").or_insert(100); // insert if not existed
*number = 56; // "100": 56

build HashMap from Vector by collect:

let vec = vec![
    ("1", 1),
    ("2", 2),
];
let map: HashMap<_, _> = vec.into_iter().collect();
// println!("vec: {:?}", vec); // illegal: vec is borrowed by into_iter
println!("map: {:?}", map);

3. Statement

Statements and expressions are not the same!

  • statements have no value
  • expressions have values; they cannot contain ;

4. Function

img

  • must annotate types for each argument

  • "no return value" is equivalent to returning ()

  • functions that NEVER return "return" a !

    fn dead_end() -> ! {
        panic!("你已经到了穷途末路,崩溃吧!");
    }
    

5. Ownership

5.1 Basis

  • every value has only one owner
  • a value will be freed when its owner is not in opening scopes
  • C++ behavior std::move by default! including assign, pass args or return values

Transferring ownership is analog to std::move (e.g. String is stored in heap; it doesn't has copy trait!):

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 is invalid after this
    println!("{}, world!", s1); // illegal!

    let x = String::from("abc");
    let y = x.clone(); // deep copy
    println!("{}, {}", x, y); // legal
}

5.2 Borrowing

  • Only one mutable reference or any number of immutable references can be valid at the same time.

  • References are valid until they are not be used.

    The compiler will compute the smallest range for it.

    DIFFERENT from variable bindings!

  • Use mutable ref. referring to a mutable ref. to change the value of the referred one:

    let mut a = String::from("hello");
    let mut b = String::from("world");
    let mut c = &mut a; // make a mutable by ref
    *c = b.clone();
    c = &mut b; // thanks to c's mutability; make b mutable by ref
    *c = "hello".to_string();
    println!("a: {}", a); // world
    println!("b: {}", b); // hello
    
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", cal_len(&s));
}

fn change(some_string: &mut String) {
    // deref traits enables direct using of a reference when calling a method of the object
    some_string.push_str(", world");
}

fn cal_len(s: &String) -> usize {
    s.len()
}

Non-Lexical Lifetimes (NLL)

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; 
    let r2 = &s; 
    println!("{} and {}", r1, r2);
    // r1, r2 are invalid at this point

    let r3 = &mut s; 
    println!("{}", r3);
    // r3 is not invalid at this point
}

6. Flow Control

6.1 for

使用方法 等价使用方式 所有权
for item in collection for item in IntoIterator::into_iter(collection) 转移所有权至 for (后续无法使用)
for item in &collection for item in collection.iter() 不可变借用
for item in &mut collection for item in collection.iter_mut() 可变借用(元素可以被修改)

Iterate index and value simultaneously:

let a = [ 1, 2, 3 ];
for (i, v) in a.iter().enumerate() {
  continue;
}

for item in &collection is more efficient than visit elements in a collection by index, because the later one always needs bounds checking.

6.2 loop

Returning from loop:

fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2; // return by break
        }
    };
    assert_eq!(result, 20);
}

7. Patterns and Matching

7.1 match arms

match some_u8_value {
    1 | 2 => println!("one or two"),
    3..5 => println!("three to four"),
    _ => (),
}

Shadowing inside match:

fn main() {
   let age = Some(30);
   println!("在匹配前,age是{:?}",age);
   if let Some(age) = age {
       // here age is i32 but not Some(i32)
       println!("匹配出来的age是{}",age);
   }

   println!("在匹配后,age是{:?}",age);
}

7.2 if let

Actually, let is a keyword for "pattern binding".

if makes it possible that except the written case, other cases can be ignored.

Only match one case:

if let Some(3) = v {
    println!("three");
}

let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

if let can be used together with else if:

if let Some(color) = favorite_color {
    println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
    println!("Tuesday is green day!");
} else if let Ok(age) = age {
    if age > 30 {
        println!("Using purple as the background color");
    } else {
        println!("Using orange as the background color");
    }
} else {
    println!("Using blue as the background color");
}

7.3 matches! (macro)

matches! returns true or false:

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));

7.4 while let

let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

// pop returns Option<T>
// loop break when it returns a None, which causes fail matching
while let Some(top) = stack.pop() {
    println!("{}", top);
}

7.5 deconstruction and matching

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
    // less verbose
    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

Use .. to skip some values:

let numbers = (2, 4, 8, 16, 32);
match numbers {
    (first, .., last) => {
        println!("Some numbers: {}, {}", first, last);
    },
}

7.6 match guard

if condition after match arms:

let x = 4;
let y = false;

match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"), // the matched one due to y is false
}

7.7 @ binding

var @ pattern: bind the value matching the pattern to var for later use.

enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    Message::Hello { id: id_variable @ 3..=7 } => {
        println!("Found an id in range: {}", id_variable)
    }
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    }
    // parenthese outside the pattern are required!
    hello @ (Message::Hello { id: 10..=12 } | Message::Hello { id: 13..=15 }) => {
        println!("Found an id in another range when hello is {:?}", hello)
    }
    Message::Hello { id } => {
        println!("Found some other id: {}", id)
    }
}

8. Method

pub struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(w: u32, h: u32) -> Rectangle {
        Rectangle { width: w, height: h }
    }
    // getter
    pub fn width(&self) -> u32 {
        return self.width;
    }
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

9. Generics

e.g.

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Restricting type parameter T to what can be added by + is needed:

fn add<T: std::ops::Add<Output = T>>(a:T, b:T) -> T {
    a + b
}

e.g.

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

"Changeable reference":

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest: &T = &list[0];

    for item: &T in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}

10. Trait

  • a collection of functions
  • trait object as a type: Box<dyn MyStruct> or &dyn MyStruct
trait Draw {
    fn draw(&self) -> String;
}

struct Screen {
    // trait object
    components: Vec< Box<dyn Draw> >,
}

impl Draw for u8 {
    fn draw(&self) -> String {
        format!("u8: {}", *self)
    }
}

impl Draw for f64 {
    fn draw(&self) -> String {
        format!("f64: {}", *self)
    }
}

impl Screen {
    fn run(&self) {
        for comp in &self.components {
            println!("{}", comp.draw());
        }
    }
}

fn main() {
    let a = 4_u8;
    let b = 6.54_f64;
    let screen = Screen {
        components: vec![
            Box::new(a),
            Box::new(b),
        ]
    };
    screen.run();
}

11. Error handling

panic!("crash and burn");

// unwarp: Ok -> get the value; Err -> panic
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();

// expect: output something when panic
use std::fs::File;
let f = File::open("hello.txt").expect("Failed to open hello.txt");

Use ? to spread errors:

// Result<String, Box<dyn std::error::Error>>
fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?; // chain calling
    Ok(s)
}

Use ? to spread None:

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

Last update: March 2, 2022
Authors: Co1lin