Rust中如何从枚举子类型的引用获取枚举类型的引用?
问题分析
你的代码里有两个关键错误,本质原因是你试图返回一个指向临时创建的Shape实例的引用,但这个临时实例会在函数结束后立即销毁,导致悬空引用,同时类型匹配也存在问题:
- 第一个错误:
Shape::R(r)需要接收Rect类型的值,但r是&Rect(因为iter().find返回的是&(usize, Rect),解构后得到的是引用);就算你尝试解引用*r,也会因为Rect存储在Vec中(属于Container)而无法移动它(因为你只有&self,不能修改或移动容器内的元素)。 - 第二个错误:直接返回
&Circle,但函数签名要求返回&Shape,这两个类型完全不兼容——Circle不是Shape的子类型,&Circle也不能隐式转换为&Shape。
下面给出几种可行的解决方案,你可以根据实际需求选择:
方案一:调整Container存储结构,直接存储Shape
最简单的解决方式是让Container存储Vec<(usize, Shape)>,而不是分开存储矩形和圆形。这样你可以直接找到对应的&Shape返回:
#[derive(Debug)] struct Rect {} #[derive(Debug)] struct Circle {} #[derive(Debug)] enum Shape { R(Rect), C(Circle), } struct Container { shapes: Vec<(usize, Shape)>, // 直接存储Shape枚举 } impl Container { fn find_shape(&self, id: usize) -> Option<&Shape> { // 直接查找并返回引用 self.shapes.iter().find(|(sid, _)| *sid == id).map(|(_, shape)| shape) } } fn main() { let cont = Container { shapes: vec![ (0, Shape::R(Rect {})), (2, Shape::R(Rect {})), (1, Shape::C(Circle {})), (3, Shape::C(Circle {})), ], }; let s = cont.find_shape(1).unwrap(); println!("shape is {:?}", s); }
优点:逻辑简单,类型安全,不需要额外的设计。
缺点:如果你的业务场景确实需要分开存储Rect和Circle(比如需要单独操作某一类图形),这个方案就不适用了。
方案二:定义持有引用的枚举作为返回类型
如果你必须分开存储Rect和Circle,可以定义一个专门的枚举来持有对这两种类型的引用,代替返回&Shape:
#[derive(Debug)] struct Rect {} #[derive(Debug)] struct Circle {} #[derive(Debug)] enum Shape { R(Rect), C(Circle), } // 定义持有引用的枚举,生命周期绑定到Container的生命周期 #[derive(Debug)] enum ShapeRef<'a> { R(&'a Rect), C(&'a Circle), } struct Container { rects: Vec<(usize, Rect)>, circls: Vec<(usize, Circle)>, } impl Container { fn find_shape(&self, id: usize) -> Option<ShapeRef<'_>> { // 先查找矩形,找到就返回ShapeRef::R self.rects.iter() .find(|(sid, _)| *sid == id) .map(|(_, rect)| ShapeRef::R(rect)) // 没找到的话再查找圆形 .or_else(|| { self.circls.iter() .find(|(sid, _)| *sid == id) .map(|(_, circle)| ShapeRef::C(circle)) }) } } fn main() { let cont = Container { rects: vec![(0, Rect {}), (2, Rect {})], circls: vec![(1, Circle {}), (3, Circle {})], }; let s = cont.find_shape(1).unwrap(); println!("shape is {:?}", s); }
优点:保留了分开存储的结构,同时类型安全,没有运行时开销。
缺点:需要额外定义一个枚举类型,调用方需要处理这个新的枚举。
方案三:使用Trait对象实现动态分发
如果需要更灵活的多态行为,可以定义一个Shape trait,让Rect和Circle实现它,然后返回&dyn Shape trait对象:
use std::fmt::Debug; // 定义Shape trait,继承Debug以便打印 trait Shape: Debug {} #[derive(Debug)] struct Rect {} impl Shape for Rect {} #[derive(Debug)] struct Circle {} impl Shape for Circle {} struct Container { rects: Vec<(usize, Rect)>, circls: Vec<(usize, Circle)>, } impl Container { fn find_shape(&self, id: usize) -> Option<&dyn Shape> { // 查找矩形并转换为trait对象 self.rects.iter() .find(|(sid, _)| *sid == id) .map(|(_, rect)| rect as &dyn Shape) // 没找到则查找圆形并转换 .or_else(|| { self.circls.iter() .find(|(sid, _)| *sid == id) .map(|(_, circle)| circle as &dyn Shape) }) } } fn main() { let cont = Container { rects: vec![(0, Rect {}), (2, Rect {})], circls: vec![(1, Circle {}), (3, Circle {})], }; let s = cont.find_shape(1).unwrap(); println!("shape is {:?}", s); }
优点:灵活性高,后续可以轻松添加新的图形类型,不需要修改返回类型。
缺点:存在轻微的运行时开销(动态分发),且trait对象无法使用Size trait相关的操作(比如直接存储在Vec中而不需要装箱)。
内容的提问来源于stack exchange,提问作者progquester
相关产品推荐
相关产品推荐

