Rust Note

Installation

Install Rust

bash
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

Update Rust

bash
rustup update

Uninstall Rust

bash
rustup self uninstall

IDE

Vscode

  • rust-analyzer: A fast, featureful language server for Rust

  • CodeLLDB: Native debugger for Rust and other languages

  • Even Better TOML: Better TOML support

  • Error Lens: Improve highlighting of errors, warnings and other language diagnostics

Cargo

  • cargo new project-name: Create a new project

  • cargo build: Build the project, --release for release build

  • cargo run: Run the project, --release for release build

  • cargo check: Check the project

  • Cargo.toml: Project configuration file

  • Cargo.lock: Dependency lock file

Basic Syntax

Variable

  • variable binding

    rust
    let x = 5;
  • mutable variable

    rust
    let mut x = 5;
  • never used variable

    rust
    let _x = 5; // can use _ to ignore the warning
  • variable destructuring

    rust
    let (x, y) = (1, 2);
  • variable shadowing

    rust
    let x = 5;
    let x = x + 1;
  • constant

    rust
    const MAX_POINTS: u32 = 100_000;    // can't use mut and must be type annotated

Data Type

Scalar Type

  • integer

    LengthSignedUnsigned
    8-biti8u8
    16-biti16u16
    32-biti32u32
    64-biti64u64
    128-biti128u128
    archisizeusize
    • integer overflow

      rust
      let x: u8 = 255;
      let y = x + 1;  // if debug build, panic; if release build, two's complement wrapping
  • float

    LengthType
    32-bitf32
    64-bitf64
    • avoid comparing floats
  • NaN: to represent the result of an operation that can't return a valid value

    rust
    let x = 0.0 / 0.0;  // NaN, you can use is_nan() to check

Math Operation

  • addition

    rust
    let sum = 5 + 10;
  • subtraction

    rust
    let difference = 95.5 - 4.3;
  • multiplication

    rust
    let product = 4 * 30;
  • division

    rust
    let quotient = 56.7 / 32.2;
  • remainder

    rust
    let remainder = 43 % 5;

Logical Operation

  • and

    rust
    let x = true && false;
  • or

    rust
    let x = true || false;
  • not

    rust
    let x = !true;
  • bit

    rust
    let x = 1 | 2;  // 3
    let x = 1 & 2;  // 0
    let x = 1 ^ 2;  // 3
    let x = 1 << 2; // 4
    let x = 4 >> 2; // 1
    let x = !1;     // -2
  • Range

    rust
    let x = 1..=5; // 1, 2, 3, 4, 5
    let x = 1..5;  // 1, 2, 3, 4
  • Use As to Convert Data Type

    rust
    let x: i32 = 5;
    let y = x as i64;

Character、Boolean、Unit Type

  • character

    rust
    let x = 'z';
    let y = '😻';
    let z = ''
    println!("{}", std::mem::size_of_val(&z)); // 4
    //  char use `'` and string use `"`
  • boolean

    rust
    let x = true;
    let y: bool = false;
  • unit type

    rust
    let x = (); // usually used as placeholder, not consume memory

Statement and Expression

  • statement

    rust
    let x = 5; // `let x = 5` is a statement
  • expression

    rust
    let y = {
        let x = 3;
        x + 1 // `x + 1` is an expression, expression don't include ending semicolon
    };

Function

  • function definition

    rust
    fn add(x: i32, y: i32) -> i32 {
        x + y
    }
    1. use snake case for function and variable names
    2. any position of the function definition is ok
    3. use -> to specify the return type
    4. every parameter must have a type annotation
    5. return value is the last expression in the function body
    6. use return to return early
    7. use ; to return ()
    8. ! is the never type, represents a function that never returns

Ownership

