问题
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. I defined various methods for Entry
such as Entry::read_filename
and Entry::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.
I want to be able to define certain methods such as Entry::iter_contents
only for entries where entry_type
is a certain variant.
I tried turning EntryType
into a trait and made a DirectoryEntryInfo
and FileEntryInfo
struct, which both implemented 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>>
. Using a trait like this, I have no way to downcast Entry<EntryList>
to 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.
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
.
Is there a good way to do this without something like 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
. 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. 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.
回答1:
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:
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:
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. One thing that can make things easier is writing a helper method to find the wrapped 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
.
来源:https://stackoverflow.com/questions/48120780/defining-a-method-for-a-struct-only-when-a-field-is-a-certain-enum-variant