# Rust Book

doc.rust-lang.org/book (opens new window)

# 1. Ownership

  1. immutable variable can be many
  2. mutable variable only exist one at one time
  3. last usage ends, the variable scope ends
  4. reference is borrow
  5. fn vs method
    1. fn ’s parameters are anything;
    2. method parameters: first is always self;
    3. method are defined in impl
    4. fn defined in impl don’t accept self, they are static/associate fn; ???TODO
// String slice
let my_string = String::from(“hello”);
let my_slice = &my_string[..];
// Array slice
let a = [1, 2, 3, 4];
let a_slice = &a[1..3];
1
2
3
4
5
6

# 2. Structs

  1. Defination
struct User {email: String, name: String}
// unit struct
struct UnitExample;
// tuple struct
struct User(String, String)
1
2
3
4
5
  1. Instance
let user1 = User {
    name: String::from(“naaa”),
    email: String::from(“eee”)
};
let user2 = User(String::from(“name”), String::from(“email”));
let user3 = User{
    name: String::from(“userss”),
    ..user2
};
1
2
3
4
5
6
7
8
9

# 3. Enum

  1. Defining an Enum
    1. enum variant can be any kind
    2. enum variants are of the same type
    3. You can use Some(T)/None directly.
enum Option<T> {
    Some(T),
    None,
}
1
2
3
4
  1. Match
    1. if let is a syntax sugar when match matches the first arm and ignores the rest.
// match version
let mut count = 6;
match coin {
    Coin::Quater(state) => println!("State from {:?}", state),
    _ => count += 1,
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4. Packages, Crates and Modules

  1. Cargo new my-project:

    1. creates a package, can have multiple binary crates.
    2. with src/main.rs which is a binary crate.
    3. with src/lib.rs which is a library crate.
    4. files in src/bin/ are separate binary crate.
    5. src/main.rs and src/lib.rs are called crate roots
    6. the entire module tree is rooted under the implicit module named crate.
  2. Packages: A Cargo feature that lets you build, test, and share crates

    1. A package is one or more crates that provide a set of functionality.
    2. A package contains a Cargo.toml file that describes how to build those crates.
    3. A package can contain one library crate at most.
    4. It can contain many binary crates.
    5. it must contain at least one crate (either library or binary).
  3. Crates: A tree of modules that produces a library or executable

    1. A crate is a binary or library.
    2. Group related functionality together in a scope to share.
    3. Only crates get compiled.
    4. rustc --crate-type=lib rary.rs (opens new window) can override compile type.
    // This crate is a library
    #![crate_type = "lib"]
    // The library is named "rary"
    #![crate_name = "rary"]
    
    1
    2
    3
    4
  4. Modules and use: Let you control the organization, scope, and privacy of paths

    1. define a module: mod your_mode_name {}
    2. Inside modules, we can have other modules
    3. child mod can use parent mod items, the otherwise can't.
    4. to make an item private, you put it in a mod.
    5. filename can be mod name.
  5. Paths: A way of naming an item, such as a struct, function, or module

    1. the use keyword that brings a path into scope
    2. use crate::front_of_house::hosting; then you can use hosting::anything directly.
    3. relative path: use self::front_of_house::hosting;
    4. use std::io::Result as IoResult;
    5. use std::{cmp::Ordering, io}; use std::io::{self, Write}; use std::collections:😗;
    6. the pub keyword to make items public.
    7. pub use:
  6. Organise your project:

    1. touch src/test_fun.rs
    2. add pub mod test_fun in src/lib.rs
    3. add use your_project::test_fun; when you use it;
  7. Attributes:

    // apply to the whole crate
    #![crate_attribute]
    
    // apply to module/item
    #[item_attribute]
    
    // different syntaxes
    #[attribute = "value"]
    #[attribute(key="value")]
    #[attribute(value)]
    
    // multiple values
    #[attribute(value, value2)]
    
    // multiple lines
    #[attribute(value,
    value1, value2, value3)]
    
    // ===DeadCode===
    #[allow(dead_code)]
    // ===cfg===
    // two different ways to use config
    // 1. in attribute position
    #[cfg(target_os = "linux")]
    #[cfg(not(target_os = "linux"))]
    // 2. macro as a boolean expression
    if cfg!(target_os = "linux") {...}
    // custom cfg
    // rustc --cfg some_condition should be used
    #[cfg(some_condition)]
    fn conditional_function() {
        println!("condition met!");
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

# 5. Common Collections

  1. vector: Vec<T>

    let mut v: Vec<i32> = Vec::new();
    let mut v = vec![1, 2, 3];
    v.push(4);
    v.get(2); // option<i32>;
    let third: &i32 = &v[2];
    
    1
    2
    3
    4
    5
  2. string

    let mut s = String::new();
    s.push('l');
    s.push_str("bar");
    let ss = "initial contents".to_string();
    let ss = String::from("initial contents");
    
    // concatenation
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2;
    // format!
    let s = format!("{}-{}", s1, s2);
    
    // iteration
    for c in "नमस्ते".chars() {
        println!("{}", c);
    }
    for b in "नमस्ते".bytes() {
        println!("{}", b);
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
  3. hash map

    use std::collections::HashMap;
    // new
    let mut scores = HashMap::new();
    // insert
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    // overwrite
    scores.insert(String::from("Blue"), 25);
    
    let team_name = String::from("Blue");
    // get
    let score = scores.get(&team_name);
    // iteration
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
    // or_insert
    scores.entry(String::from("Yellow")).or_insert(50);// not insert
    scores.entry(String::from("Green")).or_insert(50);// insert
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# 6. Error Handling

  1. unrecoverable: panic!

  2. recoverable: Result<T, E>

    use std::fs::File;
    
    fn main() {
        let f = File::open("hello.txt");
    
        let f = match f {
            Ok(file) => file,
            Err(error) => panic!("Problem opening the file: {:?}", error),
        };
    
        // unwrap
        let f = File::open("hello.txt").unwrap();
        // expect
        let f = File::open("hello.txt").expect("Failed to open hello.txt");
    }
    
    // the ? Operator
    #![allow(unused)]
    fn main() {
    	use std::fs::File;
    	use std::io;
    	use std::io::Read;
    
    	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)
    	}
    }
    // only allowed to use the ? operator in a function that returns Result
    // or Option or another type that implements std::ops::Try
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    1. To panic or Not

    it’s appropriate to panic in examples, prototype code, and tests.

