29个版本 (18个破坏性更新)

0.19.0 2023年1月19日
0.17.0 2023年1月2日
0.13.0 2022年12月31日
0.11.0 2022年10月7日
0.4.0 2022年1月13日

#535 in 数据结构

Download history 1/week @ 2024-03-13 89/week @ 2024-03-27 90/week @ 2024-04-03

每月下载量 86次

MIT许可证

60KB
1K SLoC

(双向)协程

这是一个简单的纯库,用于在Rust中模拟双向协程。这允许协程被定义为与实际的IO实现抽象化。

主要动机是编写可测试的协程,它们对输入做出响应并生成输出。这是一种比Future更通用的形式。

这是一种协作并发形式。它也与actor模型类似。

心理模型

该库的心理模型是基于'拉取'的,在概念上与Rust的迭代器相似,但我们也可以'请求'更多的输入,这是迭代器无法做到的。这意味着我们不需要'分配'或缓冲太多,我们只需要将'下一步要做什么'作为一个闭包装箱。

没有使用借用,协程在传递过程中被消耗。这是为了避免协程状态的'克隆'这种奇怪情况,以及围绕执行方式的复杂性。

功能性

有一个核心数据类型,即协程。其余的功能由允许您将协程和闭包粘合在一起以编写您的问题的函数提供。

示例

use bicoro::*;
use bicoro::iterator::*;
use ::do_notation::m;
 
// The coroutine in dot-notation
let co : Coroutine<i32,String,()> =
        m! {
            value_1 <- receive();
            value_2 <- receive();
            let sum = i32::wrapping_add(value_1,value_2);
            let output = sum.to_string();
            send(output);
            result(())
        };
 
// Arrange inputs and an store outputs in a vec
let inputs = vec![1,2];

// creates an interator of outputs, fed via the inputs
let mut it = as_iterator(co,inputs.into_iter());

// By ref so we don't consume the iterator (important if
// we want to get the remaining inputs or coroutine)
let outputs = it.by_ref().collect::<Vec<_>>();
// Verify output values
assert_eq!(outputs, vec!["3"]); 

// Consume the iterator, so that we can check results
let (result,mut remaining_inputs) = it.finish();
// We return the result from the coroutine.
assert!(matches!(result,Result::Ok(())));
// All the inputs from the iterator were consumed
assert!(matches!(remaining_inputs.unwrap().next(),None));

执行

协程只描述要做什么,实际上不会做任何事情。您需要通过执行器运行它。提供了一个适用于迭代器的执行器,但您也可以创建自己的以实现不同的上下文。

用例

协议描述

这可以用来使用无IO哲学https://sans-io.readthedocs.io描述协议。可以将协议视为一个接收输入消息并响应输出的actor,同时维护自己的内部状态。由于协程是可嵌套和可组合的,我们可以创建子例程和函数来解决协议的特定'子状态',例如连接然后过渡到协议的主要部分。

然后执行器可以映射到底层IO函数,例如读取/写入字节。将这些分开意味着协议实现可以彻底测试。

状态机

我们可以模拟状态机(请参阅示例)。特别有用,因为我们可以在协议中组合和组合状态机。类型甚至可以显示机器没有'最终'状态,例如 Coroutine<Input,Ouput,!> 意味着我们始终可以向机器提供信息。

在状态转换时,我们还可以发出多个消息或没有消息,因此在输出方面我们非常灵活。

具有终止状态的迭代器

Coroutine<!,Output,Result> 类似于 Rust 迭代器,但最终结果不需要是相同的类型。这非常有用,因为您不需要创建一个笨拙的枚举,并且只在最后创建一个 '终止' 状态。它也更能清楚地传达 '我们已经完成了' 的信息,并消耗了例程,因此不会意外地再次使用。

折叠

Coroutine<Input,!,Result> 类似于折叠(归约)一组输入。这甚至可以在没有迭代器的情况下编写。例如,它可以在循环中,当需要读取输入时读取,这对于交互式组件非常有用。

do-notation

实现与 https://github.com/phaazon/do-notation 兼容,允许使用类似于在 futures 上使用 .await 的语法。

这有助于缓解回调地狱,但并非绝对必要。

依赖项