schuelermine
schuelermine

Reputation: 2290

How to type None if it expects a function type as type argument?

Suppose I have a function that takes a callback

fn foo<T>(callback: impl FnOnce(T) -> T, value: T) -> T {
    callback(value)
}

Suppose I now want to make this callback optional. The, in my view, most obvious way of doing that is to use Option:

fn foo<T>(callback: Option<impl FnOnce(T) -> T>, value: T) -> T {
    if let Some(callback) = callback {
        callback(value)
    } else {
        value
    }
}

However, when doing this, I run into a problem at the use site. For instance, the following code does not compile:

fn bar() -> u8 {
    let value: u8 = b'.';
    let value = foo(None, value);
    value
}

I get the following error:

error[E0282]: type annotations needed
  --> XX:XX:XX
   |
XX |     let value = foo(None, value);
   |                     ^^^^ cannot infer type of the type parameter `T` declared on the enum `Option`
   |
help: consider specifying the generic argument
   |
XX |     let value = foo(None::<T>, value);
   |                         +++++

For more information about this error, try `rustc --explain E0282`.

However, it seems impossible to provide a type for this, as impl … syntax does not work as a type argument here, and you’d need a concrete type from a closure anyways.

Is it possible to annotate the type for a missing closure like this?

Upvotes: 1

Views: 87

Answers (3)

cdhowie
cdhowie

Reputation: 168998

As an alternative to the existing answers, supplying an identity function (one that returns its argument) would be the more appropriate functional style here, and has the advantage that it makes the whole problem go away. It is also likely to perform better as there's no branch that can be incorrectly predicted.

fn identity<T>(v: T) -> T { v }

fn foo<T>(callback: impl FnOnce(T) -> T, value: T) -> T {
    callback(value)
}

fn bar() -> u8 {
    let value: u8 = b'.';
    let value = foo(identity, value);
    value
}

In other words, using Option can actually be wasteful when "no function" will cause the same observable behavior as "the identity function."

(Of course, |v| v is shorter than identity, so which you use is a matter of taste.)

This doesn't solve the general problem of how you specify None in the context of an optional function when the identity function shouldn't cause the same behavior as no function at all. The other answers address how to handle that situation. This answer is intended to provide a better solution when both cases do have the same behavior.

Upvotes: 1

cameron1024
cameron1024

Reputation: 10136

There are a few ways. You'll need to pick a type that implements FnOnce(T) -> T. For the examples, I'll use fn(T) -> T, but it doesn't make much difference:

  • use a turbofish:
foo(None::<fn(u8) -> u8>, 123);
  • use a let binding with a type ascription
let option_fn: Option<fn(u8) -> u8> = None;
foo(option_fn, 123);
  • make the generics named, and use the turbofish on foo:
fn foo<T, F: FnOnce(T) -> T>(f: F, t: T) -> T {
  ...
}

foo::<u8, fn(u8) -> u8>(None, 123);

Upvotes: 1

cafce25
cafce25

Reputation: 27283

You have to provide it a type that implements FnOnce(u8) -> u8 since that is what foo requires, the simplest one I can think of is a function pointer:

fn bar() -> u8 {
    let value: u8 = b'.';
    let value = foo(None::<fn(_) -> _>, value);
    value
}

Upvotes: 3

Related Questions