Move and Clone

  • principle

    1. each value in Rust has a variable that's called its owner
    2. there can only be one owner at a time
    3. when the owner goes out of scope, the value will be dropped
  • stack and heap

    1. stack: LIFO, fixed size, fast
    2. heap: dynamic size, slower
  • ownership move

    rust
    let s1 = String::from("hello");
    let s2 = s1; // s1 is invalid, rust will directly copy basic data type, but String is not basic data type, so the ownership of s1 is moved to s2
  • deep copy

    rust
    let s1 = String::from("hello");
    let s2 = s1.clone(); // deep copy, use a lot of resources
  • shallow copy

    any type implement Copy trait can use shallow copy. This include all the combination of basic data type and types that do not require memory or resource allocation.

  • ownership and function

    rust
    fn take_ownership(s: String) {
        println!("{}", s);
    }
    
    fn main() {
        let s = String::from("hello");
        take_ownership(s); // s is invalid
    }
    rust
    fn make_copy(x: i32) {
        println!("{}", x);
    }
    
    fn main() {
        let x = 5;
        make_copy(x); // x is valid
    }
  • ownership and return value

    rust
    fn return_ownership() -> String {
        let s = String::from("hello");
        s
    }
    fn main() {
        let s1 = return_ownership(); // ownership is moved to s1
    }
    rust
    fn return_ownership() -> String {
        let s = String::from("hello");
        &s
    }
    fn main() {
        let s1 = return_ownership(); // s1 is invalid, s is dropped
    }

Reference and Borrowing

  • reference

    rust
    let x = 5;
    let y = &x; // y is a reference to x
    
    assert_eq!(5, x);
    assert_eq!(5, *y); // dereference
  • immutable reference

    rust
    fn calculate_length(s: &String) -> usize {
        s.len()
    }
    fn main() {
        let s = String::from("hello");
        let len = calculate_length(&s);
    }
  • mutable reference

    rust
    fn change(s: &mut String) {
        s.push_str(", world");
    }
    fn main() {
        let mut s = String::from("hello");
        change(&mut s);
    }
    1. In the same scope, you can only have one mutable reference to a particular piece of data.
    2. mutable reference can't coexist with immutable reference.
    3. the scope of the reference is the last time it is used.
  • Non-lexical Lifetimes: used to find the lifetime of the reference

    rust
    fn main() {
        let r;
        {
            let x = 5;
            r = &x;
        }
        println!("{}", r); // error, x is dropped
    }
  • Dangling Reference: a reference that refers to a location in memory that may have been given to someone else, so the data has been deallocated

    rust
    fn dangle() -> &String {
        let s = String::from("hello");
        &s
    }

Compound Data Type

String

  • create

    rust
    let s = String::from("hello");
  • update

    rust
    let mut s = String::from("hello");
    s.push_str(", world");
    s.push('!')
  • insert

    rust
    let mut s = String::from("hello");
    s.insert(5, ',');
    s.insert_str(6, " world");
  • replace

    rust
    let mut s = String::from("hello, world");
    s.replace("hello", "hi");
  • replacen

    rust
    let mut s = String::from("hello, world");
    let s1 = s.replacen("hello", "hi", 1);
  • replace_range

    rust
    let mut s = String::from("hello, world");
    s.replace_range(0..5, "hi");
  • pop

    rust
    let mut s = String::from("hello");
    let x = s.pop(); // x is Some('o')
  • remove

    rust
    let mut s = String::from("hello");
    let x = s.remove(0); // x is 'h'
  • truncate

    rust
    let mut s = String::from("hello");
    s.truncate(3); // s is "hel"
  • clear

    rust
    let mut s = String::from("hello");
    s.clear(); // s is ""
  • concatenate

    rust
    let s1 = String::from("hello");
    let s2 = String::from("world");
    let s3 = s1 + &s2; // s1 is invalid
    let s4 = format!("{}-{}", s1, s2); // s1 is valid
  • slice

    rust
    let s = String::from("hello, world");
    let hello = &s[0..5];
    • use &s[..] to get the whole string
  • the index should be valid UTF-8 character boundary

    rust
      let s = String::from("你好");
      let s1 = &s[0..1]; // error
      let s2 = &s[0..3]; // ok
    • Char is Unicode type in Rust that is 4 bytes, but Char in String is UTF-8, so the length of the string is the number of bytes, not the number of characters, a character may be 1-4 bytes.
  • convert between string and &str

    rust
    let s = String::from("hello"); // "hello".to_string()
    let s1: &str = &s;
  • Escape Characters

    rust
    let s = "hello\nworld";
    let s = r"hello\nworld"; // raw string
    let s = r#"hello "world""#; // include "

