Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 427
MasterOfDeath Avatar asked Oct 28 '19 14:10

MasterOfDeath


2 Answers

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);
}
like image 196
user4815162342 Avatar answered Nov 15 '22 04:11

user4815162342


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);
    }
}
like image 38
Denys Séguret Avatar answered Nov 15 '22 04:11

Denys Séguret