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

使用RefCell<T>时遇到奇怪的编译期借用错误

问题:Ratatui中RefCell/RefMut引发的编译期借用错误

环境与代码背景

  • 使用Ratatui库开发程序,rustc版本1.77.2
  • 核心代码片段:
self.terminal().draw(|frame| self.render_frame(frame))?;
  • 细节说明:
    • terminal()是接收&self的getter方法,最初直接返回结构体中的Terminal实例,但返回&mut Terminal会触发借用错误
    • 改为将Terminal字段用RefCell<Terminal>包裹,terminal()返回RefMut<Terminal>,内部调用self.terminal.borrow_mut()
    • draw是Ratatui的屏幕绘制方法,闭包中调用的self.render_frame需要&mut self作为参数

遇到的编译错误

error[E0502]: cannot borrow self as mutable because it is also borrowed as immutable
--> src/tui.rs:75:38
|
75 | self.terminal().draw(|frame| self.render_frame(frame))?;
| --------------- ^^^^^^^ ---- - ... and the immutable borrow might be used here, when that temporary is dropped and runs the destructor for type RefMut<'_, ratatui::Terminal<ratatui::backend::CrosstermBackend<Stdout>>>
| | | |
| | | second borrow occurs due to use of self in closure
| | mutable borrow occurs here
| immutable borrow occurs here
| a temporary with access to the immutable borrow is created here ...

疑问

原本以为RefMut是拥有所有权的类型,不受编译期借用检查器管控,只要render_frame里不调用terminal()就不会有问题,但实际触发了编译错误。想明确:

  1. 对RefCell/RefMut的使用哪里理解有误?
  2. 如何修复这个错误?

问题解析与解决方案

错误原因

虽然RefCell的借用规则是运行时检查,但RefMut<'a, T>包含生命周期'a,这个生命周期会绑定到self的借用上。调用self.terminal()时,编译器会推断RefMut的生命周期与&self的借用周期一致;而闭包中尝试获取&mut self时,RefMut的临时值还未销毁(直到整个draw方法执行完毕),这就触发了编译期的借用冲突——不可变借用未结束时,无法获取可变借用。

修复方案

方案1:拆分操作,提前持有RefMut

把terminal()的调用拆出来,用局部变量持有RefMut,让编译器明确生命周期范围,避免闭包中借用self的冲突:

let mut terminal = self.terminal();
terminal.draw(|frame| self.render_frame(frame))?;

方案2:分离render_frame所需状态

如果render_frame不需要访问Terminal相关字段,可将这部分状态拆分到单独结构体中,闭包仅借用子结构体的可变引用,避免与Terminal的借用冲突:

// 调整结构示例
struct App {
    terminal: RefCell<Terminal<CrosstermBackend<Stdout>>>,
    ui_state: RefCell<UiState>, // 存放render_frame需要的状态
}

struct UiState {
    // 示例状态字段
    counter: u32,
}

impl UiState {
    fn render(&mut self, frame: &mut Frame) {
        // 渲染逻辑
    }
}

impl App {
    fn draw(&self) -> Result<(), Box<dyn Error>> {
        let mut terminal = self.terminal();
        terminal.draw(|frame| {
            let mut ui_state = self.ui_state.borrow_mut();
            ui_state.render(frame);
        })?;
        Ok(())
    }
}

方案3:使用UnsafeCell(谨慎使用)

如果能严格保证render_frame不会访问Terminal字段,可使用UnsafeCell绕过编译期检查,但需自行保证运行时借用安全:

struct App {
    terminal: UnsafeCell<Terminal<CrosstermBackend<Stdout>>>,
}

impl App {
    fn terminal(&self) -> &mut Terminal<CrosstermBackend<Stdout>> {
        unsafe { &mut *self.terminal.get() }
    }

    fn draw(&self) -> Result<(), Box<dyn Error>> {
        self.terminal().draw(|frame| {
            // 必须确保render_frame不会访问terminal字段
            let self_mut = unsafe { &mut *(self as *const Self as *mut Self) };
            self_mut.render_frame(frame);
        })?;
        Ok(())
    }
}

注意:该方案使用unsafe代码,若违反借用规则会导致未定义行为,非必要不推荐使用。

内容的提问来源于stack exchange,提问作者Iris

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 17:52:40