Box
特性:
- 在堆上分配内存去存储T,并返回一个指向该内存的指针
- 单一所有权(只能有一个
Box
指向堆上的数据,拥有T的所有权,T不再拥有所有权),只有这个Box
对象才能访问和修改存储在堆上的数据,避免了数据竞争和并发问题 - 离开作用域之后自动释放T内存,或者手动调用drop()方法释放
- 在编译时已知大小(它是一个指向堆上分配的值的指针),但可以存储未知大小的数据(存储的是指向堆上数据的指针,而不是数据本身)
应用场景:
- 递归数据结构,Box是一个非常好的选择。例如,你可以使用Box来创建一个链表或者树形结构。
- 动态大小类型 当你需要在堆上分配一些动态大小的类型(比如trait对象)时,Box很有用
- 处理大型数据 ,使用
Box<T>
可以提供一些额外的性能优势
为什么使用:
- 内存管理:Rust的所有权模型要求在编译时知道所有变量的大小,但有时候我们可能需要在运行时动态地分配内存。Box提供了一种在堆上分配内存的简单方式,并且当Box离开作用域时,它会自动清理其占用的内存。
- 抽象:Box可以用来创建trait对象,这是一种在运行时进行多态的方式。这允许我们编写能够处理不同类型的代码,而这些类型都实现了一个共同的trait。
Cell
Cell<T>
只能用于 Copy
类型,基本的数字类型、布尔类型、字符类型等都实现了 Copy
trait
原理:
get操作的时候:获取 Cell
内部的 T
类型的值的位复制,返回这个复制的值
set操作的时候:接受一个 T
类型的值作为参数,使用这个值替换 Cell
内部的当前值
为什么要求一定实现 Copy trait?
如果不这样约定,
- Cell的get方法就会返回T的引用,就不能随便对其进行修改操作了,因为rust对引用有要求只能存在一个可变引用和多个不可变引用,我们多次调用set或者get的时候实际上获取了多个可变引用或者多个不可变引用,违背了rust的借用规则
- 在set的时候,会对T值进行修改,会导致之前get的值发生了改变,因为是同一个引用
如果使用Copy trait,上面的问题都不会出现
- get方法返回的是T数据的副本,并不会影响原来值的所有权
- set的时候是对原来的值进行替换修改,也不会影响原来值的所有权
get 和set操作实际都不是真正对原始值进行操作的,这种隔离性使得你可以自由的操作数据,而不会受到限制
RefCell
特性:
- 内部可变性:允许在不使用可变引用的情况下修改数据
- 运行时检查: 在运行时检查借用规则的正确性,而不是编译时,违反借用规则仍然会导致panic
- 运行时开销:运行时的借用检查和 panic捕获会导致额外的开销,因此尽量在编译时确定借用规则
- 多线程不安全:非线程安全,不能在多线程中使用
应用场景:
- 避免借用规则限制:RefCell 允许在特定情况下绕过 Rust 的借用规则,使得在不可变引用的同时修改数据成为可能。这在某些场景下非常有用,特别是当你需要在不可变引用的上下文中修改数据时。
- 运行时检查错误:RefCell 在运行时进行借用检查,可以捕获到违反借用规则的错误,并在发生错误时引发 panic。这有助于在开发过程中及时发现和修复问题。
- 灵活性:相比于使用可变引用,使用 RefCell 可以更灵活地管理数据的可变性。RefCell 允许在需要时暂时获取可变引用,而不需要在整个作用域中保持可变引用的所有权。
为什么使用
- 提供借用灵活性,运行时检查而不是编译时
- 数据是不可变的,但是允许内部可变性
演示代码
fn main() {
println!("Hello, 本案例演示了 RefCell<T> 和内部可变性模式!");
let value = RefCell::new(String::from("Hello world"));
// 通过调用borrow_mut()方法获取可变借用,去改变String的值
value.borrow_mut().push('!');
println!("{}", value.into_inner())
}
Cell 和 RefCell异同点
工作方式:
Cell<T>
提供了set
和get
方法来修改和获取内部的值。它通过复制和移动值来实现这一点,因此它只能用于Copy
trait 的类型。RefCell<T>
则提供了borrow
和borrow_mut
方法来获取内部值的不可变引用和可变引用。它使用运行时借用检查来保证借用规则的遵守,如果违反了借用规则(例如,同时存在多个可变引用或者同时存在可变引用和不可变引用),程序会在运行时崩溃。
使用场景:
Cell<T>
适用于需要在不可变引用的上下文中修改值,但该值是Copy
trait 类型的情况。RefCell<T>
适用于需要在运行时改变某个值,但不能或不想改变该值的所有权或可变性的场景,例如,需要在运行时根据条件修改值,或者需要在多个地方共享并修改同一个值。
性能:
Cell<T>
的所有操作都是在编译时完成的,没有运行时开销。RefCell<T>
则有一些运行时开销,因为它需要在运行时检查借用规则。
总的来说,Cell<T>
和 RefCell<T>
都可以在保持 Rust 的所有权和借用规则的同时,提供一种在不可变引用的上下文中修改值的方式。Cell<T>
更简单,性能更好,但只能用于 Copy
trait 的类型。而 RefCell<T>
则更灵活,可以用于任何类型,但有一些运行时开销,并且如果使用不当,可能会导致运行时错误。
Rc
Rc 是一种用于共享不可变数据的引用计数指针。它允许多个指针共享同一个数据,并且在没有任何所有者时自动释放数据。使用 Rc 可以简化对共享数据的所有权管理,并提供一种有效的方式来处理循环引用和共享不可变数据的情况。
特性:
- 引用计数自动管理,支持对不可变引用进行自动的增加(clone方法)或者减少(引用在作用域结束的时候)引用计数次数
- 共享所有权,Rc 允许多个 Rc 指针共享同一个数据,通过Rc::Clone方法使用
使用场景方面:
- 共享不可变数据:当需要在多个地方共享不可变数据时(实际就是读取数据的时候),可以使用 Rc。Rc 允许多个 Rc 指针同时拥有对数据的不可变引用,而不需要使用可变引用。
- 循环引用:当存在循环引用的情况,即 A 指向 B,B 又指向 A,而且它们之间都使用了 Rc 指针时,Rc 可以用于解决内存泄漏的问题。通过使用 Rc,可以在不需要数据时适时释放内存。
为什么使用 Rc:
- 共享不可变数据:Rc 允许多个指针共享不可变数据,这在某些场景下非常有用。例如,当多个部分需要访问同一个只读数据时,使用 Rc 可以避免数据的多次拷贝和所有权转移。
- 简化所有权管理:Rc 可以简化对共享数据的所有权管理。它允许多个所有者同时拥有对数据的引用,而不需要在代码中显式传递所有权或使用可变引用。
原始指针
原始指针(Raw Pointer)是指直接操作内存地址的指针,它们通常不受语言的类型系统和安全检查的限制。在 Rust 中,原始指针主要有两种类型:*const T
和 *mut T
,分别表示不可变和可变的原始指针。
Rust 的引用与原始指针有一些相似之处,它们都是用于访问数据的指针。但是,引用在编译时进行了借用检查,确保了内存安全和数据访问的正确性。引用必须遵循借用规则,包括不允许空指针、不允许悬垂指针和不允许多重引用等。
相比之下,原始指针不受这些限制,它们可以指向任意内存地址,可以进行指针运算,可以忽略借用规则,这就增加了潜在的安全风险。原始指针的不安全性主要来自以下几个方面:
- 空指针:原始指针可以为 null,即指向无效的内存地址。当对空指针进行解引用时,会导致程序崩溃或产生未定义行为。
- 悬垂指针:原始指针可以在其所指向的数据被释放后仍然存在。当悬垂指针被解引用时,会访问到无效的内存,导致未定义行为。
- 内存安全:原始指针没有借用规则的限制,可以同时拥有多个可变指针,可能导致数据竞争和未定义行为。
虽然原始指针存在安全风险,但它们在某些特定的场景下仍然有应用的价值。以下是一些原始指针的应用场合:
- 与外部代码交互:当需要与非 Rust 语言编写的库或代码进行交互时,可能需要使用原始指针来传递数据。
- 低级编程和底层操作:在进行低级编程或进行底层操作时,可能需要直接访问内存地址来实现特定的功能。
- FFI(Foreign Function Interface):在与 C 或其他语言进行 FFI 调用时,原始指针通常是传递数据的常用方式。
需要强调的是,使用原始指针需要非常小心,并且需要在必要时使用 unsafe
关键字来标记包含原始指针操作的代码块。这样可以将不安全的操作限制在有限的范围内,并确保在使用原始指针时仍然遵循 Rust 的安全规则。
总结起来,原始指针是直接操作内存地址的指针,与 Rust 的引用相比,原始指针不受类型系统和安全检查的限制。原始指针存在安全风险,包括空指针、悬垂指针和内存安全问题。然而,在与非 Rust 代码交互、低级编程和底层操作以及 FFI 调用等场景下,原始指针仍然有一定的应用价值,但需要小心使用,并使用 unsafe
关键字标记相关的不安全操作。
// ... 更多功能等待实现 ...