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

时间:2018-01-05 20:24:09

标签: enums rust type-safety type-parameter downcast

我有以下结构:

#[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文件系统表中的一个条目,用于描述文件或目录。我为Entry定义了各种方法,例如Entry::read_filenameEntry::write_to_disk。但是,我有一些方法对常规文件和目录都没有意义。例如,Entry::iter_contents遍历所有目录的子条目。

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

我尝试将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> {
    ...
}

这样,我可以访问所有条目字段,而无需知道它是否是目录或文件,并且能够将其转换为能够为我提供所有{{1除了基于类型参数的方法(如Entry

)之外的字段

有没有像RFC 1450这样的方法可以做到这一点?

我知道枚举变体不是他们自己的类型,不能用作类型参数。我只是在寻找另一种方法来有条件地为结构定义一个方法,并且仍然能够有一种方法来存储这个结构的任何变体,如Entry::iter_contentsThis article非常接近我要做的事情。但是,使用其中的示例,无法在编译时知道VecMyEnum<Bool>还是Bool,而无法存储True。能够将诸如False之类的内容转发给MyEnum<Box<Bool>>会解决这个问题,但我并不知道Rust中的任何内容。

1 个答案:

答案 0 :(得分:3)

不幸的是,你不能相当,因为(如问题评论中所述)enum variants are not types并且类型系统无法获得有关变体的信息。

一种可能的方法是将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,
    }
}

然后,您可以在self.as_info()的实施中使用self.info代替Entry