Generic functions taking `Option`

I have the following problem

fn  parse_int(input: Input, range: Option<impl RangeBounds<i32>>) -> Output {
    // ...
}

let _ = parse_int(input, None);

Here the compiler will reject the program, because it cannot monomorphize the impl RangeBounds, because it has no type information to go on. In reality, I don't care what type the compiler chooses for the Some case, because it is never instantiated. What I would really like is for the compiler to choose something like !, the never type. Is there a way to do this, without having to write type annotations with a made up type (most likely ())?

1 Like

Then what's the point of making it an Option (and an argument at all)? Aren't you just trying to specify a type parameter for the function, like fn parse_int<R>(input: Input) -> Output?

1 Like

I think what they're saying is that, in the case where they pass None, their code doesn't care what the type of range is, so it seems awkward that they have to specify it.

For example:

use std::fmt::Display;

fn example<T: Display>(o: Option<T>) {
    match o {
        Some(value) => println!("{}", value),
        None => {}
    }
}

fn main() {
    // Works, because the compiler knows T is &str
    example(Some("test")); 
    
    // Doesn't work, because the compiler doesn't know what T is -
    // but T is never used in this code branch!
    example(None); 
}
1 Like

So the function will be used in multiple places. Sometimes it will be used with a Some, sometimes not.

Some options:

  • Change your API to have separate parse_int and parse_int_with_range methods; the first can just call the second with the necessary type annotations
  • Add a helper method that returns a suitable Option::<T>::None, so users can e.g. call parse_int(input, no_bounds())
  • Have parse_int receive impl RangeBounds<i32> directly instead of an Option, if an unbounded range would have the same behavior/meaning as passing None for your application. Then, you can just call parse_int(i, ..). (Related: there's a style question of whether it would be clear to readers at the call site why the function is receiving a ..)
5 Likes

One thing, which i might do is just stick a type declaration on it in a const and then at least you don't have to repeat the type decl,
the following added to your example works.

#[allow(non_upper_case_globals)]
const Nevah: Option<!> = None;
fn main() {
    // Works.
    example(Nevah)
}
6 Likes

If it's a method on a type, then you can move it into a generic trait and call it without even needing to worry about Option:

trait IntParser<T> {
    fn parse_int(input: Input, range: T);
}
impl<T: RangeBounds<i32>> IntParser<T> for MyStruct {
    fn parse_int(input: Input, range: T) {
        /**/
    }
}
impl IntParser<()> for MyStruct {
    fn parse_int(input: Input, range: ()) {
        /**/
    }
}

Those are conflicting impls (because () is an external type, so you can't assume it will never implement RangeBounds):

error[E0119]: conflicting implementations of trait IntParser<()> for type MyStruct:

  --> src/lib.rs:13:1
   |
8  | impl<T: RangeBounds<i32>> IntParser<T> for MyStruct {
   | --------------------------------------------------- first implementation here
...
13 | impl IntParser<()> for MyStruct {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyStruct`
   |

= note: upstream crates may add a new impl of trait std::ops::RangeBounds<i32> for type () in future versions

Oh dear, what a bummer.
After some fiddling around, I got it to work with both Options and ():

trait IntParser<T> {
    fn parse_int(input: Input, range: T);
}
impl<T: RangeBounds<i32>> IntParser<Option<T>> for MyStruct {
    fn parse_int(input: Input, range: Option<T>) {
        /**/
    }
}
impl IntParser<()> for MyStruct {
    fn parse_int(input: Input, range: ()) {
        Self::parse_int(input, None::<Range<i32>>)
    }
}

Playground.

It is possible to qualify None in this case with a variety of methods. Considering the trait bound Display in the provided example, any of these will work:

example(Option::<&str>::None); 
example(Option::<i32>::None); 
example(Option::<Box<dyn Display>>::None); 

Consequently the Box<dyn Trait> approach also works for the case given in the OP, assuming Box<dyn RangeBounds<T>> implements RangeBounds<T>: playground

It's ugly. And it's kludgy. But it works.

1 Like

Thanks for all the help! In the end I removed the Option part and had the range .. mean None. This solution only works in this specific case, though.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.