# 7. Generic Types, Traits and Lifetimes

  1. Traits: Define shared behavior.

    1. trait 像是公共方法的模板
    // trait
    pub trait Summary {
        fn summarize(&self) -> String;
    }
    // default impl
    pub trait Summary {
        fn summarize(&self) -> String {
            String::from("(default impl of Summary)")
        }
    }
    
    impl Summary for NewsArticle {
        fn summarize(&self) -> String {
    		// do something to override default impl
        }
    }
    
    // item impl Summary as parameter
    pub fn notify(item: &impl Summary) {
        println!("Breaking news! {}", item.summarize());
    }
    ↕️
    // trait bound
    pub fn notify<T: Summary>(item: &T) {
        println!("Breaking news! {}", item.summarize());
    }
    
    // Multiple Trait Bounds
    pub fn notify(item: &(impl Summary + Display)) {
    pub fn notify<T: Summary + Display>(item: &T) {
    
    // where
    fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    fn some_function<T, U>(t: &T, u: &U) -> i32
        where T: Display + Clone,
              U: Clone + Debug
    {
    
    // as return type
    fn returns_summarizable() -> impl Summary {
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    1. Lifetimes

    every reference in Rust has a lifetime, which is the scope for which that reference is valid

    // Lifetime Annotation Syntax
    &'a mut i32
    
    // In fn signatures
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    
    // In Struct Definitions
    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    
    // Lifetime Elision Rules
    1. input lifetimes:
    	each reference parameter gets its own lifetime parameter
    2. output lifetimes:
    	1. if there is one input lifetime parameter,
    		that lifetime is assigned to all output lifetime parameters
    	2. In methods the lifetime of self is assigned to all output lifetime parameters
    
    // 'static
    the lifetime of all string literals is 'static
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

# 8. Automated Tests

  1. How to write tests

    1. the attribute: #[test] indicate the fn is a test function.

    2. assert!, assert_eq!, and assert_ne!

      #[test]
      #[should_panic(expected = "Guess value must be less than or equal to 100")]
      
      1
      2
  2. Control how tests are run

    1. $ cargo test -- --test-threads=1
    2. $ cargo test -- --show-output
    3. $ cargo test add // tests name contain add
    4. #[ignore]
    5. $ cargo test -- --ignored // only run ignored
  3. Test Organization

    1. unit tests

      1. to test each unit of code in isolation to pinpoint code work as expected

        #[cfg(test)]
        mod tests {
            #[test]
            fn it_works() {
                assert_eq!(2 + 2, 4);
            }
        }
        
        1
        2
        3
        4
        5
        6
        7
    2. integration tests

      1. can only call functions that are part of your library’s public API
      2. tests directory
      src|
       lib.rs
       tests|common|mod.rs // common is a submod that any i_t file can use
       tests|integration_test.rs
      
      1
      2
      3
      4
      1. Only library crates expose functions that other crates can use
      2. eprintln! print error on the screen.
      3. cargo run to poem.txt > output.txt => store std::output to a file.

# 9. Iterators and Closures

  1. Closures

    1. thread::sleep(Duration::from_secs(2));
    struct Cacher<T>
    where
        T: Fn(u32) -> u32,// T is a closure
    {
        calculation: T,
        value: Option<u32>,
    }
    
    1
    2
    3
    4
    5
    6
    7
    1. Capturing the ENV
    fn main() {
        let x = 4;
    
        // if changed to fn, it wil not compile
        let equal_to_x = |z| z == x;
    
        let y = 4;
    
        assert!(equal_to_x(y));
    
    	  let y = vec![1, 2, 3];
        let equal_to_y = move |z| z == y; // use move ownership
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  2. Iterators

    // Iterator trait
    pub trait Iterator {
    		// Item is the type returned from iterator
        type Item;
    
        fn next(&mut self) -> Option<Self::Item>;
    }
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn iterator_demonstration() {
            let v1 = vec![1, 2, 3];
    				// creates an iterator over items in v1
            let mut v1_iter = v1.iter();
    
    				// next() change iter internal state
    				// .next() value is immutable ref of v1
            assert_eq!(v1_iter.next(), Some(&1));
            assert_eq!(v1_iter.next(), Some(&2));
            assert_eq!(v1_iter.next(), Some(&3));
            assert_eq!(v1_iter.next(), None);
    				// takes ownership of v1 and returns owned values
    				let mut v1_iter = v1.into_iter();
    				// iterate over mutable refs
    				let mut v1_iter = v1.iter_mut();
        }
    }
    
    fn main() {
        let v1: Vec<i32> = vec![1, 2, 3];
    
        let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
    
        assert_eq!(v2, vec![2, 3, 4]);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

# 10. Cargo, crates.io

  1. Customize your build through release profiles

    // Cargo.toml
    // override default settings in diff profiles
    [profile.dev]
    opt-level = 0
    
    [profile.release]
    opt-level = 3
    
    1
    2
    3
    4
    5
    6
    7
  2. Publish libraries on crates.io (opens new window)

    1. Documentation comments use three slashes: ///
    2. Comments describe the entire crate: //! in src/lib.rs
    3. pub use reexport/reconstruct your project
  3. Organize large projects with workspaces

    1. workspaces can help manage multiple related packages.
    2. crates share one target directory.
[workspace]

members = [
  "02_concepts_is_a_crate",
]
1
2
3
4
5
  1. Install binaries from crates.io (opens new window)
  2. Extend Cargo using custom commands

# 11. Smart Pointers

  1. Using Box<T> to Point to Data on the Heap

    1. Store Data on the Heap
    fn main() {
        // Store an i32 value on the heap using a box
        let b = Box::new(5);
        println!("b = {}", b);
    }
    
    1
    2
    3
    4
    5
    1. Box<T>provide only the indirection and heap allocation
  2. Treating Smart Pointers Like Regular References with the Deref Trait

    1. The Deref trait allows you to customize the behavior of the dereference operator,
    struct MyBox<T>(T);
    
    impl<T> MyBox<T> {
        fn new(x: T) -> MyBox<T> {
            MyBox(x)
        }
    }
    
    use std::ops::Deref;
    
    impl<T> Deref for MyBox<T> {
        type Target = T;
    
        fn deref(&self) -> &T {
            &self.0
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    1. Deref coercion
  3. Running Code on Cleanup with the Drop Trait

    1. Drop lets you customize what happens when a value is about to go out of scope
    2. Variables are dropped in the reverse order of their creation
    3. Force a value to be dropped before the end of its scope: std::mem::drop
  4. Rc, the Reference Couted Smart Pointer

    1. Rc<T> enable multiple ownership
    2. TheRc<T> type keeps track of the number of references to a value:using
    3. Rc<T> is only for use in single-threaded scenarios
    4. we call Rc::clone, the reference count to the data within the Rc<List> will increase
    5. fn Rc::strong_count get the reference count.
    6. the Drop trait decreases the reference count automatically
    7. Rc<T> allows only immutable borrows checked at compile time;
  5. RefCell and the Interior Mutability Pattern

    1. RefCell<T> is only for use in single-threaded scenarios, has single owner.
    2. With references and Box<T>, the borrowing rules’ invariants are enforced at compile time. With RefCell<T>, these invariants are enforced at runtime.
    3. RefCell<T>.borrow() ⇒ Ref<T>; RefCell<T>.borrow_mut() ⇒ RefMut<T>.
    4. The RefCell<T> keeps track of how many Ref<T> and RefMut<T> smart pointers are currently active.
    5. Every time we call borrow, the RefCell<T> increases its count
    6. RefCell<T> lets us have many immutable borrows or one mutable borrow at any point in time
  6. Reference Cycles Can Leak Memory

    1. Rc<T>::downgrade() ⇒ Weak<T> with weak_count
    2. Weak<T>::upgrade() ⇒ Option<Rc<T>> with strong_count

# 12. Concurrency

  1. Using Threads to Run Code

    let handle = thread::spawn(|| {
        // do something
    });
    
    // block
    handle.join().unwrap();
    
    1
    2
    3
    4
    5
    6
  2. Using Message Passing to Transfer Data Between Threads

    use std::sync::mpsc;// multiple producer single consumer
    
    fn main() {
        let (tx, rx) = mpsc::channel();
        let tx1 = mpsc::Sender::clone(&tx);
    }
    
    1
    2
    3
    4
    5
    6
  3. Shared-State Concurrency

    use std::sync::Mutex;// mutual exclusion
    
    fn main() {
        let m = Mutex::new(5);
    
        {
            let mut num = m.lock().unwrap();
            *num = 6;
        }
    
        println!("m = {:?}", m);
    }
    
    // Arc<T> is used in concurrent situations like Rc<T>, a is atomic
    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        let counter = Arc::new(Mutex::new(0));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                let mut num = counter.lock().unwrap();
    
                *num += 1;
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("Result: {}", *counter.lock().unwrap());
    }
    
    // Similarities Between RefCell<T>/Rc<T> and Mutex<T>/Arc<T>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
  4. Extensible Concurrency with the Sync and Send Traits

# 13. OOP

  1. Characteristics
  2. Using Trait Objects That Allow for Values of Different Types
  3. Implementing an OO Design Pattern

# 14. Patterns and Matching

  1. All the Places Patterns Can Be Used
  2. Refutability: Whether a Pattern Might Fail to Match
  3. Pattern Syntax

# 15. Advanced Features

  1. Unsafe Rust
  2. Advanced Traits
  3. Advanced Types
  4. Advanced Functions and Closures
  5. Macros

# 16. Final Project: Building a Multithreaded Web Server

# 17. Appendix

  1. A

  2. B

  3. C

  4. D - Useful Development Tools

    // install rustfmt
    rustup component add rustfmt
    // format cargo project
    cargo fmt
    
    // use rustfix to fix your code
    cargo fix
    
    // lints with Clippy
    // install
    rustup component add clippy
    // run clippy lints
    cargo clippy
    
    // Rust Language Server(rls)
    rustup component add rls
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  5. E

  6. F

  7. G - How Rust is Made and “Nightly Rust”

    // “stability without stagnation”, Train Schedule
    nightly: * - - * - - * - - * - - * - - * - * - *
                        |                         |
    beta:                * - - - - - - - - *       *
                                            |
    stable:                                *
    
    rustup toolchain install nightly
    rustup toolchain list
    rustup override set nightly
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
Last Updated: 2/8/2023, 2:30:06 AM