Есть ли способ намекнуть компилятору использовать какой-то универсальный тип по умолчанию при использовании Option :: None? - PullRequest
3 голосов
/ 28 октября 2019

Мне нужна функция, которая получает Option универсального типа T, который реализует черту std::iter::IntoIterator. Наивная реализация может выглядеть следующим образом (да, развертывание может вызвать панику на None):

fn main() {
    let v = vec![1i32, 2, 3];
    print_iter(Some(v));
    print_iter(None);
}

fn print_iter<T: IntoIterator<Item = i32>>(v: Option<T>) {
    for e in v.unwrap() {
        println!("{}", e);
    }
}

Тест на детская площадка .

Это работает, как и ожидалосьдля Some(...), но не для None с:

error[E0282]: type annotations needed
 --> src/main.rs:4:5
  |
4 |     print_iter(None);
  |     ^^^^^^^^^^ cannot infer type for `T`

Очевидно, что тип T в этих случаях неизвестен. Можно использовать print_iter::<Vec<i32>>(None);, но это не кажется по-настоящему идиоматичным, потому что это дает какой-то произвольный тип, не основанный ни на чем ...

Есть ли какой-нибудь способ намекнуть компилятору, что я не делаюзаботиться о None или использовать какой-то тип по умолчанию?

Ответы [ 2 ]

3 голосов
/ 28 октября 2019

Есть ли какой-нибудь способ намекнуть компилятору, что меня не волнует None или использовать какое-то значение по умолчанию?

Вы можете реализовать свое собственное неуниверсальное значениеслужить по умолчанию. Для начала давайте предположим, что print_iter не принял Option<T>, но сам по себе перечисление:

enum PrintArg<T> {
    Ignore,
    Use(T),
}

fn print_iter<T: IntoIterator<Item = i32>>(v: PrintArg<T>) {
    if let PrintArg::Use(v) = v {
        for e in v {
            println!("{}", e);
        }
    }
}

Это еще не решает проблему, потому что если вы передадите PrintArg::Ignore в print_iter(), вы вернулись на круги своя - компилятор не может вывести T. Но с вашим собственным типом вы можете легко изменить print_iter, чтобы принимать все, что может быть преобразовано в PrintArg:

fn print_iter<T, V>(v: T)
where
    T: Into<PrintArg<V>>,
    V: IntoIterator<Item = i32>,
{
    if let PrintArg::Use(v) = v.into() {
        for e in v {
            println!("{}", e);
        }
    }
}

С помощью этой модификации,вы можете создать фиктивное неуниверсальное значение Ignore и использовать черту From, чтобы определить ее преобразование в PrintArg::Ignore<T> с T по вашему выбору - например:

struct Ignore;

impl From<Ignore> for PrintArg<Vec<i32>> {
    fn from(_v: Ignore) -> Self {
        PrintArg::Ignore
    }
}

Поскольку Ignore не является универсальным, его использование не требует (или не принимает) <T>. Хотя нам и пришлось изобрести тип для PrintArg<T> в реализации черты From, мы никогда не конструируем его, поэтому не имеет значения, какой из них мы выберем, пока он удовлетворяет границе IntoIterator.

Конечно, вы все равно захотите вызывать print_iter() с Some(...), поэтому вы также определите преобразование Option<T> в PrintArg<T>:

impl<T> From<Option<T>> for PrintArg<T> {
    fn from(v: Option<T>) -> Self {
        match v {
            Some(v) => PrintArg::Use(v),
            None => PrintArg::Ignore,
        }
    }
}

. место, ваш API чист, что позволяет main() выглядеть так ( детская площадка ):

fn main() {
    let v = vec![1i32, 2, 3];
    print_iter(Some(v));
    print_iter(Ignore);
}
3 голосов
/ 28 октября 2019

Нет проблем со значением None.

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

Вы можете указать тип с помощью турбо-рыбы:

print_iter::<Vec<i32>>(None);

Но вы не можетеобычно надо;Ваши обычные случаи будут выглядеть так:

let a: Option<Vec<i32>> = None;
print_iter(a);

или

print_iter(my_options.a);

, и обе конструкции без проблем.

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

macro_rules! pi {
    (None) => {
         // here we handle the call as a nop but
         //  other behaviors are possible, like
         //  calling the function with a type
         //  specified with a turbofish
    };
    ($a:expr) => {
        print_iter($a)
    };
}

fn main() {
    let v = vec![1i32, 2, 3];
    pi!(Some(v));
    pi!(None);
}

fn print_iter<T: IntoIterator<Item = i32>>(v: Option<T>) {
    for e in v.unwrap() {
        println!("{}", e);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...