如何将Rust结构体拆分为读写分离对象?解决引用冲突
安全实现只读流与可写数据的分离
需求
将结构体Foo拆分为独立的只读对象和可写对象,使readonly方法返回的流具备bar的只读访问能力,同时main函数可随时修改bar的值。
初始代码与编译错误
原有代码尝试直接通过引用生成流,但受限于Rust的借用检查规则,修改bar时触发编译错误:
use futures::stream::StreamExt; struct Foo { bar: u8 } impl Foo { fn readonly(self: &Self) -> impl futures::stream::Stream<Item = u8> + '_ { futures::stream::iter(0..10).map(|_x| { self.bar }) } } #[tokio::main] async fn main() { let mut foo = Foo {bar: 123u8}; let mut stream = foo.readonly(); let a = stream.next().await; println!("a: {:?}", a); foo.bar = 132; let b = stream.next().await; println!("b: {:?}", b); }
编译报错:
error[E0506]: cannot assign to `foo.bar` because it is borrowed --> src/main.rs:23:5 | 18 | let mut stream = foo.readonly(); | -------------- borrow of `foo.bar` occurs here ... 23 | foo.bar = 132; | ^^^^^^^^^^^^^ assignment to borrowed `foo.bar` occurs here 24 | 25 | let b = stream.next().await; | ------------- borrow later used here
尝试Rc的问题
使用Rc<RefCell>包裹结构体后,因RefCell的借用规则,流持有只读借用时修改数据会触发运行时panic:
use std::{cell::RefCell, rc::Rc}; use futures::stream::StreamExt; struct Foo { bar: u8 } impl Foo { fn readonly<'a>(self: &'a Self) -> impl futures::stream::Stream<Item = u8> + 'a { futures::stream::iter(0..10).map(|_x| { self.bar }) } } #[tokio::main] async fn main() { let foo = Rc::new(RefCell::new(Foo {bar: 123u8})); let foo_cloned = foo.clone(); let foo_borrowed = foo_cloned.borrow(); let mut stream = foo_borrowed.readonly(); let a = stream.next().await; println!("a: {:?}", a); foo.borrow_mut().bar = 0u8; let b = stream.next().await; println!("b: {:?}", b); }
运行panic:
a: Some(123) thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:28:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
unsafe实现(不推荐)
通过unsafe绕过RefCell的借用检查可实现需求,但会破坏Rust的内存安全保障:
#[tokio::main] async fn main() { let foo = Rc::new(RefCell::new(Foo {bar: 123u8})); let foo_cloned = foo.clone(); let foo_borrowed = foo_cloned.borrow(); let mut stream = foo_borrowed.readonly(); let a = stream.next().await; println!("a: {:?}", a); unsafe { (*foo.as_ptr()).bar = 0u8; } let b = stream.next().await; println!("b: {:?}", b); }
输出:
a: Some(123) b: Some(0)
安全的最优实现
单线程场景:使用Cell<u8>
利用Cell提供的单线程内部可变性,避免长期借用导致的冲突:
use futures::stream::StreamExt; use std::cell::Cell; struct Foo { bar: Cell<u8>, } impl Foo { fn readonly(&self) -> impl futures::stream::Stream<Item = u8> + '_ { futures::stream::iter(0..10).map(|_| { self.bar.get() }) } } #[tokio::main] async fn main() { let foo = Foo { bar: Cell::new(123u8) }; let mut stream = foo.readonly(); let a = stream.next().await; println!("a: {:?}", a); foo.bar.set(132); let b = stream.next().await; println!("b: {:?}", b); }
说明:
Cell<u8>的get()和set()操作都是瞬时的,不会持有Foo的引用,因此流的闭包不会独占数据。- 完全符合Rust安全规范,编译无错误、运行无panic。
多线程场景:使用Arc<AtomicU8>
若需在多线程环境下(比如流在独立任务中运行),使用原子类型配合Arc实现安全的共享读写:
use futures::stream::StreamExt; use std::sync::{Arc, atomic::{AtomicU8, Ordering}}; struct Foo { bar: Arc<AtomicU8>, } impl Foo { fn readonly(&self) -> impl futures::stream::Stream<Item = u8> + '_ { let bar = self.bar.clone(); futures::stream::iter(0..10).map(move |_| { bar.load(Ordering::Relaxed) }) } } #[tokio::main] async fn main() { let foo = Foo { bar: Arc::new(AtomicU8::new(123u8)) }; let mut stream = foo.readonly(); let a = stream.next().await; println!("a: {:?}", a); foo.bar.store(132, Ordering::Relaxed); let b = stream.next().await; println!("b: {:?}", b); }
说明:
Arc允许多线程共享同一数据,AtomicU8保证读写操作的原子性,无需锁即可安全修改。Ordering::Relaxed适用于仅需保证bar自身原子性的场景,若需同步其他内存操作可调整为更强的内存顺序。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

