在自引用Rust crate中擦除所有者类型的实现方案
我定义了包含Text字段的结构体Tweet,借助ouroboros crate实现了带Owner泛型的自引用结构体BorrowedText,通过Arc<Owner>确保所有者不会被提前释放。由于存在大量拥有Text字段的类型,为避免生成过多BorrowedText<T>类型,我希望擦除所有者类型,改用Arc<dyn Any>作为所有者。但尝试用ouroboros或owning_ref(含ErasedBoxRef)均未成功实现从Arc<Tweet>获取&Text并向上转型为Arc<dyn Any>的逻辑。请问是否可通过ErasedBoxRef或其他API实现该需求?或者能否安全使用以下unsafe代码替代自引用crate?
struct Tweet { text: Text } #[ouroboros::self_referencing] struct BorrowedText<Owner> { owner: Arc<Owner>, // 确保所有者在借用期间不会被释放 #[borrows(owner)] borrowed: &'this Text, } #[ouroborus::self_referencing] // 注:此处拼写应为ouroboros struct BorrowedText { owner: Arc<dyn Any>, // 确保所有者在借用期间不会被释放 #[borrows(owner)] borrowed: &'this Text, } // 尝试的unsafe初始化代码 BorrowedText { owner: owner.clone(), borrowed: unsafe { (&owner.text) as (*const Text) } }
关于ErasedBoxRef/owning_ref的实现思路
可以通过owning_ref的类型擦除能力实现,但需要结合 trait 保证安全获取&Text,而非直接使用dyn Any。核心思路是:
- 定义
HasTexttrait,统一所有拥有Text字段类型的访问接口; - 用
OwningRef<Arc<dyn HasText>, Text>封装所有者和引用,借助Erasedtrait 完成类型擦除。
不过更简洁的方式是直接结合ouroboros与 trait 类型擦除,无需额外依赖owning_ref。
你的unsafe代码的安全性问题
这段unsafe代码存在严重的未定义行为风险,绝对不能使用:
- 语法错误:
Arc<dyn Any>没有text字段,&owner.text根本无法编译; - 类型安全问题:无法保证
Arc<dyn Any>指向的实际类型包含Text字段,强制转换会导致非法内存访问; - 内存布局风险:即使类型有
Text字段,也无法保证字段的内存位置符合预期,直接强制转换引用会破坏Rust的内存安全模型。
推荐的安全实现方案
方案1:用自定义trait实现类型擦除
定义HasText trait,让所有拥有Text字段的类型实现它,再用Arc<dyn HasText>作为所有者,配合ouroboros安全构建自引用结构体:
use ouroboros::self_referencing; use std::sync::Arc; struct Text(String); // 定义trait统一获取Text的接口 trait HasText { fn get_text(&self) -> &Text; } struct Tweet { text: Text, } impl HasText for Tweet { fn get_text(&self) -> &Text { &self.text } } // 其他拥有Text字段的类型同样实现HasText即可 #[self_referencing] struct BorrowedText { owner: Arc<dyn HasText>, #[borrows(owner)] borrowed: &'this Text, } impl BorrowedText { // 安全的构造函数 fn new(owner: Arc<dyn HasText>) -> Self { Self::build(|owner| owner.get_text()) } }
该方案完全避免unsafe代码,既实现类型擦除,又保证引用有效性,同时不会产生泛型爆炸。
方案2:基于dyn Any的安全实现(需类型检查)
如果一定要用dyn Any,必须在构造时通过downcast确认类型合法性,避免不安全操作:
use ouroboros::self_referencing; use std::any::Any; use std::sync::Arc; struct Text(String); struct Tweet { text: Text, } trait HasText { fn get_text(&self) -> &Text; } impl HasText for Tweet { fn get_text(&self) -> &Text { &self.text } } #[self_referencing] struct BorrowedText { owner: Arc<dyn Any>, #[borrows(owner)] borrowed: &'this Text, } impl BorrowedText { // 构造时检查类型合法性,返回Option避免非法实例 fn new<T: HasText + 'static>(owner: Arc<T>) -> Option<Self> { let owner_any = owner.clone() as Arc<dyn Any>; Self::try_build(|owner| { owner.downcast_ref::<T>().map(|t| t.get_text()) }, owner_any) } }
此方案依赖T: 'static(dyn Any的要求),通过downcast_ref确保类型正确,只有合法类型才能构造出BorrowedText,避免了未定义行为。
内容的提问来源于stack exchange,提问作者mq7

