当前位置: 首页 > news >正文

Rust 练习册 14:RefCell 实际使用场景

在之前的练习中,我们学习了 RefCell 的基本概念和用法。今天我们将通过一个具体的示例来深入了解 RefCell 在实际场景中的应用,特别是"对内可变,对外不可变"这一重要设计模式。

RefCell 的核心理念

RefCell 实现了 Rust 中的"内部可变性"模式。这种模式允许我们在不可变的容器中修改数据,这在某些场景下非常有用,尤其是在需要绕过 Rust 严格的借用检查规则时。

正如 README 中提到的:

对内可变,对外不可变

这意味着我们可以保持结构体的不可变性,同时允许其内部某些字段具有可变性。

项目中的示例代码

让我们先看看项目中的示例代码:

use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{mpsc, Arc};
use std::thread;struct Foo;struct Bar {foo: RefCell<Foo>,msg: String,
}impl Foo {fn foo(&mut self, bar: &Bar) {println!("{}", bar.msg);}
}impl Bar {fn new(foo: Foo) -> Self {Self {foo: RefCell::new(foo),msg: String::from("Baaaaaar"),}}fn bar(&mut self) {self.foo.borrow_mut().foo(self);}
}fn main() {let mut bar = Bar::new(Foo);bar.bar();
}

在这个示例中,我们创建了一个有趣的设计模式:

  1. Bar 结构体包含一个 RefCell<Foo> 和一个 String
  2. Foo 结构体有一个方法 foo,它需要 &mut self&Bar 作为参数
  3. Bar 结构体有一个方法 bar,它通过 RefCell 获取 Foo 的可变引用并调用其方法

这种设计允许我们在 Bar 的不可变方法中修改 Foo,同时保持 Bar 本身的不可变性。

RefCell 工作原理

正如 README 中解释的:

RefCell会记录当前存在多少个活跃的 Ref和 RefMut 智能指针:

  • 调用 borrow 时,不可变借用计数加1
  • 任意 Ref的值离开作用域时(释放),不可变借用计数减1
  • 每次调用 borrow_mut: 可变借用计数加1
  • 任何 RefMut 离开使用域时(释放),可变借用计数减1

实际应用场景

1. 图数据结构

use std::cell::RefCell;
use std::rc::Rc;#[derive(Debug)]
struct Node {value: i32,children: RefCell<Vec<Rc<Node>>>,parent: RefCell<Option<Rc<Node>>>,
}impl Node {fn new(value: i32) -> Rc<Node> {Rc::new(Node {value,children: RefCell::new(vec![]),parent: RefCell::new(None),})}fn add_child(self: Rc<Node>, child: Rc<Node>) {*child.parent.borrow_mut() = Some(self.clone());self.children.borrow_mut().push(child);}
}fn graph_example() {let root = Node::new(1);let child1 = Node::new(2);let child2 = Node::new(3);root.clone().add_child(child1);root.clone().add_child(child2);println!("Root: {:?}", root);
}

2. 观察者模式

use std::cell::RefCell;
use std::rc::Rc;trait Observer {fn notify(&self, message: &str);
}struct Subject {observers: RefCell<Vec<Rc<dyn Observer>>>,state: String,
}impl Subject {fn new() -> Subject {Subject {observers: RefCell::new(vec![]),state: String::new(),}}fn attach(&self, observer: Rc<dyn Observer>) {self.observers.borrow_mut().push(observer);}fn set_state(&self, state: String) {self.state = state;self.notify_observers();}fn notify_observers(&self) {for observer in self.observers.borrow().iter() {observer.notify(&self.state);}}
}struct ConcreteObserver {name: String,
}impl ConcreteObserver {fn new(name: String) -> ConcreteObserver {ConcreteObserver { name }}
}impl Observer for ConcreteObserver {fn notify(&self, message: &str) {println!("Observer {}: Received message '{}'", self.name, message);}
}fn observer_pattern_example() {let subject = Rc::new(Subject::new());let observer1 = Rc::new(ConcreteObserver::new("A".to_string()));let observer2 = Rc::new(ConcreteObserver::new("B".to_string()));subject.attach(observer1);subject.attach(observer2);subject.set_state("New State".to_string());
}