operate UTF-8 String

  • char

    rust
    let s = "你好";
    let c = s.chars().nth(0); // Some('你')
  • bytes

    rust
    let s = "你好";
    let b = s.bytes().nth(0); // Some(228)
  • traverse

    rust
    let s = "你好";
    for c in s.chars() {
        println!("{}", c);
    }

Tuple

  • create

    rust
    let x: (i32, f64, u8) = (500, 6.4, 1);
  • destructure

    rust
    let (x, y, z) = x;
  • access

    rust
    let x = x.0;
  • return multiple values

    rust
    fn get_point() -> (i32, i32) {
        (3, 5)
    }

Struct

  • define and create

    rust
    struct User {
        username: String,
        email: String,
        sign_in_count: u64,
        active: bool,
    }
    let user = User {
        username: String::from("someone"),
        email: String::from("[email protected]")
        sign_in_count: 1,
        active: true,
      };
  • simple create

    rust
     fn build_user(username: String, email: String) -> User {
        User {
            username,
            email,
            sign_in_count: 1,
            active: true,
        }
      }

    when the field name and the variable name are the same, you can use the shorthand

  • access

    rust
    let username = user.username;
  • update

    rust
    let user2 = User {
        username: String::from("someone2"),
        ..user
    };
  • Tuple Struct

    rust
    struct Color(i32, i32, i32);  // no field name
    let black = Color(0, 0, 0);
  • Unit-Like Struct

    rust
    struct AlwaysActive;
    let always_active = AlwaysActive; // we don't care about the data, just the behavior
    impl AlwaysActive {
        fn is_active(&self) -> bool {
            true
        }
    }
  • Print Struct

    rust
    #[derive(Debug)]
    struct User {
        username: String,
        email: String,
        sign_in_count: u64,
        active: bool,
    }
    
    println!("{:?}", user);       // print to standard stdout
    println!("{:#?}", user);      // pretty print
    dbg!(user);                   // Macro, print and return the value to stderr

Enum

  • define

    rust
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }
  • create

    rust
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
  • Assimilation Type

    rust
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
  • Option

    rust
      enum Option<T> {
          Some(T),
          None,
      }
    
      let x = Some(5);
      let s = Some("hello")
      let y: Option<i32> = None;      // must specify the type when use None

Array & Vector

  • define

    rust
    let a = [1, 2, 3, 4, 5];
    let a: [i32; 5] = [1, 2, 3, 4, 5];
    let a = [3; 5]; // [3, 3, 3, 3, 3]
  • from_fn

    rust
    let arr: [String; 8] = std::array::from_fn(|_i| String::from("hello"));
  • access

    rust
    let first = a[0];
  • slice

    rust
    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3]; // [2, 3]

Control Flow

if

  • if

    rust
    let x = 5;
    if x < 5 {
        println!("less than 5");
    } else if x == 5 {
        println!("equal to 5");
    } else {
        println!("greater than 5");
    }

loop

  • for

    rust
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("{}", element);
    }
    for idx in 0..5 {
        println!("{}", a[idx]);
    }
    MethodEquivalentOwnership
    for item in collectionfrom item in IntoIterator::into_iter(collection)collection is moved
    for item in &collectionfrom item in collection.iter()collection is borrowed
    for item in &mut collectionfrom item in collection.iter_mut()collection is mutably borrowed
  • enumerate

    rust
    let a = [10, 20, 30, 40, 50];
    for (idx, element) in a.iter().enumerate() {
        println!("{}: {}", idx, element);
    }
  • continue

    rust
    for number in 1..=100 {
        if number % 3 == 0 {
            continue;
        }
        println!("{}", number);
    }
  • while

    rust
    let mut number = 3;
    while number != 0 {
        println!("{}", number);
        number -= 1;
    }
  • loop

    rust
    let mut number = 3;
    loop {
        println!("{}", number);
        number -= 1;
        if number == 0 {
            break;
        }
    }

Pattern Matching

