简体   繁体   English

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

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

I have the following struct: 我有以下结构:

#[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 is an entry in a GameCube ROM file system table which describes a file or directory. Entry是GameCube ROM文件系统表中的一个条目,用于描述文件或目录。 I defined various methods for Entry such as Entry::read_filename and Entry::write_to_disk . 我为Entry定义了各种方法,例如Entry::read_filenameEntry::write_to_disk However, I have some methods that don't make sense to be available to both regular files and directories. 但是,我有一些方法对常规文件和目录都没有意义。 For example, Entry::iter_contents iterates over all of a directory's child entries. 例如, Entry::iter_contents遍历所有目录的子条目。

I want to be able to define certain methods such as Entry::iter_contents only for entries where entry_type is a certain variant. 我希望能够为entry_type是特定变体的条目定义某些方法,例如Entry::iter_contents

I tried turning EntryType into a trait and made a DirectoryEntryInfo and FileEntryInfo struct, which both implemented EntryType . 我尝试将EntryType转换为特征并创建了一个DirectoryEntryInfoFileEntryInfo结构,它们都实现了EntryType

Sadly, there were some problems with this approach. 可悲的是,这种方法存在一些问题。 I have a Vec<Entry> elsewhere and with this change it would become Vec<Entry<EntryType>> . 我在其他地方有一个Vec<Entry> ,这个改变它将成为Vec<Entry<EntryType>> Using a trait like this, I have no way to downcast Entry<EntryList> to Entry<DirectoryEntryInfo> . 使用这样的特征,我无法将Entry<EntryList>向下转换为Entry<DirectoryEntryInfo> I also tried doing something with Any , as that is the only way I am aware of to downcast in Rust, but I was only able to cast entry_type , not the entire Entry itself. 我也试过用Any做一些事情,因为这是我在Rust中知道要转向的唯一方法,但我只能转换entry_type ,而不是整个Entry本身。

Ultimately, I'd like to end up with something similar to this: 最终,我想最终得到类似的东西:

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> {
    ...
}

This way, I could access all of the entries fields without knowing whether or not it's a directory or file, as well as be able to cast it to a type that would provide me with all of the Entry fields in addition to methods based on the type parameter like Entry::iter_contents . 这样,我可以访问所有条目字段,而无需知道它是否是目录或文件,并且能够将其转换为一种类型,除了基于该方法的方法之外,还将为我提供所有Entry字段。类型参数,如Entry::iter_contents

Is there a good way to do this without something like RFC 1450 ? 有没有像RFC 1450这样的方法有一个很好的方法吗?

I'm aware that enum variants are not their own types and cannot be used as type parameters. 我知道枚举变体不是他们自己的类型,不能用作类型参数。 I am just looking for an alternate way to conditionally define a method for a struct and still be able to have a way to store any variant of this struct in something like a Vec . 我只是在寻找一种替代方法来有条件地为结构定义一个方法,并且仍然能够以类似Vec的方式存储这个结构的任何变体。 This article is extremely close to what I am trying to do. 这篇文章非常接近我想要做的。 However, using the example from it, there is no way to store a MyEnum<Bool> without knowing whether that Bool is True or False at compile time. 但是,使用它的示例,无法在编译时知道BoolTrue还是False时存储MyEnum<Bool> Being able to downcast something like MyEnum<Box<Bool>> to MyEnum<False> would fix this, but I'm not aware of anything like that in Rust. 能够将像MyEnum<Box<Bool>>这样的东西向下MyEnum<Box<Bool>>MyEnum<False>会解决这个问题,但我不知道在Rust中有类似的东西。

Unfortunately, you can't do quite that, because (as mentioned in the question comments) enum variants are not types and information about the variant isn't available to the type system. 不幸的是,你不能做的相当 ,因为(如问题的意见中提到) 枚举变种没有类型和相关信息的变化是不可用的类型系统。

One possible approach is to "hoist" the enum to the outer layer and have each variant contain a struct that wraps the shared data: 一种可能的方法是将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),
}

Then you can easily define as_file and as_dir along the following lines: 然后,您可以沿着以下行轻松定义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,
        }
    }
}

It's not ideal, because any code you would have written on Entry before now needs to defer to EntryInfo in the appropriate variant. 这并不理想,因为您之前在Entry编写的任何代码都需要在适当的变体中EntryInfo One thing that can make things easier is writing a helper method to find the wrapped EntryInfo : 可以使事情变得更容易的一件事是编写一个帮助器方法来查找包装的EntryInfo

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

Then you can use self.as_info() instead of self.info in the implementation of Entry . 然后你可以在Entry的实现中使用self.as_info()而不是self.info

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM