Реализация отложенной загрузки с помощью типа enum в Rust - PullRequest
1 голос
/ 10 января 2020

В моем случае использования у меня есть большой список Layer типов, которые состоят из данных изображения и некоторых метаданных об изображении:

extern crate image;

type Img = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;

#[derive(Debug, Clone)]
struct Layer {
    // some metadata fields
    lazy_image: Img,
}

Пока все хорошо. Тем не менее, я выполняю некоторые отбраковки слоев перед фактической обработкой данных изображения, поэтому я хотел бы выполнить загрузку изображения ленивым образом, чтобы избежать необходимости загружать все изображения, которые мне на самом деле не нужны.

Это была моя первая попытка:

extern crate image;

type Img = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;

#[derive(Debug, Clone)]
enum LazyImage {
    Path(String),
    Image(Img)
}

#[derive(Debug, Clone)]
struct Layer {
    // some metadata fields
    lazy_image: LazyImage,
}

impl Layer {
    fn get_image(&mut self) -> Img {
        match self.lazy_image {
            LazyImage::Image(img) => img,
            LazyImage::Path(path) => {
                let img = image::open(path).expect("Could not open image").to_rgba();
                self.lazy_image = LazyImage::Image(img);
                img
            }
        }
    }
}

Как вы можете догадаться, это не сработало из-за проблем с владением моим заявлением о совпадении в get_image. Компилятор предлагает заимствовать self.lazy_image, по сути дела:

impl Layer {
    fn get_image(&mut self) -> Img {
        match &self.lazy_image {
            LazyImage::Image(img) => img,
            LazyImage::Path(path) => {
                let img = image::open(path).expect("Could not open image").to_rgba();
                self.lazy_image = LazyImage::Image(img);
                img
            }
        }
    }
}

Теперь, однако, у меня есть проблемы с типом: первая ветвь (если изображение уже загружено) возвращает ссылку &Img вместо фактический Img. Хорошо, нет проблем, давайте go полный справочник. Единственный улов в том, что, поскольку я выполняю обработку этих изображений, они должны быть изменяемыми:

impl Layer {
    fn get_image(&mut self) -> &mut Img {
        match &self.lazy_image {
            LazyImage::Image(img) => img,
            LazyImage::Path(path) => {
                let img = image::open(path).expect("Could not open image").to_rgba();
                self.lazy_image = LazyImage::Image(img);
                &mut img
            }
        }
    }
}

Теперь они одного типа, но различаются по изменчивости: в первой ветви (если изображения уже загружены) я получаю неизменную ссылку. Я пытался разворачиваться немного больше, но безуспешно, чтобы заставить это делать то, что я хочу.

Как вы можете сказать, я немного новичок в Rust и немного колеблюсь. Более того, я на самом деле не уверен, что я хочу, чтобы делал для достижения желаемой цели.

Любая помощь, рассказывает ли мне, как удовлетворить компилятор, или даже просто рассказывает мне, я все об этом ошибаюсь, буду очень признателен.

1 Ответ

2 голосов
/ 10 января 2020

Я бы предложил реализовать метод get() непосредственно для типа LazyImage, поскольку он чувствует, что загрузка изображения является внутренним вопросом этого типа.

Поскольку вы, скорее всего, не хотите переместить изображение из структуры данных, вы должны вернуть &mut Img. Эта ссылка, в свою очередь, должна указывать на значение, сохраненное в варианте перечисления Image. Поле варианта enum может быть извлечено только путем удаления структуры, поэтому нам необходимо снова выполнить деструктуру после загрузки изображения.

Одним из возможных решений является использование двух операторов if let для деструктурирования:

impl LazyImage {
    fn get(&mut self) -> &mut Img {
        if let LazyImage::Path(path) = self {
            let img = image::open(path).expect("Could not open image").to_rgba();
            *self = LazyImage::Image(img);
        }
        if let LazyImage::Image(img) = self {
            img
        } else {
            unreachable!()
        }
    }
}

После первого if let перечисление гарантированно будет Image. Вторая деструктуризация также может быть выполнена с использованием выражения match. Компилятор будет настаивать на том, чтобы мы включили случай else, поскольку компилятор не может легко увидеть, что другая ветвь недоступна.

Обратите внимание, что мы используем эргономика соответствия , чтобы избежать некоторых шум в коде.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...