🚩 Home / rust / fundamental.md

rust-lang

common concept

  • fn,函数

    • 函数定义
      fn main(){}
    • 函数内语句
      fn main() {
      let x = 3;
      let y = {
          let x = 5;
          println!("{}", x); // 5
          x + 1
      };
      println!("{}", x); // 3
      println!("{}", y); // 6
      }
    • 下述表达式是一个block,最后一行没有分号结尾,那么作为一个值返回,而不是一个语句
      {
      let x = 5;
      x + 1
      }
    • 返回值:在rust中一般默认最后一行表达式(不带分号)作为返回值,这时不使用return关键字,如果提前返回一个值,可以使用关键字return。此外,返回值类型需要声明,方式如下:
      fn main() {
      let x = add(6, 7);
      println!("{}", x);
      }
      

    fn add(x:u32, y:u32) -> u32 {

    x + y
    // or use 'return x + y;'

    } ```

  • let: 变量定义,一般是immutable

    fn main() {
      let x = 6;
    }
  • mut: 定义mutable变量

    fn main() {
      let mut x = 5;
      println!("x = {}", x);
      x = 6;
      println!("x = {}", x);
    }
  • shadowing & mut

    • shadowing 必须是使用let再重定义,可以更改变量的类型,但是mut只能改变量内容不能改类型

    • shadowing 可以让我们少起变量名

      fn main() {
      // following code is ok
      let x = 5;
      let x = 6;
      let x = "56";
      
      // following code will be error
      let mut y = 6;
      y = "67";
      }
      fn main() {
      let space = "  ";
      let space = space.len();
      println!("{}", space);
      }
  • const & let

    • const一般用来定义常量,并且要指明类型,定义的值不能改变,且不是函数返回值等(一般直接定义好),let定义的量虽然也不能改变,但是可以shadowing
  • data type

    • scalar
      • integers
        • 包含8,16,32,64,128bit的 unsigned 和 signed 整数,如u32i32
        • 可以使用0x,0o,'0b'分别表示十六,八,二进制。
        • byte表示(仅限u8类型),b'A'
      • floating-point numbers,默认是f64
        fn main() {
        let x = 6.4; // f64
        let y:f32 = 3.2; // f32
        }
      • Booleans: bool 标记
        fn main() {
        let t = true;
        let f:bool = false;
        }
      • characters: 使用单引号,4byte,可以表示Unicode,不仅仅ASCII
        fn main() {
        let c1 = 'z';
        let c2 = '🙂';
        }
    • Compound
      • Tuple: 可以使用模式匹配来解构tuple,此外可以使用“.”来单独提取一个元素
        fn main() {
        let x: (i32, f64, u8) = (500, 6.4, 1);
        let (o, p, q) = x;
        let a = x.0;
        let b = x.1;
        let c = x.2;
        }
      • Array
        • 初始化
          fn main() {
          let arr = [1,2,3,4,5];
          println!("{:?}", arr);
          let arr:[i32; 5] = [1,2,3,4,5];
          println!("{:?}", arr);
          let arr = [3;5];
          println!("{:?}", arr);
          }
        • 访问
        • index out of bounds,此时 runtime error 会被触发并且程序不会成功退出,而是 panicked。rust会在数组使用索引访问之前检测索引,避免非法内存访问,确保内存安全。
          fn main() {
          let a = [1, 2, 3, 4, 5];
          let index = 10;
          let element = a[index];
          println!("The value of element is: {}", element);
          }
  • 控制流

    • 条件语句

      • if else 判断条件不使用括号,并且判断条件必须是bool类型
        fn main() {
        let x = 3;
        if x < 5 {
            println!("x is less than 5.");
        } else {
            println!("x is greater than or equal to 5.")
        }
        }
      • if else 在 赋值语句中使用,但是要注意赋值的类型要相同
        fn main() {
        let x = if true {1} else {0};
        println!("{}", x);
        }
    • 循环

      • loop,直到给出终止,否则一直循环。使用break来终止循环,并且break 后可以跟一个返回值。

        fn main() {
        let mut counter = 0;
        
        let result = loop {
            counter += 1;
        
            if counter == 10 {
                break counter * 2;
            }
        };
        
        println!("The result is {}", result); // 20
        }
      • while,条件循环,满足条件才循环

        fn main() {
        let mut counter = 3;
        println!("{}", counter);
        while counter != 0 {
            println!("{}", counter);
            counter -= 1; // rust 没有 i-- 语法
        }
        }
      • for,一般用来遍历集合类型,如果用while来判断index遍历,index可能会溢出,导致不安全的代码。for遍历会更加安全。for 也可以遍历一个Range类型(比如,1..5)来遍历。

        fn main() {
        let counter = [1,2,3,4,5];
        for i in counter.iter() {
            println!("{}", i);
        }
        }
        fn main() {
        for number in 1..4 {
            println!("{}", number);
        }
        }

ownership

  • ownership rules

    • rust中,每一个值都对应一个变量,称为他的owner

    • 每个值在每个时刻只有一个owner

    • 当变量离开作用域时,值被释放

      fn main() {
      {                      // s is not valid here, it’s not yet declared
          let s = "hello";   // s is valid from this point forward
      
          // do stuff with s
      }                      // this scope is now over, and s is no longer valid
      }
  • String

    • 通过使用String类型,可以支持字符串可变,在编译时期,字符串大小不可知,那么需要申请内存空间在heap上。一般包含两个过程:
      • 在runtime通过allocator申请内存空间存放
      • 使用完毕之后,需要释放内存空间
    • 大多数语言的第一步操作是相同的,但是rust的第二步是不同的(其他许多语言是通过GC完成的)。
      • Rust takes a different path: the memory is automatically returned once the variable that owns it goes out of scope.
      • There is a natural point at which we can return the memory our String needs to the allocator: when s goes out of scope. When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.
      • 类似c++中的 Resource Acquisition Is Initialization (RAII)
  • Ways Variables and Data Interact: Move

    • 在类似java的语言中,对于对象变量,是可以进行复制的即:
      String s1 = new String("hello");
      String s2 = s1;
    • 在这种情况下s2和s1指向同一个内存空间。s1,s2都可以使用,但会相互影响。
    • 但是在rust中,为避免内存的double free,rust在对复杂类型进行一个重指向时,前一个变量会变成invalid,这跟shallow copy不太一样,这种方式被叫做move,即value从一个variable移动到另外一个,则这个variable变成invalid。
      fn main() {
      let s1 = String::from("hello");
      let s2 = s1;
      println!("{}, world!", s1); // error, s1 is invalid now
      }
  • Ways Variables and Data Interact: Clone

    • move仅仅是把一个变量值赋给另外一个变量,如果想进行复制,即不仅复制指针,也复制堆上具体数据。可以使用clone()方法(如果有的话)
      let s1 = String::from("hello");
      let s2 = s1.clone();
      println!("s1 = {}, s2 = {}", s1, s2);
  • Ownership and Function

    • 在作为参数传入函数时,变量仍然遵循move和copy,即复杂类型作为参数传入函数后,即变为invalid,简单类型仍然是valid的。

      fn main() {
      let s1 = String::from("hello");
      show_string(s1); // s1 is moved, and s1 is invalid now
      println!("{}", s1); // error
      
      let x = 1;
      println!("{}", add_one(x));
      let y = x + 1; // x is u32 type, and is copied, x is still valid
      println!("{}", y);
      }
      

    fn add_one(x:u32) -> u32 {

    return x + 1;

    }

    fn show_string(s:String) {

    println!("{}", s);

    }

    - 但是这种情况下,对于复杂类型,一旦作为参数传入后,那么就不能再使用了。一个解决方式就是以元组的方式,将处理的结果和原类型传出。
    ```rust
    fn calculate_length(s: String) -> (String, usize) {
      let length = s.len(); // len() returns the length of a String
    
      (s, length)
    }
    • 但这样仍然很繁琐。
  • References and Borrowing

    • 解决上述的这种问题,即引用复杂类型的值而不使用ownership,可以使用reference
      fn main() {
      let s1 = String::from("hello");
      let l:usize = show_string(&s1);
      println!("{} {}", s1, l); // s1 is still valid
      }
      

    fn show_string(s:&String) -> usize { // s is a reference to String

    s.len()

    } // s goes out of scope, but s has no ownership, nothing happened

    - We call having references as function parameters *borrowing*.
    - 在rust中 *borrowing* 的值是不能修改的,
    ```rust
    fn main() {
      let s1 = String::from("hello");
      show_string(&s1);
      println!("{}", s1); // s1 is still valid
    }
    
    fn show_string(s:&String) {
        s.push_str(", world!");
    }
    • 通过mut可以将reference变成可修改的
      fn main() {
      let mut s1 = String::from("hello");
      show_string(&mut s1);
      println!("{}", s1); // s1 is still valid
      }
      

    fn show_string(s:&mut String) {

    s.push_str(", world!");

    }

    - 这种`mut`的reference有一个限制,就是一个scope里只能有一个`mut`的reference,并且如果有一个immutable的reference,也不能在有一个mutable的reference。
    ```rust
    fn main() {
      let mut s1 = String::from("hello");
      let sp1 = &s1;
      let sp2 = &mut s1; // cannot borrow `s1` as mutable because it is also borrowed as immutable
      println!("{}, {}", sp1, sp2);
    }
    • 但是可以等到reference unused在使用
      fn main() {
      let mut s1 = String::from("hello");
      let sp1 = &s1;
      println!("{}", sp1);
      let sp2 = &mut s1;
      println!("{}", sp2);
      }
  • dangling Reference

    • 在其他语言中,很容易错误的写出悬空指针,就是指针指向了一块已经被回收的内存,在rust中,这种悬空指针是不被允许的,在编译阶段会被指出。
      fn main() {
      let reference_to_nothing = dangle();
      }
      

    fn dangle() -> &String {

    let s = String::from("hello");
    &s // return reference of s

    } // s will be deallocated, &s is dangling reference ```

  • slice

    • &strString字符串slice类型,这是一个不可变引用,因此如果去修改原字符串,会导致一个可变引用的使用。使用slice期间,不能有字符串的改变。
    • 字符串的字面值就是一个slice,对于语句let s = "hello";s的类型就是&str
      fn main() {
      let s = String::from("hello world");
      println!("{}", first_word(&s));
      }
      

    fn first_word(s: &String) -> &str {

    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    
    &s[..]

    }

