在 Rust 中,变量的可变性有两种核心方式:

  • 使用 mut 关键字声明可变变量

  • 使用 RefCell<T> 提供内部可变性

本篇博客将通过对比和示例,讲解这两者的异同和适用场景。


一、使用mut声明可变变量

这是最常规的可变变量声明方式,所有可变行为都必须在编译时通过检查

fn main() {
    let mut s = String::from("hello");
    s.push_str(" world");
    println!("{}", s); // 输出:hello world
}

特点:

  • 编译器在编译时就保证你不会同时拥有可变引用和不可变引用

  • 性能最优,但灵活性不高,尤其是在嵌套结构中


二、使用RefCell<T>实现内部可变性

RefCell<T> 提供运行时的“内部可变性”:即使在不可变引用下,也可以对数据进行修改。

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(String::from("hello"));
    data.borrow_mut().push_str(" world");
    println!("{}", data.borrow());
}

你甚至可以在不可变借用中修改内容:

fn modify(data: &RefCell<String>) {
    data.borrow_mut().push_str("!");
}

fn main() {
    let s = RefCell::new(String::from("hello"));
    modify(&s);
    println!("{}", s.borrow()); // 输出:hello!
}

注意:如果在运行时违反了借用规则(比如两个可变引用或可变+不可变并存),RefCell 会 panic。

举个例子:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(String::from("hello"));

    let r1 = data.borrow();      // 不可变借用
    let r2 = data.borrow_mut();  // 尝试可变借用 -> 会在运行时 panic!

    println!("{}", r1);
}

运行时会报错:

thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:6

这是因为 Rust 的借用规则要求:

  • 要么多个不可变引用

  • 要么一个可变引用

    不能两者并存,而 RefCell 在运行时动态检测这个约束。


三、两者对比总结

特性

mut

RefCell<T>

可变性检查方式

编译时检查

运行时检查(违反借用规则会 panic)

可变借用数量限制

编译时仅允许一个可变引用

可动态判断,只要借用规则不冲突即可

是否允许在不可变上下文中修改

❌ 不允许

✅ 允许(通过内部可变性)

是否线程安全

✅(默认使用需满足借用规则)

❌(RefCell<T> 不是线程安全)

使用场景

简单结构、无共享需求

嵌套结构、共享但仍需修改的字段(如 UI 状态)


四、何时使用 RefCell<T>

  1. 结构体整体是不可变的,但某个字段需要动态修改;

  2. 多个组件需要共享数据,并在内部修改;

  3. 和 Rc<T> 配合,用于多所有者 + 内部可变;

use std::cell::RefCell;
use std::rc::Rc;

struct App {
    state: Rc<RefCell<String>>,
}

fn main() {
    let shared_state = Rc::new(RefCell::new(String::from("start")));
    let app1 = App { state: Rc::clone(&shared_state) };
    let app2 = App { state: Rc::clone(&shared_state) };

    app1.state.borrow_mut().push_str(" - updated by app1");
    app2.state.borrow_mut().push_str(" - updated by app2");

    println!("最终状态: {}", shared_state.borrow());
}

五、小结

方式

检查方式

是否线程安全

是否适合共享

是否适合嵌套结构

性能

mut

编译时

最优

RefCell<T>

运行时

✅(需配合 Rc)

略低

选择哪种方式取决于你的具体需求。如果能用 mut 解决问题,那就是最好的选择;若需要灵活性且场景符合 RefCell 的使用边界,那它是你构建复杂数据结构的强大工具。


本站由 困困鱼 使用 Stellar 创建。