Загадка типа ржавчины: преобразование функции в метод и вызов функции подписи прерывают компиляцию - PullRequest
0 голосов
/ 20 октября 2019

Тайна о типах ржавчины, которую я не совсем понимаю.

Недавно я занимался рефакторингом кода для проекта и столкнулся с загадкой печатания, которая озадачила меня, и я надеюсь, что кто-то здесь сможет прояснить, что происходит

Я пишу программу для реализации алгоритма вырезания шва . «Вырезание шва» - это способ автоматического уменьшения размеров изображения путем нахождения для каждого пикселя в первой строке извилистого пути пикселей сверху вниз или слева направо, который будет наносить наименьший ущерб изображениюи затем удаляя этот «шов». Путь вычисляется из энергии каждого пикселя: вычисляется расстояние между цветами двух соседей этого пикселя, а затем определяется шов с наименьшей общей дельтой .

Итак, Изображение → Энергетическая карта → Шов → Уменьшенное изображение.

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

Хорошая новость заключается в том, что каждый источник читаетсятолько. Генерация энергетической карты требует доступа на запись только к энергетической карте. Для столбцов, если бы я мог вращать энергетическую карту, я мог бы выполнять многопоточное горизонтальное сканирование. Это оказывается легко, и все, что мне нужно, это прокси-объект для сопоставления (x, y) с (y, x) при чтении изображения. Давайте назовем этот прокси-объект Pivot.

Позвольте мне показать некоторый код (AviShaOne - это первый алгоритм вырезания шва от Avidan & Shamir, «Seam Carving», 2007):

fn calculate_energy<I, P, S>(image: &I) -> TwoDimensionalMap<u32>
where
    I: GenericImageView<Pixel = P>,
    P: Pixel<Subpixel = S> + 'static,
    S: Primitive + 'static,
{
   ...
}

impl<'a, I, P, S> AviShaOne<'a, I, P, S>
where
    I: GenericImageView<Pixel = P>,
    P: Pixel<Subpixel = S> + 'static,
    S: Primitive + 'static,
{
    fn find_vertical_seam(&self) -> Vec<u32> {
        energy_to_seam(&calculate_energy(self.image))
    }
    fn find_horizontal_seam(&self) -> Vec<u32> {
        energy_to_seam(&calculate_energy(&Pivot::new(self.image)))
    }
}

Позвольте мне подчеркнуть: приведенный выше код работает . Pivot перераспределяет координаты, и теперь внутренние функции могут работать с объектами памяти с поддержкой потоков.

Если я хочу уменьшить изображение более чем на один шов, мне придется повторно выполнять этот расчет. Было бы неплохо, если бы я мог кэшировать карту энергии. (Поскольку шов меандрирует, и удаление этого шва может повредить швы, которые пересекают его, удаляемый столбец будет довольно большим, но все равно будет менее опасным, чем пересчет для каждого шва .) Я хочу добавитькарту энергии к struct, и приведите calculate_energy в impl.

Вот где все становится странным:

impl<'a, I, P, S>  AviShaOne<'a, I, P, S>
where
    I: GenericImageView<Pixel = P>,
    P: Pixel<Subpixel = S> + 'static,
    S: Primitive + 'static,
{
    fn calculate_energy(&self, image: &I) -> TwoDimensionalMap<u32>
    {
        ...
    }

    fn find_horizontal_seam(&self) -> Vec<u32> {
        energy_to_seam(&self.calculate_energy(&Pivot::new(self.image)))
    }
}

Все, что я делал, это двигался calculate_energy в реализацию. Никакой другой код не был изменен. Pivot имеет такую ​​же сигнатуру типа, что и AviShaOne, так как они оба обрабатывают эти GenericImageView объекты. И теперь он не скомпилируется:

176 |         energy_to_seam(&self.calculate_energy(&Pivot::new(self.image)))
    |                                               ^^^^^^ expected type parameter, found struct `pivot::Pivot`

Это загадка. Функции буквально одинаковы. Типовые подписи, насколько я могу судить, конгруэнтны, буквально копируются и вставляются для согласованности. Я еще не добавил energy как поле! Она работала нормально, когда функция была просто функцией, но теперь, когда она является членом реализации, компилятор хочет что-то еще, но я понятия не имею, что.

Чего хочет компилятор?

1 Ответ

2 голосов
/ 20 октября 2019

Существует очень важное изменение, calculate_energy больше не является универсальной функцией для всех типов I, таких как I: Gener... и т. Д., И т. Д. Вместо этого это обычная функция, ожидающая очень специфического типа I.

Какой тип I? Ну, какой бы I AviShaOne не использовал, я предполагаю, что это тот же тип, что и self.image. Так что вызывать calculate_energy(self.image) нормально.

Однако Pivot::new(self.image) имеет совершенно другой тип, это Pivot, а не I. И поскольку calculate_energy больше не является универсальной функцией, она не может справиться с этим.

...