Well, yes, the lack of ergonomics was only my fault: I love type-level constants too much that I tend to (ab)use use them everywhere, but they are indeed a tad cumbersome 
For your example plain Fn generics can be used, which would actually increase the flexibility of your mocking functions since they would be able to carry state:
fn do_one_thing () {
println!("do_one_thing()");
}
fn do_other_thing () {
println!("do_other_thing()");
}
mod lib {
use super::*;
fn do_thing_mockable (
do_one_thing: impl Fn(),
do_other_thing: impl Fn(),
)
{
do_one_thing();
do_other_thing();
}
#[inline]
pub
fn do_thing () {
do_thing_mockable(do_one_thing, do_other_thing);
}
#[cfg(test)]
#[test]
fn test_do_thing_with_dummy ()
{
fn dummy_do_thing () {
println!("Dummy was indeed called");
}
do_thing_mockable(dummy_do_thing, dummy_do_thing);
panic!("Show stdout");
}
}
fn main () {
lib::do_thing();
}
The only issue with this is that the functions are given in an unnamed manner which could be error prone when refactoring. Named args is another topic in and on itself, but it would indeed be very nice to have:
#[cfg(test)]
#[test]
fn test_do_thing_with_dummy ()
{
fn dummy_do_thing () {
println!("Dummy was indeed called");
}
do_thing_mockable(MockOverrides {
do_one_thing: Some(dummy_do_thing),
..Default::default()
});
panic!("Show stdout");
}
which, for cases as simple as these, could be derived with a proc-macro, with a syntax along these lines:
#[derive(Mock)]
fn do_thing (
#[mockable]
do_one_thing: impl Fn(),
#[mockable]
do_other_thing: impl Fn(),
)