Over 3x increase in compile time when using `FnMut` instead of `fn` pointer

#1

I am unsure whether this makes sense to report as a bug, but I have a multi-crate project where I am passing a function pointer from one crate to another. I want to allow that function to mutate data that the crate I am passing the function to, does not know about. I also want to be able to change the crate passing out the function pointer without recompiling the crate that is receiving it.

I had a working version using static mut, which was definitely not thread-safe, and for all I know was unsafe in other ways as well. So as part of figuring out the best way to improve that situation, I tried using a move closure instead. This obviously required changing the type from fn() to some form of FnMut. This change increased the compile time of the project from around 4 seconds to around 15 seconds, even though the receiving crate was not reported as being recompiled. I have a vague understandly that FnMut involves an additional layer of indirection if it cannot be inlined, (I’m thinking it’s a struct containing a pointer to the function and all it’s closed over state,) but I still found this surprising.

There are many of the relevant details in this SO question I asked about avoid using static mut but I’m wondering, since my project is open source, if it would be useful to make a git branch and tag some commits or something so others can replicate this.

0 Likes

#2

To understand closures, you can look at my blog pos t about then here

The increase in compile time may be beause of monomorphozation, which is due to generics. If you are not using gener8cs, then I am not sure why you are getting long compile times.

0 Likes

#3

Yes, this behavior makes sense. Generic functions are only compiled when they are monomorphized, i.e. when a use of them appears in the code that has all fully determined type parameters. How can it generate machine code if it doesn’t know the types, after all? :stuck_out_tongue:

When you used fn(), the functions in your provider crate were monomorphic, and thus codegen happened when that crate is compiled. After you changed it to FnMut(), monomorphization now occurs in the consumer crate instead.


To obtain the benefits of closed over state with the benefits of eager code-gen that you get from monomorphic functions, you can use &mut dyn FnMut(). You can even do this without changing your public signatures, by writing a secondary function:

pub fn do_the_thing<F: FnMut()>(mut function: F) {
    _do_the_thing(&mut function);
}

// Monomorphized version for eager compilation
fn _do_the_thing(function: &mut dyn FnMut()) {
    ...
}

Also, this is not a question for internals, but rather https://users.rust-lang.org/

2 Likes