Is there a way to hint to the compiler to use some kind of default generic type when using Option::None?

ε祈祈猫儿з 提交于 2019-12-10 16:47:57

问题


I need a function that gets an Option of an generic type T that implements the trait std::iter::IntoIterator. A naive implementation could look like the following (yes, the unwrap would panic on 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);
    }
}

Test on playground.

This works as expected for Some(...), but fails for None with:

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

Obviously the type of T is unknown in those cases. One could use print_iter::<Vec<i32>>(None); but this does not feel really idiomatic, because this gives some arbitrary type that isn't based on anything...

Is there any way to hint to the compiler that I don't care for None or use some kind of default?


回答1:


Is there any way to hint to the compiler that I don't care for None or use some kind of default?

You can implement your own non-generic value to serve as the default. For starters, let's assume print_iter didn't accept Option<T>, but an enum of its own:

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);
        }
    }
}

This doesn't solve the problem yet, because if you pass PrintArg::Ignore to print_iter(), you are back at square one - the compiler is unable to to infer the T. But with your own type, you can easily change print_iter to accept anything that can be converted into 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);
        }
    }
}

With this modification, you can create a dummy non-generic Ignore value and use the From trait to define its conversion to a PrintArg::Ignore<T> with T of your choice - for example:

struct Ignore;

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

As Ignore is non-generic, its use doesn't require (or accept) a <T>. While we did have to invent a type for PrintArg<T> in the From trait implementation, we never construct it, so it's irrelevant which one we choose, as long as it satisfies the IntoIterator bound.

Of course, you'll still want to be able to invoke print_iter() with Some(...), so you'll also define a conversion of Option<T> to 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,
        }
    }
}

With these in place, your API is clean, allowing main() to look like this (playground):

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



回答2:


There's no problem with the value None.

There's just a problem when your variable type can't be inferred which doesn't usually occur. The only case where there's a problem is when you directly pass None, and have no typed variable.

You can specify the type using the turbo fish:

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

But you don't usually need to; your normal cases would rather be like this:

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

or

print_iter(my_options.a);

and both constructs are without problem.

Now (following question edit), if you really want to be able to pass None without precising the type, for example as a literal flag, then you can define a macro:

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);
    }
}


来源:https://stackoverflow.com/questions/58593042/is-there-a-way-to-hint-to-the-compiler-to-use-some-kind-of-default-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!