match

  • match

    rust
    let number = 3;
    match number {
        1 => println!("one"),
        2 | 3 => println!("two or three"),
        4..=10 => println!("four to ten"),
        _ => println!("other"),
    }
  • pattern destructuring

    rust
    let x = Some(5);
    match x {
        Some(i) => println!("{}", i),
        None => (),
    }
  • matches!

    rust
    v.iter().filter(|x| matches!(x, Some(i) if i > 2)).count()

if let

  • if let

    rust
    let x = Some(5);
    if let Some(i) = x {
        println!("{}", i);
    }

    when you only care about one pattern and ignore the rest, you can use if let

while let

  • while let

    rust
    let mut v = vec![Some(1), Some(2), Some(3)];
    while let Some(i) = v.pop() {
        println!("{}", i);
    }

Pattern Matching Examples

Method

  • define

    rust
    struct Rectangle {
        width: u32,
        height: u32,
    }
    
    impl Rectangle {
        new(width: u32, height: u32) -> Rectangle {
            Rectangle { width, height }
        }
    
        fn area(&self) -> u32 {
            self.width * self.height
        }
    }
    println!("The area of the rectangle is {} square pixels.", rect.area());
  • self

    1. &self: borrow self
    2. &mut self: mutable borrow self
    3. self: take ownership of self
  • related function

    rust
    impl Rectangle {
        fn square(size: u32) -> Rectangle {
            Rectangle { width: size, height: size }
        }
    }
    let sq = Rectangle::square(3);
  • enum method

    rust
    enum Option<T> {
        Some(T),
        None,
    }
    
    impl<T> Option<T> {
        fn unwrap(self) -> T {
            match self {
                Some(val) => val,
                None => panic!("called `Option::unwrap()` on a `None` value"),
            }
        }
    }
    
    let x = Some(5);
    let v = x.unwrap();

Generics

Generics

  • define

    rust
    struct Point<T> {
        x: T,
        y: T,
    }
    
    impl<T> Point<T> {
        fn x(&self) -> &T {
            &self.x
        }
    }
    
    let p = Point { x: 5, y: 10 };

    the compiler will generate the code for each type that is used in the code(single monomorphization)

  • use multiple types

    rust
    struct Point<T, U> {
        x: T,
        y: U,
    }
    
    let p = Point { x: 5, y: 10.4 };
  • use in enum

    rust
    enum Result<T, E> {
        Ok(T),
        Err(E),
    }
    
    enum Option<T> {
        Some(T),
        None,
    }
  • use in function

    rust
    fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
        let mut largest = list[0];
        for &item in list.iter() {
            if item > largest {
                largest = item;
            }
        }
        largest
    }
  • define method for specific type

    rust
    trait Summary {
        fn summarize(&self) -> String;
    }
    
    impl Summary for i32 {
        fn summarize(&self) -> String {
            format!("i32: {}", self)
        }
    }
    
    let x = 5;
    println!("{}", x.summarize());

Const Generics

  • define

    rust
    struct Array<T, const N: usize> {
        data: [T; N],
    }
    
    let a = Array { data: [1, 2, 3] };