Struct

  • demo
    struct User {
      name: String,
      age: u32,
      id: u32,
    }
    

fn main() { let mut u = User { name: String::from("bob"), age: 24, id: 1000, }; u.age = 25; u.id = 1001; println!("{} {} {}", u.name, u.age, u.id); }

- 变量字段同名时可简写
```rust
struct User {
    name: String,
    age: u32,
    id: u32,
}

fn main() {
    let name = String::from("bob");
    let age = 32;
    let id = 1002;
    let u = User {
        name,
        age,
        id,
    };
    println!("{} {} {}", u.name, u.age, u.id);
}
  • 根据其他实例来创建新的struct
    struct User {
      name: String,
      age: u32,
      id: u32,
    }
    

fn main() { let name = String::from("bob"); let age = 32; let id = 1002; let u = User { name, age, id, };

let u2 = User {
    name: String::from("Jam"),
    ..u
};
println!("{} {} {}", u2.name, u2.age, u2.id);

}

- tuple struce
```rust
struct Color(u8, u8, u8);
struct Point(i32, i32);

fn main() {
    let black = Color(0,0,0);
    let origin = Point(0,0);
    println!("{}", black.0);

    let Point(x, y) = origin;
    println!("{} {}", x, y);
}
  • 实例,计算矩形面积
    struct Rectangle {
      height: u32,
      width: u32,
    }
    

