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
相关产品推荐
相关产品推荐