Trait

  • define

    rust
    trait Summary {
        fn summarize(&self) -> String;
    }
    
    struct NewsArticle {
        headline: String,
        location: String,
        content: String,
    }
    
    impl Summary for NewsArticle {
        fn summarize(&self) -> String {
            format!("{}, by {} ({})", self.headline, self.location, self.content)
        }
    }
  • orphan rule: if a trait is defined for a type, the type or trait must be defined in the current crate at least one of them

  • default implement

    rust
    trait Summary {
        fn summarize(&self) -> String {
            String::from("Read more...")
        }
    }
    
    impl Summary for NewsArticle {}
    
    article.summarize();
  • use trait as parameter

    rust
    fn notify(item: impl Summary) {
        println!("Breaking news! {}", item.summarize());
    }
    
    fn notify<T: Summary>(item: T) {
        println!("Breaking news! {}", item.summarize());
    }
  • multiple trait bounds

    rust
    fn notify(item: impl Summary + Display) {
        println!("Breaking news! {}", item.summarize());
    }
  • where clause

    rust
    fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
        0
    }
    
    fn some_function<T, U>(t: T, u: U) -> i32
    where
        T: Display + Clone,
        U: Clone + Debug,
    {
        0
    }
  • use trait to implement method

    rust
    struct Pair<T> {
        x: T,
        y: T,
    }
    
    impl<T> Pair<T> {
        fn new(x: T, y: T) -> Self {
            Self { x, y }
        }
    }
    
    impl<T: Display + PartialOrd> Pair<T> {
        fn cmp_display(&self) {
            if self.x >= self.y {
                println!("The largest member is x = {}", self.x);
            } else {
                println!("The largest member is y = {}", self.y);
            }
        }
    }
  • return trait

    rust
    fn returns_summarizable() -> impl Summary {
        NewsArticle {
            headline: String::from("Penguins win the Stanley Cup Championship!"),
            location: String::from("Pittsburgh, PA, USA"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                hockey team in the NHL.",
            ),
        }
    }
  • examples

    rust
      use std::ops::Add;
    
      // use derive to implement Debug trait
      #[derive(Debug)]
      struct Point<T: Add<T, Output = T>> { // T must implement Add trait
          x: T,
          y: T,
      }
    
      impl<T: Add<T, Output = T>> Add for Point<T> {      // implement Add trait for Point
          type Output = Point<T>;     // define associated type
    
          fn add(self, p: Point<T>) -> Point<T> {     // implement add method
              Point{
                  x: self.x + p.x,
                  y: self.y + p.y,
              }
          }
      }
    
      fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {        // implement add function to add two values
          a + b
      }
    
      fn main() {
          let p1 = Point{x: 1.1f32, y: 1.1f32};
          let p2 = Point{x: 2.1f32, y: 2.1f32};
          println!("{:?}", add(p1, p2));
    
          let p3 = Point{x: 1i32, y: 1i32};
          let p4 = Point{x: 2i32, y: 2i32};
          println!("{:?}", add(p3, p4));
      }

trait object

  • define

    rust
    pub trait Draw {
        fn draw(&self);
    }
    
    pub struct Button {
        pub width: u32,
        pub height: u32,
        pub label: String,
    }
    
    impl Draw for Button {
        fn draw(&self) {
            println!("Drawing a button: {}", self.label);
        }
    }
    
    pub struct SelectBox {
        pub width: u32,
        pub height: u32,
        pub options: Vec<String>,
    }
    
    impl Draw for SelectBox {
        fn draw(&self) {
            println!("Drawing a select box: {:?}", self.options);
        }
    }
    
    pub struct Screen {
        pub components: Vec<Box<dyn Draw>>,       // Box is a smart pointer, can be replaced by Vec<&dyn Draw>
    }
    
    
    impl Screen {
        pub fn run(&self) {
            for component in self.components.iter() {
                component.draw();
            }
        }
    }
  • dynamic dispatch

    1. use Box<dyn Trait> to store the trait object
    2. use &dyn Trait to store the trait object reference
  • Self and self

    rust
    impl Draw for Button {
      fn draw(&self) -> Self {        // Self refers to the type that implements the trait or method
          self.clone()                // self refers to the instance of Object
      }
    }
  • limit of trait object

    1. can't use generic type
    2. can't use associated type
    3. can't use Self

Advanced Trait

  • associated type

    rust
    pub trait Iterator {
        type Item;
    
        fn next(&mut self) -> Option<Self::Item>;
    }
    
    pub struct Counter {
        count: u32,
    }
    
    impl Iterator for Counter {
        type Item = u32;
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.count < 5 {
                self.count += 1;
                Some(self.count)
            } else {
                None
            }
        }
    }
  • default generic type

    rust
    trait Add<RHS = Self> {
        type Output;
    
        fn add(self, rhs: RHS) -> Self::Output;
    }
  • fully qualified syntax

    rust
    trait Pilot {
        fn fly(&self);
    }
    
    trait Wizard {
        fn fly(&self);
    }
    
    struct Human;
    
    impl Pilot for Human {
        fn fly(&self) {
            println!("This is your captain speaking.");
        }
    }
    
    impl Wizard for Human {
        fn fly(&self) {
            println!("Up!");
        }
    }
    
    impl Human {
        fn fly(&self) {
            println!("*waving arms furiously*");
        }
    }
    
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    // if without self, the compiler can't determine which fly method to call
    //so need to use fully qualified syntax like <Type as Trait>::method
    person.fly();
  • super trait

    rust
    trait Pilot {
        fn fly(&self);
    }
    
    trait Wizard {
        fn fly(&self);
    }
    
    trait Greet: Pilot + Wizard {
        fn greet(&self) {
            self.fly();
        }
    }
  • newtype

    rust
    struct Wrapper(Vec<String>);
    
    impl fmt::Display for Wrapper {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "[{}]", self.0.join(", "))
        }
    }

