仅当字段是某个枚举变体时才为结构定义方法?
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_filename
和 Entry::write_to_disk
。但是,我有一些对常规文件和目录都可用的方法没有意义。例如,Entry::iter_contents
遍历目录的所有子条目。
我希望能够为 entry_type
是特定变体的条目定义某些方法,例如 Entry::iter_contents
。
我尝试将 EntryType
变成一个特征并制作了一个 DirectoryEntryInfo
和 FileEntryInfo
结构,它们都实现了 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_file
和 as_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
。
我有以下结构:
#[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_filename
和 Entry::write_to_disk
。但是,我有一些对常规文件和目录都可用的方法没有意义。例如,Entry::iter_contents
遍历目录的所有子条目。
我希望能够为 entry_type
是特定变体的条目定义某些方法,例如 Entry::iter_contents
。
我尝试将 EntryType
变成一个特征并制作了一个 DirectoryEntryInfo
和 FileEntryInfo
结构,它们都实现了 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_file
和 as_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
。