使用特征作为幻影类型

问题描述

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

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

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

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

但是使用 trait 作为幻像类型会带来问题:

  • 编译器让我声明 T: ?Sized,好像 T 的大小是潜在的需要。我可以接受,但由于 PhantomData<T> 的目的是告诉 T 不会被使用,我想知道是否还有其他方法
  • 我收到警告:“不推荐使用没有明确 'dyn' 的 trait 对象”。我可以使用全局 #![allow(bare_trait_objects)] 来摆脱它,但此警告在其他方面很有用,我不想这样做。有没有办法允许 bare_trait_object only “当用作 Id<T> 的类型参数时”?

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

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;
}

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