Rust 中 Rc、Arc 与内部可变性总结

281 阅读3分钟

Rust 中 Rc、Arc 与内部可变性总结

在 Rust 中,所有权默认是唯一的,也就是说,一个数据只有一个所有者。当所有者离开作用域时,数据会被自动释放。

但在某些场景中,我们希望让多个地方共享对同一数据的所有权,这就需要使用共享所有权。

怎么理解呢? 比如在使用Config对象的场景下使用Rc:

struct Config { }

fn main() {
    let config = Rc::new(Config { });

    let module_a_config = Rc::clone(&config);
    let module_b_config = Rc::clone(&config);
    let module_c_config = Rc::clone(&config);
}

这样做的好处:

  1. Config只创建了一次,避免重复分配内存。
  2. 每个module使用Config不用担心所有权或者生命周期的问题。
  3. module模块都不再需要配置时,数据会自动释放。

如果使用&Config,很容易出现生命周期的报错或者出现悬挂指针的问题。

Rc和Arc实际上是打破了Rust对所有权的限制。

Rc和Arc

  • Rc 和 Arc 都是智能指针,它们通过内部的引用计数机制来管理数据的所有权。每当有一个新的引用计数指针创建时,它的引用计数就会增加;当引用计数降为零时,数据会被自动销毁。

  • 共享所有权:打破了一个数据只有一个所有者。允许多个所有者共享对同一个数。 据的所有权,确保当最后一个所有者离开时,数据被释放。

  • Rc是单线程场景下共享所有权

  • Arc是多线程场景下共享所有权

只读使用方式

struct Config {
    name: String,
    version: String,
}

impl Config {
    pub fn new(name: &str, version: &str) -> Self {
        Self {
            name: name.to_string(),
            version: version.to_string(),
        }
    }

    fn print(&self) {
        println!("name = {}, verison = {}", self.name, self.version);
    }

    fn print_msg(&self, msg: &str) {
        println!("name = {}, version = {}, msg = {}", self.name, self.version, msg);
    }
}

fn main() {
    let config = Rc::new(Config::new("app", "v0.0.1"));

    let module_config = Rc::clone(&config);

    println!("name = {}", config.name);
    println!("version = {}", config.version);
    module_config.print();
    module_config.print_msg("Hello, world!");

    // 也可以正常借用
    let module_config_ref = &module_config;
    println!("module_config_ref.name = {}", module_config_ref.name);
}

只读取, Rc和Config 使用上几乎一样。

fn main() {
        let arc_config = Arc::new(Config::new("arc app", "v0.0.2"));

    let tasks: Vec<_> = (1..5).map(move |i| {
        let module_arc_config_ref = Arc::clone(&arc_config);
        thread::spawn(move || {
            module_arc_config_ref.print_msg(format!("thread {}", i).as_str());
        })
    })
        .collect();

    for task in tasks {
        task.join().unwrap();
    }
}

Arc使用上类似。

修改内部值

这里要提到rust另一个规则:

  • 不可变借用(&T):可以同时拥有多个,不会改变数据。

  • 可变借用(&mut T):只能有一个,并且可以修改数据,但在此期间不能有不可变借用。

当使用Rc创建了多个所有者之后,如果要修改值的话,也就违反了 可变借用(&mut T)规则。

需要用Refell或者Mutex来破坏这一规则。

  • RefCell 是在单线程中使用的,可以提供内部可变性,但只适用于单线程场景。

  • Mutex 是在多线程环境中使用的,确保同一时间只能有一个线程访问数据,相当于锁。

所以要修改多所有者的数据需要使用Rc<RefCell> 或者 Arc<Mutex>

  • Rc<RefCell>
struct Config {
    name: String,
    version: String,
}

impl Config {
    pub fn new(name: &str, version: &str) -> Self {
        Self {
            name: name.to_string(),
            version: version.to_string(),
        }
    }

    fn print(&self) {
        println!("name = {}, version = {}", self.name, self.version);
    }

    fn print_msg(&self, msg: &str) {
        println!("name = {}, version = {}, msg = {}", self.name, self.version, msg);
    }

    fn set_version(&mut self, new_version: &str) {
        self.version = new_version.to_string();
    }
}

fn main() {
    let config = Rc::new(RefCell::new(Config::new("app", "v0.0.1")));

    let module_config = Rc::clone(&config);

    {
        // 可变借用修改 version
        let mut cfg_mut = config.borrow_mut();
        cfg_mut.set_version("v0.0.2");
    }

    {
        // 使用不可变借用打印
        let cfg = module_config.borrow();
        cfg.print();
        cfg.print_msg("Hello, Rc<RefCell>!");
    }

    {
        // 再修改
        // 使用不可变借用打印
        let mut module_cfg_mut = module_config.borrow_mut();
        module_cfg_mut.set_version("v1.0.2");
    }

    // 再借用看看字段
    println!("version from main: {}", config.borrow().version);
}

这里需要使用borrow(), borrow_mut()获取Ref 再操作。

  • Arc<Mutex>
fn main() {
    let arc_config = Arc::new(Mutex::new(Config::new("arc app", "v0.0.2")));

    let tasks: Vec<_> = (1..5).map(|i| {
        let module_arc_config_ref = Arc::clone(&arc_config);
        thread::spawn(move || {
            let mut config = module_arc_config_ref.lock().unwrap();
            config.set_version(&format!("-t{}", i));
            config.print_msg(&format!("from thread {}", i));
        })
    }).collect();

    for task in tasks {
        task.join().unwrap();
    }

    // 主线程查看最终版本
    let final_config = arc_config.lock().unwrap();
    println!("Final config: name = {}, version = {}", final_config.name, final_config.version);
}

Mutex使用时需要先lock() 之后操作,会自动解锁。