《Rust 程序设计语言》看到哪里记哪里

很多人利用 Rust 学习了操作系统开发等内容,可以更深入到操作系统底层知识,我想这就是我学习 Rust 的原因吧。

# 编译和运行是彼此独立的步骤

rustc 是 Rust 语言编译器,在运行 Rust 程序之前,必须先使用 Rust 编译器编译它:

$ rustc main.rs

编译成功后,在 Linux、macOS 上,Rust 会输出一个二进制的可执行文件:

$ ls
main main.rs

main 是一个可执行文件,在 Windows 下是 main.exe,执行后就会打印出程序中的 hello world!

$ ./main
hello world!

# cargo

cargo 是 Rust 的包管理工具,也是一个用于编译打包的工具。

新建项目:

$ cargo new

构建项目:

$ cargo build

构建并运行项目:

$ cargo run

快速检查代码确保其可以编译,不产生可执行文件:

$ cargo check

优化编译项目,生成发布版本:

$ cargo build --release

# 在 Rust 中,变量默认是不可变的

let apples = 5; // 不可变
let mut apples = 5; // 可变
const THREE_HOURS_IN_SECONDS: u32 =  60 * 60 * 3; // 常量

# 在 Rust 中,每一个值都属于某一个数据类型

** scalar 标量**

  • 整型 i8/i16/i32/i64/i128/isize(有符号) u8/u16/u32/u64/u128/usize(无符号)
  • 浮点型 f32/f64
  • 布尔型 bool
  • 字符类型 char

** compound 复合类型**

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

let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
  • 数组
let a: [i32; 5] = [1, 2, 3, 4, 5]; // 类型为 i32 长度为 5 的数组

# 参数(parameters: 形参,arguments: 实参)

函数中给一个变量赋值必须是表达式(Expressions),而不是语句(Statements)。

fn plus_one(x: i32) -> i32 {
    x + 1; // 加了分号是语句,不加分号是表达式,此处会报错
}

上面的例子返回单位类型(),与 i32 不匹配,从而出现一个报错。去掉分号则可修复这个错误。

# 循环返回值

使用 break 关键字返回值,通过分号结束赋值给 result 语句。

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    }; // 分号

    println!("The result is {result}");
}

# 循环标签

如果存在嵌套循环,breakcontinue 应用于此时最内层的循环。指定一个循环标签,将标签与 break 或 continue 一起使用,可应用于已标记的循环。

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up; // 结束外层标记的循环
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

# 所有权规则

  1. Rust 中每一个值都有一个所有者(owner)
  2. 值在任一时刻有且只有一个所有者
  3. 当所有者(变量)离开作用域,这个值将被丢弃
fn main() {
    let s1 = gives_ownership();         // gives_ownership 将返回值
                                        // 转移给 s1

    let s2 = String::from("hello");     // s2 进入作用域

    let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                        // takes_and_gives_back 中,
                                        // 它也将返回值移给 s3
} // 这里,s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  // 所以什么也不会发生。s1 离开作用域并被丢弃

fn gives_ownership() -> String {             // gives_ownership 会将
                                             // 返回值移动给
                                             // 调用它的函数

    let some_string = String::from("yours"); // some_string 进入作用域。

    some_string                              // 返回 some_string 
                                             // 并移出给调用的函数
                                             // 
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
                                                      // 

    a_string  // 返回 a_string 并移出给调用的函数
}

变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。

# 通过 引用 访问存储于该地址的属于其他变量的数据

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

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {// s 是 String 的引用
    s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
  // 所以什么也不会发生

这些 & 符号就是 引用,它们允许你使用值但不获取其所有权。

与引用对应的是解引用运算符(*)

正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。

# 通过 可变引用 修改一个借用的值

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

  change(&mut s); // 可变引用
}

fn change(some_string: &mut String) {
  some_string.push_str(", world");
}

在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。

# 方法在结构体的上下文中定义,第一个参数总是 self,代表调用该方法的结构体实例

#[derive(Debug)]
struct Rect {
  width: u32,
  height: u32,
}

impl Rect {
  // 关联函数,类似 static 函数
  fn square(size: u32) -> Self {
    Self {
      width: size,
      height: size,
    }
  }

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

  fn can_hold(&self, other: &Rect) -> bool {
    self.width > other.width && self.height > other.height
  }
}

fn main() {
  let rect1 = Rect {
    width: 30,
    height: 50,
  };

  let rect2 = Rect {
    width: 10,
    height: 40,
  };

  let rect3 = Rect::square(3);

  if rect1.can_hold(&rect2) {
    println!(
      "The area of the rectangle is {} square pixels",
      rect1.area()
    );
  }
}

# 枚举

enum IpAddr {
  V4(String),
  V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

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

impl Message {
  fn call(&self) {
    // 枚举方法体
  }

  let m = Message::Write(String::from("hello"));
  m.call();
}

# Option枚举

我未能抵抗住引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。 ————Tony Hoare,null 发明者

Option 枚举定义于标准库中:

enum Option<T> {
  None,
  Some(T),
}

let some_number = Some(5);
let some_char = Some('e');

let absent_number = Option<i32> = None;

assert_eq!(some_number.is_none(), false);

具体使用方法:https://doc.rust-lang.org/std/option/enum.Option.html

# match 分支匹配(类似 js 中的 switch)

fn plus_one(x: Option<i32>) -> Option<i32> {
  match x {
    None => None,
    Some(i) => Some(i + 1),
  }
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

分支匹配必须满足穷举性要求

let dice_roll = 9;
match dice_roll {
  3 => add_fancy_hat(),
  7 => remove_fancy_hat(),
  _ => (), // _ 替代了 other,在与前面模式不匹配的情况下不运行任何代码
}

fn add_fancy_hat() {}
fn remove_fancy_hat() {}
let mut count = 0;
match coin {
  Coin::Quarter(state) => println!("State quarter from {:?}!", state),
  _ => count + 1,
}

// ==== 转成 if let 简洁语法,可以认为 if let 是 match 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。 ====
let mut count = 0;
if let Coin::Quarter(state) = coin {
  println!("State quarter from {:?}!", max);
} else {
  count += 1;
}