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

单线程下为已被可变引用的对象保留读写控制的Rust最佳方式

单线程下实现同一对象多可变引用的解决方案

问题场景与原代码问题

需要测试持有Target结构体可变引用的Consumer结构体行为,单线程环境下仅能通过直接读写Target验证正确性,但原代码因违反Rust借用规则无法编译:

use std::io::Write;

struct Consumer<'a, W: Write> {
    out: &'a mut W
}

impl<'a, W: Write> Consumer<'a, W> {
    pub fn consume(&mut self, data: &[u8]) {
        // do something else
        self.out.write(data).unwrap();
    }
}

struct Target {
    buf: Vec<u8>
}

impl Target {
    pub fn drain(&mut self) {
        self.buf.clear();
    }
}

impl Write for Target {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.buf.extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

mod tests {
    use crate::{Target, Consumer};

    #[test]
    fn test_that_consumes_ok() {
        let mut t = Target{ buf: Vec::new() };
        let mut c = Consumer{ out: &mut t };
        c.consume(b"hello");
        c.consume(b"world");

        // error in this line:
        assert_eq!(t.buf.as_slice(), b"helloworld");

        // also error:
        t.drain();

        c.consume(b"new");
        assert_eq!(t.buf.as_slice(), b"new");
    }
}


fn main() {}

问题核心:无需Arc<Mutex>,如何在单线程下为同一对象提供多位置的可变引用支持?


解决方案:使用Rc<RefCell<T>>实现内部可变性

单线程下,Rc<RefCell<T>>是解决这类问题的标准方案:Rc提供同一对象的多所有权,RefCell提供运行时的借用检查,允许我们在多个位置获取可变引用(只要运行时不违反借用规则)。

修改后的完整代码

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

// Consumer持有Rc<RefCell<W>>,而非直接的可变引用
struct Consumer<W: Write> {
    out: Rc<RefCell<W>>
}

impl<W: Write> Consumer<W> {
    pub fn consume(&mut self, data: &[u8]) {
        // 通过borrow_mut()获取可变引用,运行时检查借用规则
        self.out.borrow_mut().write(data).unwrap();
    }
}

struct Target {
    buf: Vec<u8>
}

impl Target {
    pub fn drain(&mut self) {
        self.buf.clear();
    }
}

impl Write for Target {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.buf.extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

mod tests {
    use crate::{Target, Consumer};
    use std::cell::RefCell;
    use std::rc::Rc;

    #[test]
    fn test_that_consumes_ok() {
        // 用Rc<RefCell>包裹Target
        let t = Rc::new(RefCell::new(Target{ buf: Vec::new() }));
        // 克隆Rc给Consumer,共享所有权
        let mut c = Consumer{ out: Rc::clone(&t) };
        
        c.consume(b"hello");
        c.consume(b"world");

        // 通过borrow()获取不可变引用,验证数据
        assert_eq!(t.borrow().buf.as_slice(), b"helloworld");

        // 通过borrow_mut()获取可变引用,调用drain
        t.borrow_mut().drain();

        c.consume(b"new");
        assert_eq!(t.borrow().buf.as_slice(), b"new");
    }
}


fn main() {}

关键改动说明

  1. Consumer结构修改:将原有的可变引用&'a mut W替换为Rc<RefCell<W>>,移除了生命周期参数,因为Rc会自动管理对象的生命周期。
  2. consume方法调整:通过self.out.borrow_mut()获取W的可变引用,RefCell会在运行时检查是否存在其他活跃的可变引用,确保安全。
  3. 测试代码调整:
    • 用Rc::new(RefCell::new(Target{...}))创建Target实例;
    • 通过Rc::clone(&t)将所有权克隆给Consumer;
    • 访问Target的内部数据时,用borrow()(读操作)或borrow_mut()(写操作)来获取引用,这两个方法会在运行时确保借用规则被遵守。

注意事项

  • RefCell的借用检查是运行时的,如果违反规则(比如同时存在两个可变引用),程序会panic,因此要确保在单线程下逻辑正确;
  • Rc仅适用于单线程场景,如果是多线程环境,才需要使用Arc<Mutex>或Arc<RwLock>。

内容的提问来源于stack exchange,提问作者los vatos locos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 16:34:59