Определение метода для структуры только тогда, когда поле является определенным вариантом перечисления?


У меня есть следующая структура:

#[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_filename и Entry::write_to_disk. Однако у меня есть некоторые методы, которые не имеют смысла быть доступными как для обычных файлов, так и для каталогов. Например, Entry::iter_contents перебирает все дочерние записи каталога.

Я хочу иметь возможность определять определенные методы, такие как Entry::iter_contents только для записей, где entry_type является определенным вариант.

Я попытался превратить EntryType В признак и создал структуру DirectoryEntryInfo и FileEntryInfo, которые реализовали EntryType. К сожалению, с этим подходом возникли некоторые проблемы. У меня есть Vec<Entry> в другом месте, и с этим изменением он станет Vec<Entry<EntryType>>. Используя такую черту характера, я не могу понизить Entry<EntryList> до Entry<DirectoryEntryInfo>. Я также попытался сделать что-то с Any, поскольку это единственный способ, который я знаю, чтобы опуститься в ржавчине, но я смог только бросить 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. Эта статья чрезвычайно близка к тому, что я пытаюсь сделать. Однако, используя пример из него, невозможно сохранить MyEnum<Bool>, не зная, является ли это Bool True или False во время компиляции. Будучи в состоянии подавить что-то вроде MyEnum<Box<Bool>> to MyEnum<False> исправил бы это, но я не знаю ничего подобного в Rust.

1 4

1 ответ:

К сожалению, вы не можете сделатьвполне , потому что (как упоминалось в комментариях к вопросу) варианты перечисления не являются типами и информация о варианте недоступна системе типов.

Один из возможных подходов состоит в том, чтобы" поднять " 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,
    }
}

Тогда вы можете использовать self.as_info() вместо self.info в реализации Entry.