Rust的所有权(ownership)和智能指针(smart pointers)是它的两个重要特征。理解它们是掌握Rust特性和编写Rust程序的基础。
所有权(Ownership)
- Rust中的每个值都有一个被称为其“所有者”的变量。
- 值的所有者决定该值的生命周期。
- 当所有者超出作用域,该值将被丢弃。
例如:
fn main() {
let x = 5; // x becomes the owner of the integer 5
// do something...
// x goes out of scope here and is dropped. The integer 5 is dropped too.
}
这里当x超出作用域后,其所有的值也会被丢弃。
所有权可以通过移动(move)从一个变量转移到另一个变量:
fn main() {
let x = 5;
let mut y = x; // x loses ownership here and is no longer valid
// do something...
}
// x has already been dropped here, y is the only owner left
Rust禁止在没有所有权的情况下使用值,这可以有效地防止“双乘出错”等问题。
智能指针(Smart Pointers)
-
智能指针是封装了原始指针的结构体,可以轻松实现自动内存管理。
Rust提供了多种智能指针,主要有:- Box:在堆上分配值的智能指针。
- 实现了Deref特性,可以像引用一样使用。
- 实现了Drop特性,可以自动释放堆内存。
- 无法实现Copy特性,仅有一个所有者。
例如:
let x = 5; let y = Box::new(x); println!("{}", (*y) + 1); // 解引用访问值,打印6
- Rc:基于引用计数的共享所有权智能指针。
- 可以有多个所有者共享同一个值。
- 通过clone方法增加引用计数。
- 当引用计数为0时,自动释放内存。
- 实现了Deref特性,可以像引用一样使用。
- 无法在线程间使用。
例如:
let x = 5; let rc = Rc::new(x); let rc1 = rc.clone(); let rc2 = rc.clone(); println!("{} {}", rc, rc1); // 5 5
- Arc:线程安全的Rc。
- 可以在线程间共享值。
- 除了线程安全外,其他用法与Rc相同。
例如:
use std::sync::Arc; let x = 5; let arc = Arc::new(x); let arc1 = arc.clone(); let arc2 = arc.clone(); println!("{} {}", arc, arc1); // 5 5
let x = 5; let y = Arc::new(x); // 在堆上分配一个可在线程间共享的整数 let y1 = y.clone(); // 增加引用计数 let y2 = y.clone(); // y, y1, y2可以在线程间共享同一个值
- RefCell:提供运行时借用检查的智能指针。
- 实现了Deref特性,可以像引用一样使用。
- 允许我们在不可变环境下修改借用的值。
- 运行时检查借用规则,确保同一时间只有一个可变借用或多个不可变借用。
例如:
use std::cell::RefCell; let x = RefCell::new(5); *x.borrow_mut() += 1; // 可以修改借用的值 let y = x.borrow(); // 同一时间只能有一个可变借用 let z = x.borrow_mut(); // 错误,已经有一个可变借用y
- Weak:对应Rc的弱引用智能指针。
- 不会增加Rc的引用计数。
- 可以从Rc的强引用转换而来。
- 用来解决Rc的循环引用问题。
例如:
use std::rc::Rc; use std::rc::Weak; let rc = Rc::new(5); let weak = Rc::downgrade(&rc); // 得到弱引用 if let Some(rc) = weak.upgrade() { println!("{}", rc); // 如果Rc<T>仍然有效,可以获取强引用 }
- Cell:提供内部值修改的智能指针。
- 实现了Deref特性,可以像引用一样使用。
- 允许我们在不可变环境下修改借用的值。
- 不同于RefCell,它的借用检查是在编译时进行的。
- 无法在线程间使用,与Arc配合可以实现线程安全。
例如:
use std::cell::Cell; let c = Cell::new(5); let x = c.get(); // 可以获取不可变借用 c.set(x + 1); // 仍然可以修改值 let y = &c.get(); // 编译错误,无法获取可变借用
- Mutex:提供互斥锁的智能指针。
- 可以在线程间共享值。
- 需要使用lock方法获取互斥锁来访问值。
- 与Arc配合,可以实现线程安全的共享值修改。
例如:
use std::sync::Mutex; let m = Mutex::new(5); { let mut value = m.lock().unwrap(); // 获取互斥锁 *value += 1; // 修改值 } // 互斥锁自动释放
理解所有权与智能指针的工作方式,可以让我们编写无bug,高效,优雅的Rust代码。它们是Rust的核心概念,是学习和掌握Rust的基石。
8. Cow:拥有或借用值的智能指针。- 可以从借用或拥有的值创建。
- 在需要拥有的值时会自动转换为拥有状态。
- 实现了Deref特性,可以像引用一样使用。
例如:
let s = "hello".to_owned(); // 拥有值 let cow1 = Cow::Borrowed(&s); // 借用cow1 let mut cow2 = Cow::Borrowed(&s); cow2.to_mut(); // 转换为拥有,可以修改值
- PhantomData:用于泛型类型的参数而无需使用的“虚幻”数据的智能指针。
- 没有实际的数据,只用于作为泛型函数或类型的类型参数。
- 用于编译时的类型检查。在运行时,它占用零大小。
例如:
struct Foo<T> { x: i32, phantom: PhantomData<T>, // 没有实际数据,只用于类型参数 } fn bar<T>(_: T) { let _ = PhantomData; // 用于编译时类型检查 }
- Pin
:用于“钉住”数据的智能指针,防止被move。
- 实现了Deref特性,可以像引用一样使用。
- 使用Unpin特性标记类型是否可以被“拔出”。
- 主要用于异步环境等需要“钉住”数据的场景。
例如:
let mut x = 5; let pin = Pin::new(&mut x); let y = &mut *pin; // ok,可以获取pin的可变借用 let z = *pin; // 错误,pin不能被move
- std::sync::RwLock:提供读写锁的智能指针。
- 可以在线程间共享值。
- 读锁可以被多个读线程加锁。写锁是独占的。
- 与Arc配合,可以实现线程安全的共享值读写。
例如:
use std::sync::RwLock; let lock = RwLock::new(5); { let r = lock.read().unwrap(); // 获取读锁 println!("reader: {:?}", *r); } { let mut w = lock.write().unwrap(); // 获取写锁 *w += 1; }
- std::sync::Condvar:提供条件变量的智能指针。
- 用于线程间协作,基于监视器模式实现。
- 线程通过condvar等待某个条件达成,其他线程通过notify方法唤醒等待线程。
- 需要与Mutex或RwLock配合使用,来加锁访问共享数据。
例如:
use std::sync::{Condvar, Mutex}; let pair = Mutex::new((1, false)); let condvar = Condvar::new(); let mutex_guard = pair.lock().unwrap(); condvar.wait(mutex_guard); // 等待条件变量,释放锁 // 唤醒线程 let mut mutex_guard = pair.lock().unwrap(); *mutex_guard.1 = true; condvar.notify_one();
- std::sync::Barrier:实现屏障的同步类型。
- 允许多个线程在某个点上等待直到所有线程都到达该点。
- 当所有线程都到达后,所有线程会被释放继续执行。
例如:
use std::sync::Barrier; let barrier = Barrier::new(3); for i in 0..3 { let thread = std::thread::spawn(move|| { for j in 0..3 { println!("Thread {} iteration {}", i, j); } barrier.wait(); // 等待其他线程 }); }
- std::sync::Once:执行初始化一次代码的智能指针。
- 保证某段代码在多线程环境下只执行一次。
- 使用call_once方法执行代码,如果已执行会直接返回。
例如:
use std::sync::Once; static INIT: Once = Once::new(); INIT.call_once(|| println!("Only once!")); INIT.call_once(|| println!("Never again!"));
- std::sync::mpsc:多生产者单消费者队列。
- 允许多个生产者同时发送数据到队列。
- 但是只有一个消费者可以从队列接收数据。
- 使用channel方法创建发送端和接收端。
use std::sync::mpsc; let (tx, rx) = mpsc::channel(); let producer = std::thread::spawn(move|| { tx.send(1).unwrap(); tx.send(2).unwrap(); }); let val = rx.recv().unwrap(); println!("Got: {}", val); let val = rx.recv().unwrap(); println!("Got: {}", val); producer.join().unwrap();
mpsc队列还提供了其他有用的方法:
- try_recv:非阻塞地尝试接收消息。
- send_timeout:发送消息,如果在指定时间内未被接收则返回错误。
- recv_timeout:接收消息,如果在指定时间内未接收到消息则返回错误。
- iter:创建一个迭代器在队列中接收消息。
使用这些方法,我们可以实现更为高级的线程间通信与协作。
- std::sync::Semaphore:实现信号量的同步类型。
- 用于限制可以同时访问某些资源的线程数量。
- 使用acquire方法获取一个许可,使用release方法释放一个许可。
例如:
use std::sync::Semaphore; let sem = Semaphore::new(5); // 设置最大5个许可 sem.acquire(); // 获取一个许可 do_some_work(); sem.release(); // 释放许可
- park_timeout/unpark:用于线程挂起和恢复的函数。
- 使用park_timeout方法挂起当前线程指定时间。
- 使用unpark方法恢复被挂起的线程。
例如:
use std::thread; use std::sync::mpsc; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; let (tx, rx) = mpsc::channel(); let suspended = Arc::new(AtomicBool::new(false)); let t = thread::spawn(move|| { while !suspended.load(Ordering::Relaxed) { } // 等待被挂起 thread::park_timeout(Duration::from_secs(5)); // 挂起5秒 tx.send(()).unwrap(); // 发送消息 }); suspended.store(true, Ordering::Relaxed); // 挂起线程 rx.recv().unwrap(); // 接收消息 suspended.store(false, Ordering::Relaxed); // 恢复线程