Rust程序设计第二版-把书读薄之难点使用(五)

20 次浏览
2024年07月27日创建

Rust的问题还是很多,主要是一些关键的使用方法太多了,需要知道什么时候用,为什么用是非常关键的,下面罗列的是基本上贯穿《Rust程序设计 - 第二版》的所有难点理解内容。为了将这些内容融会贯通,需要非常多的例子总结才能理解。下面内容就开始对其中的内容一个一个来梳理。

主要概念

?SizedClone、Copy、Cell、RefCell

接下来介绍上面这些概念。

Sized、?Sized

在rust中,无固定大小的类型使用起来会受限制,不能当函数入参和返回值,通常需要包Box来解决。那么在这种情况下,什么类型是固定大小,什么是非固定大小呢?

let tall_man = tall_man();
println!("{:?}", tall_man);

#[derive(Debug)]
struct ACD {
    age:i32,
    tall: i32
}

pub fn tall_man() -> ACD{
    ACD{age:3, tall:4}
}

上面可以通过编译,但是下面的就不行:

#[derive(Debug)]
struct ACD {
    age:i32,
    tall: i32,
    name: [u8]
}

pub fn tall_man() -> ACD{
    ACD{age:3, tall:4, name:"bob".to_string()}
}

[T]是?Sized,dyn trait是?Sized,切片也是动态大小类型。解决办法:

let tall_man = tall_man();
println!("{:?}", tall_man);

#[derive(Debug)]
struct ACD<'a>{
    age:i32,
    tall: i32,
    name: &'a [u8]
}

pub fn tall_man() -> Box<ACD<'static>>{
    Box::new(ACD{age:3, tall:4, name: "bob".as_bytes() })
}

?Sized包含Sized

fn foo<T: ?Sized>(t: &T) {
    // T 可以是 Sized 或非 Sized
}

作为方法参数

fn takes_unsized<T: ?Sized>(value: &T) {
    // 函数体
}

作为结构体字段

struct MyBox<T: ?Sized> {
    value: Box<T>,
}

impl<T: ?Sized> MyBox<T> {
    fn new(value: Box<T>) -> MyBox<T> {
        MyBox { value }
    }
}

Clone

克隆一个值通常还需要为它拥有的任何值分配副本,因此clone无论在时间消耗还是内存占用方面都是相当昂贵的。例如,克隆 Vec<String> 不仅会复制此向量,还会复制它的每个 String 元 素。像 Rc<T> 和 Arc<T> 这样的引用计数指针类型属于例外, 即克隆其中任何一个都只会增加引用计数并为你返回一个新指针。

标准库中几乎所有能合理复制的类型都实现了 Clone。不仅 bool、 i32 等原始类型实现了 Clone,String、Vec<T> 和 HashMap 等容器类型也实现了 Clone。而那些无法合理复制的类型(如 std::sync::Mutex)则没有实现 Clone。像 std::fs::File 这样的类型虽然可以复制,但如果操作系统无法提供必要的资源,则 复制可能会失败。这些类型也没有实现 Clone,因为 clone 必须是 不会失败的。作为替代,std::fs::File 提供了一个 try_clone 方法,该方法会返回一个 std::io::Result<File> 值,用以报 告失败信息。

clone 方法应该为 self 构造一个独立的副本并返回它。由于此方 法的返回类型是 Self,并且函数本来也不可能返回无固定大小的 值,因此 Clone 特型也是扩展自 Sized 特型的,进而导致其实现 代码中的 Self 类型被限界成了 Sized。

但是下面的代码可以通过编译?

#[derive(Debug, Clone)]
struct ACD<'a>{
    age:i32,
    tall: i32,
    name: &'a [u8]
}

let sssssxx = ACD{age:3, tall:4, name: "bob".as_bytes() };
let adfsfads = sssssxx.clone();

WHY?

什么情况下需要实现Clone?

  1. 你需要显式地复制一个数据结构,而不是转移所有权,即函数后面还想用
  2. 在多线程编程中,有时需要在多个线程之间共享数据的副本,而不是转移所有权:

如果struct的子类型都是Clone,struct是克隆吗?

自动实现 Clone 的前提条件是结构体的所有字段类型都必须实现 Clone。如果结构体的某个字段类型没有实现 Clone,编译器会报错,提示无法自动派生 Clone,必须手动声明Clone。

引用也能clone

非mut支持:

let x = 3;
let y = &x;
let z = y.clone();
print!("{}", z);

mut引用克隆呢?报错:

let mut x = 3;
let y = &mut x;
let z = y.clone();
*z = 4;
print!("{}", z);

在Rust中,可变引用(&mut T)不能被克隆。clone()方法只适用于实现了Clone trait的类型,而原始的引用类型(&T和&mut T)并没有实现Clone。因此,尝试克隆一个可变引用将会导致编译错误。

Copy

因此只有当类型需要一个浅层的逐字节复制时,Rust 才允许它实现 Copy。拥有任何其他资源(比如堆缓冲区或操作系统句柄)的类型都无法实现 Copy。

如果一个 struct 实现了 Copy 特征,那么在 let p2 = p1; 的时候会自动进行按位复制,而不是移动。这意味着 p1 和 p2 都会拥有独立的副本,且 p1 仍然可以继续使用。如果没有实现copy,就会发生所有权移动。

