5

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?

SuperStormer
  • 4,997
  • 5
  • 25
  • 35
  • This should be part of a library and it is used as some kind of flag. If the user does not want to provide an input, it is handy to use the literal `None` instead of defining a typed variable first. – MasterOfDeath Oct 28 '19 at 14:55
  • 1
    Since `Option` is an `IntoIterator` you can send an empty iterator for your case like this : `print_iter(Some(None))` , [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018) – Ömer Erden Oct 29 '19 at 10:15

2 Answers2

5

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);
}
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • This is probably a better answer than mine. – Denys Séguret Oct 28 '19 at 15:23
  • 1
    OP question makes sense only because the `print_iter` function, in fact, takes several parameters (which wasn't obvious before edit and comment). And options rarely come alone (and you don't want to pass several flags to a function). So it's possible the real convenient solution is to build a more complex struct holding several options. But this answer is probably a sensible first step to the undescribed real question. – Denys Séguret Oct 28 '19 at 15:29
  • 1
    @user4815162342, DenysSéguret Thanks to both of you for the fast response! Sorry for not having a clear question in the beginning. I'm new to rust so nomenclature and phrasing my problems is still a struggle for me. – MasterOfDeath Oct 28 '19 at 16:09
3

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);
    }
}
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758