technology

Rust Note

Rust 学习笔记

← All essays
Tags
Categories

Installation

Install Rust

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

Update Rust

rustup update

Uninstall Rust

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

    let x = 5;
  • mutable variable

    let mut x = 5;
  • never used variable

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

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

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

    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

      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

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

Math Operation

  • addition

    let sum = 5 + 10;
  • subtraction

    let difference = 95.5 - 4.3;
  • multiplication

    let product = 4 * 30;
  • division

    let quotient = 56.7 / 32.2;
  • remainder

    let remainder = 43 % 5;

Logical Operation

  • and

    let x = true && false;
  • or

    let x = true || false;
  • not

    let x = !true;
  • bit

    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

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

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

Character、Boolean、Unit Type

  • character

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

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

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

Statement and Expression

  • statement

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

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

Function

  • function definition

    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

    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

    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

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

    fn return_ownership() -> String {
        let s = String::from("hello");
        s
    }
    fn main() {
        let s1 = return_ownership(); // ownership is moved to s1
    }
    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

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

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

    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

    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

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

Compound Data Type

String

  • create

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

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

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

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

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

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

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

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

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

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

    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

    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

      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

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

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

operate UTF-8 String

  • char

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

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

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

Tuple

  • create

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

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

    let x = x.0;
  • return multiple values

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

Struct

  • define and create

    struct User {
        username: String,
        email: String,
        sign_in_count: u64,
        active: bool,
    }
    let user = User {
        username: String::from("someone"),
        email: String::from("someone@gmail.com")
        sign_in_count: 1,
        active: true,
      };
  • simple create

     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

    let username = user.username;
  • update

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

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

    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

    #[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

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

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

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

      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

    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

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

    let first = a[0];
  • slice

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

Control Flow

if

  • if

    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

    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

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

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

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

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

Pattern Matching

match

  • match

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

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

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

if let

  • if let

    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

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

Pattern Matching Examples

Method

  • define

    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

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

    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

    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

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

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

    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

    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

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

Trait

  • define

    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

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

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

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

    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

    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

    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

      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

    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

    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

    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

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

    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

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

    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

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

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

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

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

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

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

    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

    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

    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

    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

    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

    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

    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

    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

    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

    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

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

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

Return and Error Handling

  • panic

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

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

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

    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),
        }
    }
  • 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

    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

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

    use sound::instrument::clarinet;
  • as

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

    pub use sound::instrument;

comment

  • line comment

    // line comment
  • block comment

    /* block comment */
  • doc line comment

    /// doc comment
  • doc block comment

    /** doc comment */
  • doc test

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

    //! package line comment
  • package block comment

    /*! package block comment */
  • example

    #![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

    #![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!

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

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

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

    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