3. Mock 对象和测试

use std::cell::RefCell;#[derive(Debug)]
struct MockNetworkClient {calls: RefCell<Vec<String>>,response: String,
}impl MockNetworkClient {fn new(response: String) -> MockNetworkClient {MockNetworkClient {calls: RefCell::new(vec![]),response,}}fn get_calls(&self) -> Vec<String> {self.calls.borrow().clone()}fn send_request(&self, url: String) -> String {self.calls.borrow_mut().push(url);self.response.clone()}
}fn mock_example() {let mock_client = MockNetworkClient::new("Mock Response".to_string());let response1 = mock_client.send_request("http://example.com/1".to_string());let response2 = mock_client.send_request("http://example.com/2".to_string());assert_eq!(response1, "Mock Response");assert_eq!(response2, "Mock Response");let calls = mock_client.get_calls();assert_eq!(calls.len(), 2);assert_eq!(calls[0], "http://example.com/1");assert_eq!(calls[1], "http://example.com/2");
}

与 Rc 配合使用

RefCell 经常与 Rc(引用计数)配合使用,以实现多所有权的可变数据:

use std::cell::RefCell;
use std::rc::Rc;fn rc_refcell_example() {let data = Rc::new(RefCell::new(5));// 多个 Rc 实例可以共享同一个 RefCelllet data1 = data.clone();let data2 = data.clone();// 通过任何一个 Rc 实例都可以修改数据*data1.borrow_mut() += 1;*data2.borrow_mut() += 1;println!("Value: {}", *data.borrow()); // 输出: 7
}

与 Arc 配合使用(线程安全版本)

对于多线程环境,我们可以使用 Arc 和 Mutex:

use std::sync::{Arc, Mutex};
use std::thread;fn arc_mutex_example() {let data = Arc::new(Mutex::new(5));let data1 = data.clone();let data2 = data.clone();let handle1 = thread::spawn(move || {let mut num = data1.lock().unwrap();*num += 1;});let handle2 = thread::spawn(move || {let mut num = data2.lock().unwrap();*num += 1;});handle1.join().unwrap();handle2.join().unwrap();println!("Value: {}", *data.lock().unwrap()); // 输出: 7
}

错误处理

RefCell 在运行时检查借用规则,如果违反规则会导致 panic。我们可以使用 try_borrowtry_borrow_mut 来避免 panic:

use std::cell::RefCell;fn error_handling_example() {let data = RefCell::new(5);// 获取不可变引用let _borrowed = data.borrow();// 尝试获取可变引用(不会 panic)match data.try_borrow_mut() {Ok(_mut_borrowed) => {println!("Got mutable borrow");}Err(_) => {println!("Could not get mutable borrow - already borrowed");}}
}

与项目代码的深入分析

让我们回到原始项目代码,深入分析其中的设计模式:

use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{mpsc, Arc};
use std::thread;struct Foo;struct Bar {foo: RefCell<Foo>,msg: String,
}impl Foo {fn foo(&mut self, bar: &Bar) {println!("{}", bar.msg);}
}impl Bar {fn new(foo: Foo) -> Self {Self {foo: RefCell::new(foo),msg: String::from("Baaaaaar"),}}fn bar(&mut self) {self.foo.borrow_mut().foo(self);}
}fn main() {let mut bar = Bar::new(Foo);bar.bar();
}

这段代码展示了几个重要概念:

  1. 内部可变性Bar 结构体通过 RefCell<Foo> 实现了内部可变性
  2. 方法调用链bar 方法通过 RefCell 获取 Foo 的可变引用,然后调用 Foo 的方法
  3. 循环引用Foo::foo 方法需要 &Bar 参数,而 Bar::bar 方法调用 Foo::foo 并传递 self