Dynamic Sized Type

Vector

  • create

    rust
    let v: Vec<i32> = Vec::new();
    let v = vec![1, 2, 3];
  • update

    rust
    let mut v = Vec::new();
    v.push(5);
  • access

    rust
    let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2];
    let third: Option<&i32> = v.get(2);
  • iterate

    rust
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }
  • mutable iterate

    rust
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
  • enum

    rust
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }
    
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
  • trait

    rust
    trait IpAddr {
        fn print(&self);
    }
    
    struct V4(u8, u8, u8, u8);
    impl IpAddr for V4 {
        fn print(&self) {
            println!("{}.{}.{}.{}", self.0, self.1, self.2, self.3);
        }
    }
    
    struct V6(String);
    impl IpAddr for V6 {
        fn print(&self) {
            println!("{}", self.0);
        }
    }
    
    let v: Vec<Box<dyn IpAddr>> = vec![
        Box::new(V4(127, 0, 0, 1)),
        Box::new(V6(String::from("::1"))),
    ];
  • examples

    rust
    assert!(!v.is_empty());
    v.insert(2, 10);
    assert_eq!(v.remove(2), 10);
    assert_eq!(v.pop(), Some(5));
    v.clear();
    let mut v1 = [1, 2].to_vec();
    v.append(&mut v1);
    v.truncate(2);
    v.extend([1, 2].iter().cloned());
    v.reserve(10);   // reserve capacity
    v.shrink_to_fit();    // shrink the capacity to the length
    v.retain(|x| *x > 2); // remove element that not satisfy the condition
    let mut m: Vec<i32> = v.drain(1..3).collect(); // delete and return the deleted elements
    let v2 = m.split_off(1); // split the vector into two vectors
    let slice = &v[1..=3];
  • sort

    rust
    let mut v = vec![1, 5, 10, 2, 15];
    v.sort();
    v.sort_by(|a, b| b.cmp(a));
    v.sort_unstable();
    v.sort_unstable_by(|a, b| b.cmp(a));

HashMap

  • create

    rust
    use std::collections::HashMap;
    
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    
    // assign specific size
    let teams = HashMap::with_capacity(100);
    
    // use iterator and collect
    let teams = vec![(String::from("Blue"), 10), (String::from("Yellow"), 50)];
    
    let scores: HashMap<_, _> = teams.into_iter().collect();
  • search

    rust
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    
    let team_name = String::from("Blue");
    let score = scores.get(&team_name);
  • iterate

    rust
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
  • update

    rust
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    let score = scores.entry(String::from("Blue")).or_insert(0);
    *score += 1;

    or_insert will return a mutable reference to the value, so you can update the value

Lifetime

  • define

    rust
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }
  • lifetime marker don't change the lifetime of the reference

    rust
    let string1 = String::from("long string is long");
    let result;
    {
    let string2 = String::from("xyz");
    result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);

    the lifetime of the reference is the same as the lifetime of the shorter string, not the longer string

  • lifetime in struct

    rust
    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt { part: first_sentence };
  • lifetime elision

    1. each parameter that is a reference gets its own lifetime parameter
    2. if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
    3. if there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters
    4. if there are multiple input lifetime parameters, and none of them are &self or &mut self, an error will be thrown
  • lifetime in method

    rust
    impl<'a> ImportantExcerpt<'a> {
        fn level(&self) -> i32 {
            3
        }
    }
  • static lifetime

    rust
    let s: &'static str = "I have a static lifetime.";

