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

PyO3中Rust与Python回调间传递可变Rust结构体遇借用冲突

问题根源

你遇到的是Rust可变借用规则和Python回调的生命周期冲突:

  • 当Descriptor::eval调用Python的to_string时,PyRefMut<Context>作为参数传递,这个可变借用会被call_method持有,直到Python函数执行完毕返回。
  • 而Python函数内部又回调eval_template,试图再次获取Context的可变借用(mut ctx: PyRefMut<ReprContext>),这直接违反了Rust的核心规则——同一时间只能存在一个可变引用,所以动态借用检查器报错。
两种可行解决方案

方案1:用全局引用Py<Context>传递

把Context的全局引用传给Python,需要修改时再临时获取可变借用,避免长期占用可变引用:

Rust侧修改代码

impl Descriptor {
    fn eval(&self, ctx: &PyRefMut<Context>) -> String {
        Python::with_gil(|py| {
            // 将临时可变借用转为全局Py引用
            let ctx_global = Py::from(py, ctx);
            self.inner.call_method(py, "to_string", (ctx_global,), None)
                .unwrap()
                .downcast_bound::<PyString>(py)
                .unwrap()
                .to_str()
                .unwrap()
                .to_string()
        })
    }
}

#[pyfunction]
fn eval_template(ctx: Py<Context>, s: &str) -> String {
    Python::with_gil(|py| {
        // 临时获取可变借用,用完就释放
        let mut ctx_ref = ctx.borrow_mut(py).unwrap();
        // 这里写你处理s和ctx的逻辑
        format!("handled {} with state: {}", s, ctx_ref.some_field)
    })
}

Python侧代码不用改

class Class(object):
    @staticmethod
    def to_string(ctx):
        return rust.eval_template(ctx, '$foo')

方案2:给Context加上内部可变性

用RefCell包裹Context的可变状态,这样可以通过不可变引用修改内部数据,绕过跨函数的可变借用冲突:

修改Context定义

use std::cell::RefCell;

// 把需要修改的状态放到InnerState里,用RefCell包裹
struct Context {
    inner: RefCell<InnerState>,
}

struct InnerState {
    // 你的状态字段,比如foo
    foo: String,
}

调整eval_template和Descriptor::eval

#[pyfunction]
fn eval_template(ctx: PyRef<Context>, s: &str) -> String {
    // 通过RefCell获取内部可变引用
    let mut inner_state = ctx.inner.borrow_mut();
    // 修改状态
    inner_state.foo = format!("processed: {}", s);
    inner_state.foo.clone()
}

impl Descriptor {
    fn eval(&self, ctx: PyRef<Context>) -> String {
        Python::with_gil(|py| {
            self.inner.call_method(py, "to_string", (ctx,), None)
                .unwrap()
                .downcast_bound::<PyString>(py)
                .unwrap()
                .to_str()
                .unwrap()
                .to_string()
        })
    }
}
核心逻辑说明
  • PyRefMut是临时可变借用,生命周期绑定到当前GIL作用域,一旦传给Python函数,整个Python执行期间这个借用都被锁定,无法再拿第二个可变借用。
  • Py<T>是全局引用,可以安全在Rust和Python之间传递,需要访问时再临时获取借用,不会长期占用。
  • RefCell实现了内部可变性,把借用检查从编译期移到运行期,适合GIL保护下的单线程场景,不会有线程安全问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 07:18:18