仅当字段是某个枚举变体时才为结构定义方法?

Defining a method for a struct only when a field is a certain enum variant?

我有以下结构:

#[derive(Debug)]
pub struct Entry {
    pub index: usize,
    pub name: String,
    pub filename_offset: u64,
    pub entry_type: EntryType,
}

#[derive(Debug)]
pub enum EntryType {
    File {
        file_offset: u64,
        length: usize,
    },

    Directory {
        parent_index: usize,
        next_index: usize,
    },
}

Entry 是 GameCube ROM 文件系统 table 中的一个条目,它描述了一个文件或目录。我为 Entry 定义了各种方法,例如 Entry::read_filenameEntry::write_to_disk。但是,我有一些对常规文件和目录都可用的方法没有意义。例如,Entry::iter_contents 遍历目录的所有子条目。

我希望能够为 entry_type 是特定变体的条目定义某些方法,例如 Entry::iter_contents

我尝试将 EntryType 变成一个特征并制作了一个 DirectoryEntryInfoFileEntryInfo 结构,它们都实现了 EntryType.

遗憾的是,这种方法存在一些问题。我在别处有一个 Vec<Entry>,经过此更改后它将变为 Vec<Entry<EntryType>>。使用这样的特征,我无法将 Entry<EntryList> 向下转换为 Entry<DirectoryEntryInfo>。我也尝试用 Any 做一些事情,因为这是我知道在 Rust 中向下转型的唯一方法,但我只能转型 entry_type,而不是整个 Entry 本身。

最终,我想以类似这样的方式结束:

impl<T: EntryType> Entry<T> {
    pub fn as_dir(&self) -> Option<Entry<DirectoryEntryInfo>> { ... }
    pub fn as_file(&self) -> Option<Entry<FileEntryInfo>> { ... }
    ...
}

impl Entry<DirectoryEntryInfo> {
    ...
}

impl Entry<FileEntryInfo> {
    ...
}

这样,我可以在不知道它是目录还是文件的情况下访问所有条目字段,并且能够将其转换为可以为我提供所有 Entry 字段以及基于类型参数的方法,如 Entry::iter_contents.

没有像 RFC 1450 这样的东西,有没有好的方法来做到这一点?

我知道枚举变体不是它们自己的类型,不能用作类型参数。我只是在寻找一种替代方法来有条件地为一个结构定义一个方法,并且仍然能够有办法将这个结构的任何变体存储在 Vec 之类的东西中。 This article 非常接近我想要做的事情。但是,使用它的示例,如果不知道 Bool 在编译时是 True 还是 False,则无法存储 MyEnum<Bool>。能够将 MyEnum<Box<Bool>> 之类的东西向下转换为 MyEnum<False> 会解决这个问题,但我不知道 Rust 中有类似的东西。

不幸的是,你不能相当,因为(如问题评论中所述)并且有关变体的信息不可用类型系统。

一种可能的方法是 "hoist" enum 到外层,并让每个变体包含一个 struct 来包装共享数据:

struct EntryInfo {
    index: usize,
    name: String,
    filename_offset: u64,
}

pub struct FileEntry {
    info: EntryInfo,
    file_offset: u64,
    length: usize,
}

pub struct DirEntry {
    info: EntryInfo,
    parent_index: usize,
    next_index: usize,
}

pub enum Entry {
    File(FileEntry),
    Dir(DirEntry),
}

然后您可以很容易地定义 as_fileas_dir,如下所示:

impl Entry {
    pub fn as_dir(&self) -> Option<&DirEntry> {
        match *self {
            Entry::Dir(ref d) => Some(d),
            _ => None,
        }
    }

    pub fn as_file(&self) -> Option<&FileEntry> {
        match *self {
            Entry::File(ref f) => Some(f),
            _ => None,
        }
    }
}

这并不理想,因为您之前在 Entry 上编写的任何代码现在都需要在适当的变体中遵循 EntryInfo。可以使事情变得更容易的一件事是编写一个辅助方法来查找包装的 EntryInfo:

fn as_info(&self) -> &EntryInfo {
    match *self {
        Entry::Dir(ref d) => &d.info,
        Entry::File(ref f) => &f.info,
    }
}

那么在Entry的实现中可以使用self.as_info()代替self.info