fn main() { let r = Rectangle { height: 3, width: 4, }; println!("{}", area(&r)); }

fn area(rectangle: &Rectangle) -> u32 { rectangle.height * rectangle.width }

- method syntex
```rust
#[derive(Debug)]
struct Rectangle {
    height: u32,
    width: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.height * self.width
    }
}

fn main() {
    let r = Rectangle {
        height: 3,
        width: 4,
    };
    println!("{:#?}", r);
    println!("{}", r.area());
}
  • 关联函数,impl的一个作用就是允许在block中创建不以self为参数的函数,关联函数经常被用作返回一个结构体新实例的构造函数。使用::来进行引用。
    #[derive(Debug)]
    struct Rectangle {
      height: u32,
      width: u32,
    }
    

impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { height: size, width: size, } }

fn area(&self) -> u32 {
    self.height * self.width
}

}

fn main() { let r = Rectangle::square(5); println!("{:#?}", r); println!("{}", r.area()); }


## enums and pattern matching
- demo
```rust
#[derive(Debug)]
enum IpAddrKind {
    V4,
    V6,
}

struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let ia = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };
    println!("{:?} {}", ia.kind, ia.address);
}
  • 可以将枚举关联数据
    #[derive(Debug)]
    enum IpAddrKind {
      V4(String),
      V6(String),
    }
    

