★ Revisiting Rust's Function Pointers

 — 6 minutes read


I once needed a function that takes an optional function pointer. This is quite easy to express in Rust:

fn maybe_closure<T: FnMut(String) -> ()>(closure: Option<T>) -> () {
  ...
}

maybe_closure(Some(...));

But I ran into a problem calling maybe_closure with None

maybe_closure(None);

because the compiler could not infer the concrete type of None in this context

11  |         maybe_closure(None);
    |                  ^^^^^^^^^^^^^^^^^^ cannot infer type for `T`
    |
    = note: type annotations or generic parameter binding required

even when I specified the type using the turbo fish syntax

    |
11  |         maybe_closure(None::<FnMut(String) -> ()>);
    |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `std::ops::FnMut(std::string::String)`
    |
    = note: `std::ops::FnMut(std::string::String)` does not have a constant size known at compile-time
    = note: required by `std::prelude::v1::None`

So I asked the friendly Rust users and xfix provided the correct answer

maybe_closure(None::<fn(_)>);

As history repeats itself, I was confronted with a similar question recently and to my surprise googled my exact same question from a year ago. Now it’s time to understand the details.

Option is a Generic Type

First, why does the compiler need to infer type for T for None?

Option is an enum that holds a generic data type in one of its variants, i.e., Some(T). Here’s the definition of Option:

pub enum Option<T> {
    None,
    Some(T),
}

So Option<T> is a type constructor for concrete types with type parameter T. This means that Option<A> and Option<B> with A != B are two different types. For example Some(10) and Some("10") are of different types as well as None::<i32> and None::<&str>.

Since None does not explicitly state of which concrete type it is, the compiler cannot infer the concrete type in a situation as above when we just pass None to the maybe_closure function.

Fn, FnMut, and FnOnce are Traits

So why didn’t maybe_closure(None::<FnMut(String) -> ()>); solve the problem?

Since Fn, FnMut, and FnOnce are traits they do not have a constant size. Only concrete types have a constant size and thus, only concrete types that implement a trait may be passed to a function. We could use Box<T> which is Rust’s smart pointer for data on the heap to get a constant size parameter for our function, but that would make using the our maybe_closure even more cumbersome.

Primitive Type fn

The solution xfix pointed out uses the primitive type fn which is a function pointer. Since this is a primitive type, it has a concrete size. But why does fn(_) type check for maybe_closure? The explanation can be found in the Rust documentation for fn:

In addition, function pointers of any signature, ABI, or safety are Copy, and all safe function pointers implement Fn, FnMut, and FnOnce. This works because these traits are specially known to the compiler.

So fn implements FnMut and is of constant size.

Summary

Passing functions and closures as parameters to functions requires constant sizes. If the parameter should be an optional None, the compiler needs explicit type information to type check the function call.

This simple Rust program illustrates this in detail. It’s also available as gist on Github and on Playground.

// Takes a function pointer from i32 to i32
fn takes_a_fp(f: fn(i32) -> i32, value: i32) -> i32 {
    f(value)
}

// Takes a closure with the trait bound Fn(i32) -> i32
fn takes_a_closure<T: Fn(i32) -> i32>(f: T, value: i32) -> i32 {
    f(value)
}

// Takes a closure with the trait bound Fn(i32) -> i32 using 1.26 impl syntax
fn takes_a_closure_impl(f: impl Fn(i32) -> i32, value: i32) -> i32 {
    f(value)
}

// Takes an optional function pointer from i32 to i32
fn takes_an_opt_fp(f: Option<fn(i32) -> i32>, value: i32) -> Option<i32> {
    if let Some(f) = f {
        Some(f(value))
    } else {
        None
    }
}

// Takes an optional closure with the trait bound Fn(i32) -> i32
fn takes_an_opt_closure<T: Fn(i32) -> i32>(f: Option<T>, value: i32) -> Option<i32> {
    if let Some(f) = f {
        Some(f(value))
    } else {
        None
    }
}

// Takes an optional closure with the trait bound Fn(i32) -> i32 using 1.26 impl syntax
fn takes_an_opt_closure_impl(f: Option<impl Fn(i32) -> i32>, value: i32) -> Option<i32> {
    if let Some(f) = f {
        Some(f(value))
    } else {
        None
    }
}

// Function from i32 -> i32
fn id(x: i32) -> i32 {
    x
}

fn main() {
    // Closure from i32 -> i32
    let closure = |x: i32| x;

    println!("takes_a_fp(closure, 42): {}", takes_a_fp(closure, 42));
    println!("takes_a_fp(fn, 42): {}", takes_a_fp(id, 42));
    println!();

    println!("takes_a_closure(closure, 42): {}", takes_a_closure(closure, 42));
    println!("takes_a_closure(fn, 42): {}", takes_a_closure(id, 42));
    println!();

    println!("takes_a_closure_impl(closure, 42): {}", takes_a_closure_impl(closure, 42));
    println!("takes_a_closure_impl(fn, 42): {}", takes_a_closure_impl(id, 42));
    println!();

    println!("takes_an_opt_fp(closure, 42): {:?}", takes_an_opt_fp(Some(closure), 42));
    println!("takes_an_opt_fp(fn, 42): {:?}", takes_an_opt_fp(Some(id), 42));
    // The type of `f` is unambiguous, therefore Rust can infer the correct type of Option<f> for None
    println!("takes_an_opt_fp(None, 42): {:?}", takes_an_opt_fp(None, 42));
    println!();

    println!("takes_an_opt_closure(closure, 42): {:?}", takes_an_opt_closure(Some(closure), 42));
    println!("takes_an_opt_closure(fn, 42): {:?}", takes_an_opt_closure(Some(id), 42));
    println!("takes_an_opt_closure(None, 42): {:?}", takes_an_opt_closure(None::<fn(_) -> i32>, 42));
    println!();

    println!("takes_an_opt_closure_impl(closure, 42): {:?}", takes_an_opt_closure_impl(Some(closure), 42));
    println!("takes_an_opt_closure_impl(fn, 42): {:?}", takes_an_opt_closure_impl(Some(id), 42));
    // The type of `f` is unambiguous, therefore Rust can infer the correct type of Option<f> for None
    println!("takes_an_opt_closure_impl(None, 42): {:?}", takes_an_opt_closure_impl(None::<fn(_) -> i32>, 42));
}