如果struct P所有成员是Copy,那么P是Copy吗:不是,需要手动声明。

什么时候不适合实现 Copy

  • 涉及资源管理的类型:

包含指针、引用、动态分配的内存(如 Box、Vec 等)或其他需要特殊清理的资源。

这些类型的按位复制可能会导致资源泄漏或双重释放等问题。

  • 大数据结构:

包含大量数据的结构体,按位复制的开销可能会很大。对于这些类型,使用 Clone 进行显式的深拷贝可能更合适。

  • 需要独占所有权的类型:

例如,String、Vec 等类型,它们需要独占的所有权来管理其内部的动态内存。这些类型的值在传递时应该被移动,而不是复制。

#[derive(Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1; // 这里发生按位复制,而不是移动
    println!("p1: ({}, {}), p2: ({}, {})", p1.x, p1.y, p2.x, p2.y);
}

在这个例子中,Point 结构体实现了 Copy 和 Clone 特征,因此 p1 可以按位复制给 p2,而不会导致 p1 被移动。

Cell

Cell 是 Rust 标准库中的一个类型,它提供了一种在不使用引用计数(如 Rc)或原子引用计数(如 Arc)的情况下,实现内部可变性的机制。Cell 允许你在不需要可共享的并发访问的情况下,安全地修改不可变数据。

为什么需要 Cell

Rust 的借用检查器强制执行可变性规则:一个值要么有一个可变引用,要么有多个不可变引用,但不能同时拥有两者。这些规则有助于防止数据竞争和悬挂引用。然而,有时你需要在一个不可变的数据结构中修改某些字段。`Cell` 提供了一种安全的方式来实现这种内部可变性。

Cell 的特性

  • Cell 提供了一种内部可变性(interior mutability)的机制。
  • Cell 允许你在不可变的上下文中修改数据。
  • Cell 的主要操作是 get 和 set,它们分别用于获取和设置内部值。

使用 Cell

use std::cell::Cell;

struct MyStruct {
    value: Cell<i32>,
}

impl MyStruct {
    fn new(value: i32) -> Self {
        MyStruct {
            value: Cell::new(value),
        }
    }

    fn get_value(&self) -> i32 {
        self.value.get()
    }

    fn set_value(&self, new_value: i32) {
        self.value.set(new_value);
    }
}

fn main() {
    let my_struct = MyStruct::new(10);
    println!("Initial value: {}", my_struct.get_value());

    my_struct.set_value(20);
    println!("Updated value: {}", my_struct.get_value());
}

  • Cell 只能用于存储实现了 `Copy` 特性的类型。这是因为 `Cell` 的 `get` 方法会复制值,而不是返回引用。
  • 如果你需要存储非 `Copy` 类型的值,可以考虑使用 `RefCell`,它提供了类似的内部可变性,但允许存储任何类型的值,并使用借用检查器在运行时进行检查。

`Cell` vs `RefCell`

- `Cell` 提供的是按值语义的内部可变性,适用于实现了 `Copy` 特性的类型。

- `RefCell` 提供的是按引用语义的内部可变性,适用于任何类型,但在运行时进行借用检查,可能会在不正确的借用模式下引发 panic。

### 总结

`Cell` 是一个强大的工具,允许你在不可变的上下文中修改值,提供了内部可变性。它在需要遵循 Rust 的借用规则但又需要修改数据的场景中非常有用。

在 Rust 中,`Cell` 和 `RefCell` 是两种用于内部可变性的类型,但它们在使用方式和限制上有所不同。

`Cell` 允许在单线程环境中进行内部可变性,但它只适用于存储实现了 `Copy` trait 的类型。`Cell` 提供的方法如 `set`, `get`, 和 `replace`,这些方法允许你在不违反 Rust 的借用规则的情况下修改存储在 `Cell` 中的值。

然而,`Cell` 不允许你直接调用引用类型上的 `mut` 方法,因为它不提供对内部数据的可变引用。这是因为 `Cell` 的设计目标是避免多重可变引用的风险,从而确保线程安全。

如果你需要在共享值上调用 `mut` 方法,你应该使用 `RefCell`。`RefCell` 允许你在运行时检查借用规则,并且可以在单线程环境中提供可变引用。通过 `borrow_mut` 方法,你可以获得一个可变引用,从而调用 `mut` 方法。

以下是一个简单的示例,展示了如何使用 `RefCell` 来调用 `mut` 方法:

use std::cell::RefCell;

struct MyStruct {
    value: i32,
}

impl MyStruct {
    fn increment(&mut self) {
        self.value += 1;
    }
}

fn main() {
    let my_struct = RefCell::new(MyStruct { value: 0 });

    {
        let mut my_struct_mut = my_struct.borrow_mut();
        my_struct_mut.increment();
    }

    println!("Value: {}", my_struct.borrow().value);
}

在这个示例中,我们使用 `RefCell` 包装了 `MyStruct`,并通过 `borrow_mut` 方法获得一个可变引用,从而调用了 `increment` 方法。这样,我们可以在不违反 Rust 借用规则的情况下实现内部可变性。

关键:cell只能set get,refcell能borrow。