You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 18:11:09