Использование Impl Trait в рекурсивной функции - PullRequest
0 голосов
/ 04 января 2019

Я экспериментировал с impl Trait и столкнулся с этой ошибкой при создании рекурсивной функции:

error[E0308]: if and else have incompatible types
  --> src/main.rs:16:5
   |
16 | /     if logic {
17 | |         one(false)
18 | |     } else {
19 | |         two()
20 | |     }
   | |_____^ expected opaque type, found a different opaque type
   |
   = note: expected type `impl Meow` (opaque type)
              found type `impl Meow` (opaque type)

Вот код для воспроизведения ( Ссылка Rust Playground ):

trait Meow {
    fn meow();
}

struct Cat(u64);

impl Meow for Cat {
    fn meow() {}
}

fn one(gate: bool) -> impl Meow {
    if gate {
        one(false)
    } else {
        two()
    }
}

fn two() -> impl Meow {
    Cat(42)
}

fn main() {
    let _ = one(true);
}

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

Можно ли как-то поддержать синтаксис impl Trait во время такого рода отказов, пожалуйста?

Ответы [ 2 ]

0 голосов
/ 04 января 2019

Отказ от ответственности: этот ответ предполагает, что читатель понимает, что -> impl Trait требует возврата одного типа;см. этот вопрос о возврате различных типов .


Непрозрачность

Один из основных принципов Rust заключается в том, что проверка типов полностью определяется интерфейс функций, типов и т. Д ... и реализация игнорируется.

Что касается функциональности -> impl Trait, это проявляется в том, что язык обрабатывает каждый -> impl Trait как непрозрачный тип, определяется только той функцией, из которой она взята.

В результате вы можете вызывать одну и ту же функцию дважды:

use std::fmt::Debug;

fn cat(name: &str) -> impl Debug { format!("Meow {}", name) }

fn meow(g: bool) -> impl Debug {
    if g {
        cat("Mario")
    } else {
        cat("Luigi")
    }
}

fn main() {
    println!("{:?}", meow(true));
}

Но вы не можете вызывать разные функции, даже если они возвращают одну и ту же функцию.тип, если хотя бы один спрятан за -> impl Trait:

use std::fmt::Debug;

fn mario() -> impl Debug { "Meow Mario" }

fn luigi() -> &'static str { "Meow Luigi" }

fn meow(g: bool) -> impl Debug {
    if g {
        mario()
    } else {
        luigi()
    }
}

fn main() {
    println!("{:?}", meow(true));
}

Выход:

error[E0308]: if and else have incompatible types
  --> src/main.rs:8:9
   |
8  | /         if g {
9  | |             mario()
10 | |         } else {
11 | |             luigi()
12 | |         }
   | |_________^ expected opaque type, found &str
   |
   = note: expected type `impl std::fmt::Debug`
              found type `&str`

И с двумя спрятанными за -> impl Trait:

use std::fmt::Debug;

fn mario() -> impl Debug { "Meow Mario" }

fn luigi() -> impl Debug { "Meow Luigi" }

fn meow(g: bool) -> impl Debug {
    if g {
        mario()
    } else {
        luigi()
    }
}

fn main() {
    println!("{:?}", meow(true));
}

Возвращает то же сообщение об ошибке, что и вы:

error[E0308]: if and else have incompatible types
  --> src/main.rs:8:5
   |
8  | /     if g {
9  | |         mario()
10 | |     } else {
11 | |         luigi()
12 | |     }
   | |_____^ expected opaque type, found a different opaque type
   |
   = note: expected type `impl std::fmt::Debug` (opaque type)
              found type `impl std::fmt::Debug` (opaque type)

Взаимодействие с рекурсией

Нет.

Язык здесь не имеет особой рекурсии и поэтому не понимает, что в случае pОбиженный в вопросе, есть только один вовлеченный тип.Вместо этого он замечает fn one(...) -> impl Meow и fn two(...) -> impl Meow и приходит к выводу, что это разные непрозрачные типы, и поэтому объединение во время компиляции невозможно.

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


Обход

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

Я отошлю вас к @ Anders's answer за его умный обходной путь.

0 голосов
/ 04 января 2019

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

fn one_template<T: Meow>(gate: bool, two: impl FnOnce() -> T) -> T {
    if gate {
        one_template(false, two)
    } else {
        two()
    }
}

fn one(gate: bool) -> impl Meow {
    one_template(gate, two)
}

Ссылка Rust Playground

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