《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}");
}
# 循环标签
如果存在嵌套循环,break 和 continue 应用于此时最内层的循环。指定一个循环标签,将标签与 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}");
}
# 所有权规则
- Rust 中每一个值都有一个所有者(owner)
 - 值在任一时刻有且只有一个所有者
 - 当所有者(变量)离开作用域,这个值将被丢弃
 
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;
}