fn main() { let ia = IpAddrKind::V4(String::from("127.0.0.1")); println!("{:?}", ia); }


```rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
  • Quit 没有关联任何数据

  • Move 包含一个匿名结构体

  • Write 包含单独一个 String

  • ChangeColor 包含三个 i32

  • 枚举也可以使用impl在其上定义方法

    impl Message {
      fn call(&self) {
        // TODO
      }
    }
    let m = Message::Write(String::from("hello"));
    m.call();

一个标准库中的枚举,Option

  • match

    • demo
      #[derive(Debug)] // 这样可以可以立刻看到州的名称
      enum UsState {
        Alabama,
        Alaska,
        // --snip--
      }
      

    enum Coin {

    Penny,
    Nickel,
    Dime,
    Quarter(UsState),

    }

    fn value_in_cents(coin: Coin) -> u8 {

    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        },
    }

    }

    fn main() {

    let x = Coin::Quarter(UsState::Alaska);
    println!("{}", value_in_cents(x));

    }

    ```rust
    fn main() {
      let x = Some(5);
      let y:Option<i32> = None;
    
      println!("{:?}", plus_one(x));
      println!("{:?}", plus_one(y));
    }
    
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1)
        }
    }
    • 通配符_

      fn main() {
      let x = 2;
      
      match x {
          0 => println!("zero"),
          1 => println!("one"),
          _ => println!("other"), // print
      }
      }
    • if let语法,只匹配一个pattern时可以使用

      fn main() {
      let x = Some(3);
      
      // match x {
      //     Some(3) => println!("three"),
      //     _ => println!("other"),
      // }
      
      if let Some(3) = x {
          println!("three");
      } else {
          println!("other");
      }
      }

Error Handling

Rust包含两类Error:

  • recoverable: 如文件不存在错误
  • unrecoverable: 如数组地址越界

在处理这两种错误时,rust不是采用其他语言抛出异常的方式,两类错误分别处理:

  • Result<T, E>
  • panic!

Unrecoverable Errors with panic!

  • call a panic
    fn main() {
      panic!("crash and burn");
    }

通过RUST_BACKTRACE=1参数可以打印 stack backtrace

Recoverable Errors with Result

  • 会返回Restlt<T, E> 类型,通过match来判断

  • shortcuts:unwrap, except

    use std::fs::File;
    

fn main() { let f = File::open("hello.txt").unwrap(); let f = File::open("hello.txt").except("Failed to open hello.txt"); }


- Propagating Errors
```rust
use std::fs::File;
use std::io;
use std::io::Read;

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),
    }
}
  • A Shortcut for Propagating Errors: the ? Operator
  • 如果是Result类型,在匹配OK时,直接返回内部value,如果匹配Err,那么就会propagating errors。
    use std::fs::File;
    use std::io;
    use std::io::Read;
    

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


```rust
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)
}
  • 在函数中使用?函数必须有Result返回值