Rust中实现延迟计算且仅计算一次的只读属性方案
实现延迟计算且缓存的OrderLine total方法
需求回顾
total方法需延迟计算:仅在首次调用时计算结果,后续调用直接复用缓存值,禁止在结构体初始化时预计算- 保持结果一致性:
- 若结构体字段允许修改,修改后下次调用
total必须重新计算 - 若可变逻辑实现难度大,可将结构体设为不可变以避免修改
- 若结构体字段允许修改,修改后下次调用
方案1:不可变结构体(简单实现)
将结构体设为不可变,通过Option<f32>缓存计算结果,首次调用total时执行计算并填充缓存,后续直接返回缓存值。
实现代码
struct OrderLine { price: f32, quantity: f32, cached_total: Option<f32>, } impl OrderLine { // 构造函数初始化时缓存为空,保证延迟计算 fn new(price: f32, quantity: f32) -> Self { Self { price, quantity, cached_total: None, } } fn total(&mut self) -> f32 { // 优先读取缓存,未计算则执行计算并缓存 if let Some(total) = self.cached_total { total } else { println!("total has been computed"); let total = self.price * self.quantity; self.cached_total = Some(total); total } } }
测试验证
修改题目测试代码后运行:
#[cfg(test)] mod tests { use super::*; #[test] fn test_total_must_be_computed_only_once() { let mut order_line = OrderLine::new(10.0, 2.0); println!("before calling total for the first time"); println!("{}", order_line.total()); println!("before calling total for the second time"); println!("{}", order_line.total()); // 预期输出: // before calling total for the first time // total has been computed // 20 // before calling total for the second time // 20 } }
运行后total has been computed仅打印一次,完全符合预期。
补充:内部可变性版本(total接收&self)
如果需要total方法在不可变引用下调用,使用RefCell实现内部可变性:
use std::cell::RefCell; struct OrderLine { price: f32, quantity: f32, cached_total: RefCell<Option<f32>>, } impl OrderLine { fn new(price: f32, quantity: f32) -> Self { Self { price, quantity, cached_total: RefCell::new(None), } } fn total(&self) -> f32 { let mut cached = self.cached_total.borrow_mut(); if let Some(total) = *cached { total } else { println!("total has been computed"); let total = self.price * self.quantity; *cached = Some(total); total } } }
测试时无需将order_line声明为mut,同样能实现缓存效果。
方案2:支持字段修改的可变结构体
允许修改price和quantity,每次修改字段时清空缓存,确保下次调用total时重新计算。
实现代码
use std::cell::RefCell; struct OrderLine { price: RefCell<f32>, quantity: RefCell<f32>, cached_total: RefCell<Option<f32>>, } impl OrderLine { fn new(price: f32, quantity: f32) -> Self { Self { price: RefCell::new(price), quantity: RefCell::new(quantity), cached_total: RefCell::new(None), } } // 修改price并清空缓存 fn set_price(&self, new_price: f32) { *self.price.borrow_mut() = new_price; *self.cached_total.borrow_mut() = None; } // 修改quantity并清空缓存 fn set_quantity(&self, new_quantity: f32) { *self.quantity.borrow_mut() = new_quantity; *self.cached_total.borrow_mut() = None; } fn total(&self) -> f32 { let mut cached = self.cached_total.borrow_mut(); if let Some(total) = *cached { total } else { println!("total has been computed"); let total = *self.price.borrow() * *self.quantity.borrow(); *cached = Some(total); total } } }
测试验证
新增测试验证修改字段后的重新计算逻辑:
#[cfg(test)] mod tests { use super::*; #[test] fn test_total_recalculates_after_modification() { let order_line = OrderLine::new(10.0, 2.0); println!("First total call: {}", order_line.total()); // 打印计算日志,结果20 println!("Second total call: {}", order_line.total()); // 无日志,结果20 // 修改price order_line.set_price(15.0); println!("Third total call: {}", order_line.total()); // 再次打印计算日志,结果30 println!("Fourth total call: {}", order_line.total()); // 无日志,结果30 } }
测试输出中,第一次和第三次调用total时会打印total has been computed,符合修改后重新计算的需求。
内容的提问来源于stack exchange,提问作者Rafael Amadigi Dal Santo
相关产品推荐
相关产品推荐