Return and Error Handling

  • panic

    rust
    panic!("crash and burn");
  • unwrap

    rust
    let f = File::open("hello.txt").unwrap(); // success or panic
  • expect

    rust
      let f = File::open("hello.txt").expect("Failed to open hello.txt"); // success or panic with message
  • Result

    rust
    use std::fs::File;
    
    fn read_username_from_file() -> Result<String, io::Error> {
        let f = File::open("hello.txt");
    
        let mut f = match f {
            Ok(file) => file,
            Err(e) => return Err(e),
        };
    
        let mut s = String::new();
    
        match f.read_to_string(&mut s) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }
  • rust
    use std::fs::File;
    
    fn read_username_from_file() -> Result<String, io::Error> {
        let mut f = File::open("hello.txt")?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok(s)
    }
  • chain

    rust
    use std::fs::File;
    
    fn read_username_from_file() -> Result<String, io::Error> {
        let mut s = String::new();
        File::open("hello.txt")?.read_to_string(&mut s)?;
        Ok(s)
    }

Package and Module

  • define

    • package: a Cargo feature that lets you build, test, and share crates

    • workspace: a directory that contains multiple packages

    • crate: a binary or library

    • module: a collection of items, such as functions, structs, traits, implementations, and modules

  • module

    rust
    mod sound {
        mod instrument {
            fn clarinet() {
                // function body
            }
        }
    }
  • use

    rust
    use sound::instrument::clarinet;
  • as

    rust
    use std::io::Result as IoResult;
  • pub use

    rust
    pub use sound::instrument;

comment

  • line comment

    rust
    // line comment
  • block comment

    rust
    /* block comment */
  • doc line comment

    rust
    /// doc comment
  • doc block comment

    rust
    /** doc comment */
  • doc test

    rust
    /// ```
    /// let x = 5;
    /// let y = 10;
    ///
    /// assert_eq!(add(x, y), 15);
    /// ```
    fn add(x: i32, y: i32) -> i32 {
        x + y
    }
  • package line comment

    rust
    //! package line comment
  • package block comment

    rust
    /*! package block comment */
  • example

    rust
    #![allow(unused)]
    fn main() {
    //! # Art
    //!
    //!  未来的艺术建模库,现在的调色库
    
    pub use self::kinds::PrimaryColor;
    pub use self::kinds::SecondaryColor;
    pub use self::utils::mix;
    
    pub mod kinds {
        //! 定义颜色的类型
    
        /// 主色
        pub enum PrimaryColor {
            Red,
            Yellow,
            Blue,
        }
    
        /// 副色
        #[derive(Debug,PartialEq)]
        pub enum SecondaryColor {
            Orange,
            Green,
            Purple,
        }
    }
    
    pub mod utils {
        //! 实用工具,目前只实现了调色板
        use crate::kinds::*;
    
        /// 将两种主色调成副色
        /// ```rust
        /// use art::utils::mix;
        /// use art::kinds::{PrimaryColor,SecondaryColor};
        /// assert!(matches!(mix(PrimaryColor::Yellow, PrimaryColor::Blue), SecondaryColor::Green));
        /// ```
        pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
            SecondaryColor::Green
        }
    }
    }

Formatting

  • simple format

    rust
    #![allow(unused)]
    fn main() {
    println!("Hello");                 // => "Hello"
    println!("Hello, {}!", "world");   // => "Hello, world!"
    println!("The number is {}", 1);   // => "The number is 1"
    println!("{:?}", (3, 4));          // => "(3, 4)"
    println!("{value}", value=4);      // => "4"
    println!("{} {}", 1, 2);           // => "1 2"
    println!("{:04}", 42);             // => "0042" with leading zeros
    }
  • format!

    rust
    let s = format!("{}-{}", 1, 2);
  • eprint!, eprintln!

    rust
    eprint!("error: {}", "error message");
    eprintln!("error: {}", "error message");
  • positional arguments

    rust
    println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");  // => "Alice, this is Bob. Bob, this is Alice"
  • named arguments

    rust
    println!("{subject} {verb} {object}", object="the lazy dog", subject="the quick brown fox", verb="jumps over");  // => "the quick brown fox jumps over the lazy dog"

    named arguments must be used after positional arguments

Reference