以Rust风格从函数返回传入或新建结构体的可变引用
问题分析与解决方案
你的核心问题是要编写一个函数,既可以接收外部的可变引用,也能在无输入时创建新实例,最终返回可修改的对象。原代码的错误在于:
- 当
complex_opt为None时,新建的Complex是函数局部变量,返回它的引用会导致悬垂引用(函数结束后局部变量被销毁); - 无法用单一生命周期标注统一两种情况:传入的引用生命周期由调用方控制,而新建实例的生命周期仅限于函数内部。
不需要用Arc(它适用于多线程共享所有权场景),最符合Rust idiom的方式是用自定义枚举封装两种所有权状态:要么持有外部可变引用,要么持有拥有所有权的实例。
实现代码
1. 定义枚举并实现解引用 trait
首先定义一个枚举来包装两种情况,同时实现Deref和DerefMut,让枚举可以像普通可变引用一样使用:
use std::ops::{Deref, DerefMut}; struct Complex { value: i32, } impl Complex { fn new() -> Complex { Complex { value: 3 } } fn setup(&mut self) { self.value = 2; } fn change(&mut self) { self.value += 5; } } // 封装可变引用或拥有所有权的实例 enum EitherMut<'a, T> { Borrowed(&'a mut T), Owned(T), } // 实现Deref,支持不可变访问 impl<'a, T> Deref for EitherMut<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { match self { EitherMut::Borrowed(t) => t, EitherMut::Owned(t) => t, } } } // 实现DerefMut,支持可变访问 impl<'a, T> DerefMut for EitherMut<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { match self { EitherMut::Borrowed(t) => t, EitherMut::Owned(t) => t, } } }
2. 修改目标函数
调整do_it函数,返回上述枚举类型,统一处理两种输入情况:
fn do_it(complex_opt: Option<&mut Complex>) -> EitherMut<'_, Complex> { let mut complex = match complex_opt { Some(c) => EitherMut::Borrowed(c), None => { let mut c = Complex::new(); c.setup(); EitherMut::Owned(c) } }; // 直接调用可变方法,DerefMut自动解引用 complex.change(); complex }
3. 使用示例
调用函数时,可以像操作普通Complex可变引用一样使用返回值:
fn main() { // 情况1:传入外部可变引用 let mut existing = Complex { value: 10 }; let mut result1 = do_it(Some(&mut existing)); result1.change(); println!("Existing value: {}", existing.value); // 输出15 // 情况2:无输入,创建新实例 let mut result2 = do_it(None); println!("New value: {}", result2.value); // 输出7 }
为什么这是正确的方式
- 枚举明确区分了两种所有权状态,避免了悬垂引用问题;
- 通过
Deref和DerefMuttrait,枚举的使用方式与普通可变引用完全一致,无需额外适配代码; - 没有运行时开销,比使用内部可变性(如
RefCell)或引用计数(如Arc)更高效。
内容的提问来源于stack exchange,提问作者Jan Diederich
相关产品推荐
相关产品推荐

