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

在自引用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。核心思路是:

  • 定义HasText trait,统一所有拥有Text字段类型的访问接口;
  • 用OwningRef<Arc<dyn HasText>, Text>封装所有者和引用,借助Erased trait 完成类型擦除。

不过更简洁的方式是直接结合ouroboros与 trait 类型擦除,无需额外依赖owning_ref。

你的unsafe代码的安全性问题

这段unsafe代码存在严重的未定义行为风险,绝对不能使用:

  1. 语法错误:Arc<dyn Any>没有text字段,&owner.text根本无法编译;
  2. 类型安全问题:无法保证Arc<dyn Any>指向的实际类型包含Text字段,强制转换会导致非法内存访问;
  3. 内存布局风险:即使类型有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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 19:57:39