使用特征作为幻像类型

Using a trait as phantom type

在 Rust 中,我想使用幻像类型来正确键入一个简单的 id:

struct Id<T> {
    val: u32,
    _type: PhantomData<T>,
}

在初稿版本中,我使用了具体结构 T,一切都很好。然后在使用不同数据源的更精细的版本中,这些结构变成了特征。比方说:

trait MyArticle {
    fn get_id() -> Id<MyArticle>;
}

但是使用 traits 作为 phantom types 会带来问题:

我目前的解决方案是在空结构和特征之间复制名称类型:

struct MyArticle_ {};

trait MyArticle {
    fn get_id() -> Id<MyArticle_>;
}

这很尴尬,但我找不到更好的了。

你的样本的问题在于理解特征是什么。事实上,它 不是 类型(这就是编译器要求 T: ?Sized 的原因),而是对类型的要求。因此解决方案相当简单:想出一个“真正的”类型。你用结构声明做对了,它可以是一个选项。但通常使用关联类型更方便:

trait MyArticle {
    type T;

    fn get_id() -> Id<Self::T>
    where
        Self::T: MyArticle;
}

// so given impls
struct X;

impl MyArticle for X {
    type T = u32;
    fn get_id() -> Id<u32> {
        todo!()
    }
}

impl MyArticle for u32 {
    type T = u32;
    fn get_id() -> Id<u32> {
        todo!()
    }
}

最后,您可以调用 X::get_id() 或完全合格的版本:<X as MyArticle>::get_id()

此外,您可以阅读 there 为什么 fn get_id() -> Id<Box<dyn MyArticle>> 不起作用。

这里的问题是特征本身不是类型,尽管 dyn Trait 是。所以当你写 Id<MyArticle> 时,它实际上意味着 Id<dyn MyArticle>(因此警告),如果 MyArticle 不是对象安全的,它就不会编译。

在这种特殊情况下,您可以使 MyArticle 对象安全:

use std::marker::PhantomData;

struct Id<T: ?Sized> {
    val: u32,
    _type: PhantomData<T>,
}

trait MyArticle {
    fn get_id() -> Id<dyn MyArticle> where Self: Sized;
}

如果您不能或不想使特征对象安全,那么我认为您的空结构解决方案是可行的方法。请注意,如果您只需要一个空结构,您可以使用 struct MyArticle_;.