淘先锋技术网

首页 1 2 3 4 5 6 7

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提供了多种智能指针,主要有:

    1. Box:在堆上分配值的智能指针。
    • 实现了Deref特性,可以像引用一样使用。
    • 实现了Drop特性,可以自动释放堆内存。
    • 无法实现Copy特性,仅有一个所有者。
      例如:
    let x = 5;
    let y = Box::new(x);
    
    println!("{}", (*y) + 1); // 解引用访问值,打印6
    
    1. 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 
    
    1. 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可以在线程间共享同一个值
    
    1. 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
    
    1. 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>仍然有效,可以获取强引用  
    }
    
    1. 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();  // 编译错误,无法获取可变借用  
    
    1. 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();                // 转换为拥有,可以修改值
    
    1. PhantomData:用于泛型类型的参数而无需使用的“虚幻”数据的智能指针。
    • 没有实际的数据,只用于作为泛型函数或类型的类型参数。
    • 用于编译时的类型检查。在运行时,它占用零大小。
      例如:
    struct Foo<T> {
        x: i32,
        phantom: PhantomData<T>,   // 没有实际数据,只用于类型参数 
    }
    
    fn bar<T>(_: T) { 
        let _ = PhantomData;     // 用于编译时类型检查
    }
    
    1. 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
    
    1. 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;  
    }
    
    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();  
    
    1. 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();  // 等待其他线程
        });  
    } 
    
    1. 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!")); 
    
    1. 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:创建一个迭代器在队列中接收消息。
      使用这些方法,我们可以实现更为高级的线程间通信与协作。
    1. std::sync::Semaphore:实现信号量的同步类型。
    • 用于限制可以同时访问某些资源的线程数量。
    • 使用acquire方法获取一个许可,使用release方法释放一个许可。
      例如:
    use std::sync::Semaphore;
    
    let sem = Semaphore::new(5);   // 设置最大5个许可 
    
    sem.acquire();      // 获取一个许可
    do_some_work();
    sem.release();      // 释放许可
    
    1. 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); // 恢复线程