5 个版本

新增 0.1.7 2024 年 8 月 22 日
0.1.6 2024 年 1 月 22 日
0.1.5 2022 年 9 月 2 日
0.1.4 2022 年 8 月 3 日

#142 in Rust 模式

Download history 53/week @ 2024-05-03 319/week @ 2024-05-10 853/week @ 2024-05-17 240/week @ 2024-05-24 634/week @ 2024-05-31 1401/week @ 2024-06-07 2158/week @ 2024-06-14 648/week @ 2024-06-21 1945/week @ 2024-06-28 2164/week @ 2024-07-05 2828/week @ 2024-07-12 2040/week @ 2024-07-19 1534/week @ 2024-07-26 1443/week @ 2024-08-02 1145/week @ 2024-08-09 248/week @ 2024-08-16

4,633 下载/月
10 个crate中使用 (7 个直接使用)

MIT/Apache

27KB
246 代码行

TinyFn crate

crates docs MIT/Apache

动机

你是否曾经把闭包放入 Box<dyn Fn(...)> 并想知道:“有没有一个 crate 可以避免为小闭包进行堆分配?”

不再需要猜测,这就是这个 crate。

如何使用

这个 crate 提供了声明式宏 tiny_fn! 生成可以存储擦除其类型的闭包包装器。

生成的闭包包装器在包装的闭包适合内联存储时避免堆分配。

该宏设计得易于编写,语法简单,主要重用了 Rust 中已存在的结构。
生成的包装器的行为应该一目了然。

示例

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);

宏展开为一个包含两个公共方法的 struct Foo 定义。

  • Foo::new 接受任何实现了 Fn(i32, i32) -> i32 的值,并返回一个新的 Foo 实例。
  • Foo::call 遵循宏指定的签名。例如,Foo::call 接受 a: i32b: i32,并返回 i32
    简单来说,Foo::call 使用 ab 参数(在同一位置)调用闭包,这些参数用于创建 Foo 的这个实例。

tiny_fn! 宏支持一次性定义多个项目。

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! 宏支持可见性限定符。

tiny_fn! {
    pub struct Foo = Fn(a: i32, b: i32) -> i32;
    struct Bar = Fn() -> String;
    pub(crate) struct Baz = Fn();
}

属性

tiny_fn! 宏支持项目属性,包括文档。

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* 特性族

tiny_fn! 宏可以为 Fn* 特性族中的任何特性生成闭包包装器。

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.
  • A 只能包装在不可变借用时可以调用的闭包。因此,A::call 接受 &self
  • B 只能包装在借用时可以调用的闭包。因此,B::call 接受 &mut self
  • C 可以包装任何闭包,甚至是一次性可调用的闭包。因此,C::call 接受 self

泛型

闭包包装器可以声明为泛型,这些类型应该在函数签名中使用。

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);

在这里,BinOp 是对 T 泛型的。
BiOp::<T>::new 通过 Fn(T, T) -> T 接受闭包界限。

值得注意的是,在 BinOp 中,T 并不受特质的约束。
闭包包装器只移动参数和返回值,因此它们不需要了解有关类型的其他信息。

标记

闭包包装器可以声明为具有标记特性。只需在函数签名后添加 | 和由加号前缀的标记特性列表即可。在 Fn 特性族中,| 符号不被使用,但由于声明性宏的限制,这里需要它。

它们将被添加到包含类型的界限上。如果它们是自动特性,它们也将为包装器类型实现。

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();
});

特殊泛型参数

tiny_fn! 宏生成的闭包包装器除了宏调用者指定的泛型类型外,总是有两个泛型参数

  • 生命周期 'closure
    包装器包含由 'closure
    生命周期绑定的闭包。
  • 常量 INLINE_SIZE: usize
    大小不超过 INLINE_SIZE 且对齐要求不超过 tiny_fn::ALIGN 的闭包将直接内联到包装器结构中。
    否则将发生堆分配。
    INLINE_SIZE 参数默认为 tiny_fn::DEFAULT_INLINE_SIZE

许可证

许可协议为以下之一

任选其一。

贡献

除非你明确声明,否则根据 Apache-2.0 许可证定义,你有意提交的任何贡献,包括在作品中包含的内容,都将按照上述协议双重许可,无需任何额外条款或条件。

无运行时依赖

特性