重温引用
引用的本质只是表示内存中某些字节起始位置的数字。 它唯一的目的就是表示特定类型的数据存在于何处。引用与数字的不同之处在于, Rust 将验证引用自身的生命周期不会超过它执行的内容。
指针
引用可以转换成一个更原始的类型, 指针。 像数字一样,它可以不受限制的复制和传递,但是Rust不保证它指向的内存位置的有效性。 有两种指针类型:
*const T- 指向永远不会改变的T类型数据的指针*mut T- 指向可以更改的T类型数据的指针
指针可以与数字相互转换(例如usize)。
指针可以使用 unsafe 代码访问数据
内存细节:
- Rust中的引用在用法上与 C 中的指针非常相似,但在如何存储和传递给其他函数上有更多的编译时间限制。
- Rust中的指针类似于 C 中的指针,它表示一个可以复制或传递的数字,甚至可以转换为数字类型,可以将其修改为数字以进行指针数学运算。
fn main() {
let a = 42;
let memory_location = &a as *const i32 as usize;
println!("Data is here {}", memory_location);
}
解引用
&引用*解引用 rust 可以自动的去解多重&
智能指针
通常, 智能指针实现了Deref、DerefMut 和 Drop 特征。 以指定当使用* 和 . 运算符解引用应该触发的逻辑。
use std::ops::Deref;
struct TattleTell<T> {
value: T,
}
impl<T> Deref for TattleTell<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
println!("{} was used!", std::any::type_name::<T>());
&self.value
}
}
fn main() {
let a = 42;
let memory_location = &a as *const i32 as usize;
println!("Data is here {}", memory_location);
let foo = TattleTell {
value: "secret message",
};
println!("{}", foo.len());
}
智能不安全代码
智能指针倾向于经常使用不安全的代码。如前所述,它们是与 Rust 中最低级别的内存进行交互的常用工具。
什么是不安全代码? 不安全代码的行为与普通 Rust 完全一样,除了一些 Rust 编译器无法保证的功能。
不安全代码的主要功能是解引用指针。 这意味着将原始指针指向内存中的某个位置并声明“此处存在数据结构!” 并将其转换为您可以使用的数据表示(例如将*const u8 转换为u8)。 Rust 无法跟踪写入内存的每个字节的含义。 因为 Rust 不能保证在用作 指针 的任意数字上存在什么,所以它将解引用放在一个 unsafe { ... } 块中。
智能指针广泛地被用来解引用指针,它们的作用得到了很好的证明。
fn main() {
let a: [u8; 4] = [86, 14, 73, 64];
// this is a raw pointer. Getting the memory address
// of something as a number is totally safe
let pointer_a = &a as *const u8 as usize;
println!("Data memory location: {}", pointer_a);
// Turning our number into a raw pointer to a f32 is
// also safe to do.
let pointer_b = pointer_a as *const f32;
let b = unsafe {
// This is unsafe because we are telling the compiler
// to assume our pointer is a valid f32 and
// dereference it's value into the variable b.
// Rust has no way to verify this assumption is true.
*pointer_b
};
println!("I swear this is a pie! {}", b);
}
引用计数
Rc 是一个能将数据从栈移动到智能指针。 它允许我们克隆其他 Rc 智能指针, 这些指针都具有不可改变地借用放在堆上的数据的能力。
只有当最后一个智能指针被删除时,堆上的数据才会被释放。
use std::rc::Rc;
struct Pie;
impl Pie {
fn eat(&self) {
println!("tastes better on the heap!");
}
}
fn main() {
let heap_pie = Rc::new(Pie);
let heap_pie2 = heap_pie.clone();
let heap_pie3 = heap_pie2.clone();
heap_pie3.eat();
heap_pie2.eat();
heap_pie.eat();
}
共享访问
RefCell 是一个容器数据结构,通常由智能指针拥有,它接收数据并让我们借用可变或不可变引用来访问内部内容。 当您要求借用数据时,它通过在运行时强制执行 Rust 的内存安全规则来防止借用被滥用
只有一个可变引用或多个不可变引用,但不能同时有!
如果你违反了这些规则,RefCell 将会panic。
use std::cell::RefCell;
struct Pie {
slices: u8
}
impl Pie {
fn eat(&mut self) {
println!("tastes better on the heap!");
self.slices -= 1;
}
}
fn main() {
// RefCell validates memory safety at runtime
// notice: pie_cell is not mut!
let pie_cell = RefCell::new(Pie{slices:8});
{
// but we can borrow mutable references!
let mut mut_ref_pie = pie_cell.borrow_mut();
mut_ref_pie.eat();
mut_ref_pie.eat();
// mut_ref_pie is dropped at end of scope
}
// now we can borrow immutably once our mutable reference drops
let ref_pie = pie_cell.borrow();
println!("{} slices left",ref_pie.slices);
}
线程间共享
Mutex 是一种容器数据结构,通常由智能指针持有,它接收数据并让我们借用对其中数据的可变和不可变引用。 这可以防止借用被滥用,因为操作系统一次只限制一个 CPU 线程访问数据,阻塞其他线程,直到原线程完成其锁定的借用。
多线程超出了 Rust 之旅的范围,但 Mutex 是协调多个 CPU 线程访问相同数据的基本部分。
有一个特殊的智能指针 Arc,它与 Rc 相同,除了使用线程安全的引用计数递增。 它通常用于对同一个 Mutex 进行多次引用。
use std::sync::Mutex;
struct Pie;
impl Pie {
fn eat(&self) {
println!("only I eat the pie right now!");
}
}
fn main() {
let mutex_pie = Mutex::new(Pie);
// let's borrow a locked immutable reference of pie
// we have to unwrap the result of a lock
// because it might fail
let ref_pie = mutex_pie.lock().unwrap();
ref_pie.eat();
}
组合智能指针
智能指针看起来可能会存在一些限制,但是我们可以做一些非常有用的结合。
Rc<Vec<Foo>> - 允许克隆多个可以借用堆上不可变数据结构的相同向量的智能指针。
Rc<RefCell<Foo>> - 允许多个智能指针可变/不可变地借用相同的结构Foo
Arc<Mutex<Foo>> - 允许多个智能指针以 CPU 线程独占方式锁定临时可变/不可变借用的能力。
内存细节:
- 您会注意到一个包含许多这些组合的主题。 使用不可变数据类型(可能由多个智能指针拥有)来修改内部数据。 这在 Rust 中被称为“内部可变性”模式。 这种模式让我们可以在运行时以与 Rust 的编译时检查相同的安全级别来改变内存使用规则。