Rust的问题还是很多,主要是一些关键的使用方法太多了,需要知道什么时候用,为什么用是非常关键的,下面罗列的是基本上贯穿《Rust程序设计 - 第二版》的所有难点理解内容。为了将这些内容融会贯通,需要非常多的例子总结才能理解。下面内容就开始对其中的内容一个一个来梳理。
?Sized、Clone、Copy、Cell、RefCell
接下来介绍上面这些概念。
在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() })
}
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无论在时间消耗还是内存占用方面都是相当昂贵的。例如,克隆 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 的前提条件是结构体的所有字段类型都必须实现 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。因此,尝试克隆一个可变引用将会导致编译错误。
因此只有当类型需要一个浅层的逐字节复制时,Rust 才允许它实现 Copy。拥有任何其他资源(比如堆缓冲区或操作系统句柄)的类型都无法实现 Copy。
如果一个 struct 实现了 Copy 特征,那么在 let p2 = p1; 的时候会自动进行按位复制,而不是移动。这意味着 p1 和 p2 都会拥有独立的副本,且 p1 仍然可以继续使用。如果没有实现copy,就会发生所有权移动。
如果struct P所有成员是Copy,那么P是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 是 Rust 标准库中的一个类型,它提供了一种在不使用引用计数(如 Rc)或原子引用计数(如 Arc)的情况下,实现内部可变性的机制。Cell 允许你在不需要可共享的并发访问的情况下,安全地修改不可变数据。
Rust 的借用检查器强制执行可变性规则:一个值要么有一个可变引用,要么有多个不可变引用,但不能同时拥有两者。这些规则有助于防止数据竞争和悬挂引用。然而,有时你需要在一个不可变的数据结构中修改某些字段。`Cell` 提供了一种安全的方式来实现这种内部可变性。
使用 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` 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。