rust 基础知识3

262 阅读5分钟

智能指针

rust 中默认是放在栈上的

Box 允许将一个值放在堆上而不是栈上,留在栈上的是指向堆数据的指针

Box 指向的数据生命周期结束时,Box 指向堆中的内容会被释放

fn main() {
  let b = Box::new(5);
  println!("b = {}", b);
}

Box 没有运行上的性能损失,但要合理使用:

  1. 当有一个编译时未知大小的类型,而又想在需要确切大小的上下文中使用这个类型的值
  2. 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
  3. 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是具体类型的时候

未知大小类型

什么样的数据会是未知大小类型?

递归类型是一种,递归类型的数据结构:当前项和下一项(结束标识)

enum List {
    Cons(i32, List),
    Nil,
}
let list = List::Cons(1, List::Cons(2, List::Nil));

这种递归的结构没法定义,rust 会直接报错:recursive type List has infinite size

rust 提供了 Box 来解决这个问题

enum List {
    Cons(i32, Box<List>),
    Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

这样子,List 大小就确定了,在 64 位系统上,Box8 字节,List16 字节

大量数据的所有权转移

rust 中,数组是存放在栈中的,如果这个数据量太大的话,在进行所有权转移时,会有性能问题

Box 可以解决这个问题,将 array 转移放在堆上,这里会发生一次拷贝,但后续传递时只需要移动指针,不需要拷贝实际数据

fn main() {
    let array = [0, 1024 * 512]; // 存放在栈上
    let box_array = Box::new(array); // 存放在堆上
}

包装 trait

因为 rust 类似于其他语言的接口,不是具体的类型,所以在 rust 中,trait 也是未知大小的

为什么需要 Box

  • 不同的错误类型可能有不同的大小
  • Box 提供了固定大小的指针
  • 使用 dyn 进行动态分发
fn main() -> Result<(), Box<dyn std::error::Error>> {
    std::fs::read("src/main.rs")?;
    Ok(())
}

当需要处理多种可能的错误类型时,rust 是不知道错误类型的大小的,这时候就需要 Box 来解决这个问题

fn my_function() -> Result<(), Box<dyn Error>> {
    // 可能返回 IO 错误
    let file = std::fs::File::open("file.txt")?;

    // 可能返回解析错误
    let number: i32 = "abc".parse()?;

    Ok(())
}

RC 引用计数

RcRust 标准库提供的一个类型,它允许我们在程序的多个部分之间共享数据的所有权

Rcreference counting 的缩写,它通过在堆上分配内存来存储引用计数,每次创建一个新的引用时,引用计数会增加,每次引用离开作用域时,引用计数会减少,当引用计数为 0 时,内存会被释放

Rc 可以看成是 Box 的高级版本,Box 只能有一个所有者,而 Rc 可以有多个所有者

0 -> 1
       => 4
2 -> 3

上面这 4 节点有多个所有者,所以就需要用 Rc 来包装

在这里 four.clone()Rc::clone(&four) 效果是一样的,都是增加引用计数

use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

fn main() {
    let four = Rc::new(List::Cons(4, Rc::new(List::Nil)));
    let zero_one = List::Cons(0, Rc::new(List::Cons(1, four.clone())));
    let two_three = List::Cons(2, Rc::new(List::Cons(3, Rc::clone(&four))));
}

使用 Rc 就可以做到一个值可以有多个引用

Vec 动态数组和 HashMap

Vec 是动态数组,与切片一样,它的大小是未知的,它内部有三个参数:

  • 指向数据的指针
  • 长度
  • 容量
fn main() {
    let mut v = Vec::new();
    for i in 1..=5 {
        v.push(i);
    }

    println!("{:?}", v);
}
// 或者
fn main() {
    let mut v = vec![1, 2, 3];
    v.push(4);
    println!("{:?}", v);
}

HashMap 是一种 key/value 的数据结构,与 Vec 类似,它也可以动态调整

fn main() {
    let mut transcript: HashMap<&str, u32> = HashMap::new();

    transcript.insert("Alice", 93);
    transcript.insert("Bob", 85);
    transcript.insert("Charlie", 87);

    transcript.remove(&"Bob");

    let alice_grade = transcript.get(&"Alice");
    match alice_grade {
        Some(grade) => println!("Alice's grade: {}", grade),
        None => println!("Alice's grade not found"),
    }
}

字符串

rust 中字符串有:

  • &str
  • String
  • &[u8]
  • &[u8; N]
  • Vec<u8>
  • &u8
  • OsStr
  • OsString
  • Path
  • PathBuf
  • CStr
  • CString
  • &'static str
  1. 函数的参数如果是字符串,最好使用 &str,这样可以接受 &strString
  2. 结构体成员如果是字符串,最好使用 String,用 &str 类型,需要手动指定生命周期

let a = "Hello World!" 是一个 &str 类型,Hello World 这个数据他是保存在二进制文件中,被保存在数据段的区域内(在整个程序运行期间永远不会变)

虽然我们看到 rust 推断的结果是 &str,但是实际上 a 是一个 &'static str 类型,如下:

let a: &str = "Hello World!";
let b: &'static str = "Hello World!"; // 具有静态生命周期的一个引用

要知道一点 str 类型几乎不会使用,我们通常使用 &strstr 它代表的是在内存中(数据段,代码段...堆,栈)的字符串数据,如果我们要使用这数据,只能去使用它的引用,也就是 &str&str 可以引用数据段中的内容,也可以引用堆中的内容

String 类型是可变的,它拥有自己的数据,它是存储在堆中

fn main() {
    let mut s = String::from("Hello");
    s.push_str(" World!");
    println!("{}", s);
}