Have you ever being placing closure into Box<dyn Fn(...)> and wondered:
"Is there a crate to avoid heap allocations for small closures?"
Wonder no more, this is the crate.
This crate provides declarative macro tiny_fn! to generates closure wrappers
able store closure erasing its type.
Generated closure wrappers avoid heap allocations when wrapped closure fits inline storage.
The macro is designed to be easy to write with simple syntax that mostly reuse constructs already existing in Rust.
Behavior of generated wrappers should be obvious from the first glance.
tiny_fn! {
struct Foo = Fn(a: i32, b: i32) -> i32;
}
let foo: Foo = Foo::new(|a, b| a + b);
assert_eq!(foo.call(1, 2), 3);Macro expands to struct Foo definition with two public methods.
Foo::newaccepts any value that implementsFn(i32, i32) -> i32and returns new instance ofFoo.Foo::callfollows signature specified to the macro. e.g.Foo::callacceptsa: i32andb: i32and returnsi32.
PlainlyFoo::callcalls closure from which this instance ofFoowas crated usingaandbarguments at the same positions.
tiny_fn! macro supports defining multiple items at once.
tiny_fn! {
struct Foo = Fn(a: i32, b: i32) -> i32;
struct Bar = Fn() -> String;
}
let foo: Foo = Foo::new(|a, b| a + b);
let bar: Bar = Bar::new(|| "Hello, World!".to_string());
assert_eq!(foo.call(1, 2), 3);
assert_eq!(bar.call(), "Hello, World!");tiny_fn! macro supports visibility qualifiers.
tiny_fn! {
pub struct Foo = Fn(a: i32, b: i32) -> i32;
struct Bar = Fn() -> String;
pub(crate) struct Baz = Fn();
}tiny_fn! macro supports item attributes, including documentation.
tiny_fn! {
/// This is `Foo` wrapper for that takes two `i32`s and return `i32`.
pub struct Foo = Fn(a: i32, b: i32) -> i32;
}Fn* traits family
tiny_fn! macro can generate closure wrappers for any of the Fn* traits family.
tiny_fn! {
struct A = Fn();
struct B = FnMut();
struct C = FnOnce();
}
let a = 42;
let a: A = A::new(|| println!("{}", a));
a.call();
a.call();
let mut b = 42;
let mut b: B = B::new(|| b += 1);
b.call();
b.call();
let c = String::from("Hello, World!");
let c: C = C::new(move || println!("{}", c));
c.call();
// c.call(); // This will not compile, because `C` can be called only once.Acan wrap only closures that are callable when immutably borrowed. And soA::calltakes&self.Bcan wrap only closures that are callable when borrowed. And soB::calltakes&mut self.Ccan wrap any closures, even ones that are callable once. And soC::calltakesself.
Closure wrappers can be declared generic over number of types and those types should be used in function signature.
tiny_fn! {
struct BinOp<T> = Fn(a: T, b: T) -> T;
}
let add: BinOp<i32> = BinOp::new(|a, b| a + b);
let mul: BinOp<i32> = BinOp::new(|a, b| a * b);
assert_eq!(mul.call(add.call(1, 2), 3), 9);Here BinOp is generic over T.
BiOp::<T>::new accepts closures bounds by Fn(T, T) -> T.
Notably T is not constrained by traits in BinOp.
Closure wrappers only move arguments and return values, so they don't need to know anything else about the type.
Closure wrappers can be declared with marker traits.
Simply add | and list of + prefixed marker traits after function signature.
In Fn traits family | symbol is not used, but here it is required due to declarative macro limitations.
They will be added to bounds on contained types. And if autotraits, they will be implemented for the wrapper type as well.
tiny_fn! {
struct Foo = Fn(a: i32, b: i32) -> i32 | + Send;
}
let foo: Foo = Foo::new(|a, b| a + b);
std::thread::spawn(move || {
foo.call(1, 2);
});Closure wrapper generated by tiny_fn! macro always have two generic parameters besides generic types specified by macro caller:
- Lifetime
'closure.
Wrapper contains closures bound by'closurelifetime. - Constant
INLINE_SIZE: usize.
Closures with size up toINLINE_SIZEand alignment requirement not exceedingtiny_fn::ALIGNwill be inlined into wrapper structure directly.
Otherwise heap allocation will occur.
INLINE_SIZEparameter is defaulted totiny_fn::DEFAULT_INLINE_SIZE.
Licensed under either of
- Apache License, Version 2.0, (license/APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (license/MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.