2 个版本

使用旧的 Rust 2015

0.3.1 2019 年 5 月 4 日
0.3.0 2019 年 4 月 21 日

异步 中排名第 1379

MIT 许可证

51KB
573

Rust 的响应式流。

Rust 方式的响应式编程

此库提供了 2 个基本响应式编程原语。 Stream 代表一个无状态的管道,可以异步订阅和处理数据。 ReactiveValue 代表一个随时间变化值的片段(类似于 Atomic,但由流支持,以便依赖者在值更改时得到通知)。这两个原语与 Rx 库系列中的 'Stream' 和 'BehaviorSubject' 分别对应。

此库的一个独特特性是流订阅仅在其订阅对象存在的时间内持续,这防止了响应式代码中常见的许多内存泄漏和僵尸回调问题。

let stream_host: epoxy::Sink<i32> = epoxy::Sink::new();
let stream = stream_host.get_stream();
{
    let _sub = stream.subscribe(|val| println!("Emitted {}", val));
    stream_host.emit(1); // 'Emitted 1' is printed
    assert_eq!(stream.count_subscribers(), 1);
}
stream_host.emit(2); // Nothing is printed
assert_eq!(stream.count_subscribers(), 0);

可以使用基于 Rust 的迭代操作集合的内置函数库来操作流。目前这些操作包括

操作 返回流属性
map(fn) 将输入流的所有值通过映射函数运行
map_rc(fn) 与 map() 相同,但映射函数接收并返回一个 Arc
flat_map(fn) 类似于 map(),但迭代映射函数的结果
filter(fn) 仅返回通过给定过滤函数的输入值
inspect(method) 传递原始流,对每个项目调用一个方法
scan(fn, default) 类似于 reduce(),但返回每次迭代的值
count_values() 返回流已发射的次数
buffer(size) 将发射的值收集到长度为 size 的向量中

ReactiveValue 有自己的操作符集,尽管也可以使用 .as_stream() 获取 ReactiveValue 的底层流引用,并使用上述任何操作。

操作 返回的响应值属性
map(fn) 将输入流的所有值通过映射函数运行
sanitize(fn, default) 如果输入不通过测试 fn,则不更改值
fallback(fn, fallback) 如果输入不通过测试 fn,则将值更改为 fallback

然而,此库还附带了一个 computed! 宏,这使得处理 ReactiveValue 与处理任何其他 Rust 变量一样简单。

# #[macro_use] extern crate epoxy;
use epoxy::ReactiveValue;

let points = epoxy::ReactiveValue::new(4);
let multiplier = epoxy::ReactiveValue::new(1);
let score = computed!(points * multiplier);

assert_eq!(*score.get(), 4);

multiplier.set(2);
assert_eq!(*score.get(), 8);

与其他 FRP 库的比较

Carboxyl / Frappe

Carboxyl和Frappe是目前Rust中最常见的两个FRP库(我将它们合并在这里,因为它们的结构非常相似)。这个库的灵感来自ReactiveX,而不是Carboxyl和Frappe使用的FRP论文,因此这里的一些术语有所不同。还有一些重要的API差异

  • Epoxy订阅 <-> Carboxyl观察者
    • Epoxy中的订阅在超出作用域时会取消订阅
    • Carboxyl观察者在观察函数返回False-y时会取消订阅
  • Epoxy ReactiveValue <-> Carboxyl信号
    • Carboxyl信号不能被订阅(观察),这对UI框架来说是一个问题
    • Epoxy ReactiveValues是推模式,而不是拉模式,在某些情况下效率较低
  • Epoxy computed! <-> Carboxyl lift!
    • Epoxy computed!宏从函数定义中提取变量,使其更易于阅读
    • Carboxyl的lift!宏明确定义了输入,从而减少了错误的可能性

正如你所见,这两个框架都有其权衡。Epoxy被设计为优化前端用例,使其更容易与DOM库等集成。

ReactiveX

这些流比ReactiveX库家族中的流要简单得多。最显著的区别是,这个库没有“冷流”的概念,这意味着没有任何流会在订阅时立即发出值。这个库中的流也永远不会关闭,因为它们旨在模拟长期异步数据流(当然,通过创建Option枚举的流并在None上取消订阅,可以制作一个可关闭的流,但这并不是库内置的功能)。最后,与Rx订阅直到明确取消订阅才存在不同,Rust的响应式订阅仅在它们的作用域内存在。

状态

此crate正在积极开发中,可能还没有准备好投入生产使用。

依赖项

~2MB
~46K SLoC