Why are zero-sized clones of function items useful?

OP of an internals thread wrote:

I can see the value of being able to .clone() a function item and get a zero sized result, so the coercion shouldn't happen in all cases.

What is the practical use of zero-sized clones of function items in Rust? It seems that if it wasn't for such use cases, function items could automatically be coerced to function pointers by the compiler.

The OP also said:

I think that if the function item doesn't implement the trait, it should be coerced to a function pointer.

I believe that the reason they had the bullet point you quoted was to provide an example of why the coercion should only apply if the function item doesn't implement the trait. And thus the desired automatic coercion wouldn't happen for .clone() anyway, since function items do implement that trait. And thus the benefits of Clone is not a blocker for the desired feature.

As for your question though...

In general one of the benefits of function item types is that they don't impose an indirection (like a function pointer does), and relatedly allows monomorphization per-function-item which can optimize better too.

Here's an example making use of the Clone implementation specifically; you can pass a function item to this function.

fn run_a_bunch<F: Fn() + Clone + Send + 'static>(f: F) {
    for _ in 0..9 {
        thread::spawn(f.clone());
    }
    thread::spawn(f);
}

Also, Clone isn't the only trait implementation for function items, and you also can implement traits for them yourself:[1]

trait Trait {
    fn method(&self) {
        println!("{}", std::any::type_name_of_val(self));
    }
}

impl<F: Fn()> Trait for F {}

fn main() {
    main.method();
    (main as fn()).method();
}

And thus the "this automatic coercion feature shouldn't apply when the function item implements the trait" argument can be applied to any number of traits, not just Clone.


  1. The OP wanted multiple blanket implementations of the same trait (+ parameters) for different function item signatures. That, and/or implementing for specific function item types, are the parts which are not possible. â†Šī¸Ž

Thank you for responding!

To see if I understand this correctly:

  1. Both run_a_bunch and thread::spawn in your example would be monomorphized by the compiler for each function item that run_a_bunch is called with.
  2. But the compiler would only generate one version of each if run_a_bunch is called with function pointers instead.
  3. The benefit of monomorphizing run_a_bunch and thread::spawn is that the monomorphized versions avoid an indirection when calling the function that the user passes to run_a_bunch.
  4. Another benefit of passing function items instead of function pointers to run_a_bunch is that f.clone() is zero-sized and thus a no-op, but with function pointers f.clone() is only a no-op if the compiler optimizes the clone away.

Is this right?

Those are all accurate, modulo optimization and pedanticism. (Function pointers can be copied, but closures may need heavier clones.)

Another benefit of monomorphization is that inlining can happen. (Devirtualization through a function pointer can technically happen, but is rare/costs more optimization fuel, AFAIU.)

There are other optimizations, like boxing a zero sized type won't allocate. And behavior may also differ based on the types, like the type name example.

1 Like

Pedanticism in the service of learning is very welcome. :slight_smile: Thanks again!

1 Like