这种设计模式在以下场景中非常有用:

  • 当我们需要在不可变的结构体中修改某些内部状态时
  • 当我们需要打破 Rust 的借用规则以实现特定的设计模式时
  • 当我们需要在方法调用中传递包含正在修改对象的引用时

最佳实践

1. 合理使用 RefCell

use std::cell::RefCell;// 好的做法:在需要内部可变性时使用 RefCell
struct Counter {value: RefCell<i32>,
}impl Counter {fn new(initial: i32) -> Counter {Counter {value: RefCell::new(initial),}}fn increment(&self) {*self.value.borrow_mut() += 1;}fn get_value(&self) -> i32 {*self.value.borrow()}
}// 避免:在不需要内部可变性时使用 RefCell
fn avoid_unnecessary_refcell() {// 如果不需要内部可变性,直接使用变量即可// let data = RefCell::new(5);// let value = *data.borrow();let data = 5; // 更简单直接println!("Value: {}", data);
}

2. 注意运行时开销

use std::cell::RefCell;fn runtime_overhead_awareness() {let data = RefCell::new(42);// 每次 borrow/borrow_mut 都有运行时检查开销for _ in 0..1000 {*data.borrow_mut() += 1;}println!("Final value: {}", *data.borrow());
}

总结

RefCell 是 Rust 中实现内部可变性的重要工具,它允许我们在不可变的容器中修改数据:

  1. 通过运行时借用检查而不是编译时检查来工作
  2. 适用于未实现 Copy 语义的类型
  3. Rc<T> 结合可以实现多所有权的可变数据
  4. 在"对内可变,对外不可变"的设计模式中非常有用

关键要点:

  • borrow() 获取不可变引用
  • borrow_mut() 获取可变引用
  • 违反借用规则会导致运行时 panic
  • 有运行时开销,应谨慎使用
  • 常与 Rc 配合使用以实现复杂的数据结构

通过合理使用 RefCell,我们可以编写出更加灵活的 Rust 代码,同时保持内存安全。

http://www.dtcms.com/a/578370.html

相关文章:

  • 新手做网站优化怎么做宾馆网站模板
  • app软件网站开发门户网站建设思维导图
  • java学习--重载
  • 懒人手机网站模板个人网页制作成品图
  • OpenAI 生产环境终极指南:从原型到规模化
  • YL4056H单节锂电池线性充电芯片
  • 静态IP与动态IP的定义
  • RAID技术全面解析:从基础原理到组合算法
  • 2.3.5 浮点数的表示与运算【2017统考真题】
  • 购物网站建设渠道字体在线生成器
  • 阿里云官方网站 icp代备案管理系统重庆网站推广什么
  • 《十五五规划》下的AI边缘计算机遇:算力下沉与工业智能化
  • 签订网站建设合同商城类网站价格
  • 《链改2.0:从数字资产到RWA》新书宁波重磅发布
  • 基于Springboot的智慧管网灌溉系统i1agupa7(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
  • 正则表达式可视化 - 正则表达式可视化与文本匹配工具
  • JavaScript 正则表达式:选择、分组与引用深度解析
  • 20251106给荣品RD-RK3588-MID开发板跑Rockchip的原厂Android14系统时更换wallpaper墙纸
  • 桂林做网站多少钱做网站给不给源代码
  • 如何使用 Nodemon 自动重启 Node.js 应用
  • Tongweb8安装部署
  • 汽车网站设计模板网页图片批量下载
  • Playwright高级用法全解析:从自动化到工程化的进阶指南
  • 【开源】FBro 浏览器工作流自动化系统
  • 从手动到智能:XTOM-STATION自动化检测中心在复杂曲面零件全尺寸检测中的应用
  • 【题解】洛谷 P4201 [NOI2008] 设计路线 [树形 DP]
  • ES索引配置字段解读
  • linux服务器自动备份数据库脚本
  • 免费的微信视频号下载器
  • 做ic销售的各种网站dede网站安全设置