13 个版本

使用旧的 Rust 2015

0.3.0 2019年11月20日
0.2.10 2019年4月10日
0.2.8 2019年1月30日
0.1.3 2018年11月29日

#1296异步


用于 4 个 crate (3 个直接)

MIT/Apache

13KB
252

这是死胡同

很遗憾地说,在评估 Rust 的愿景与我们的愿景之后,我们决定不再投资 Rust。Osaka 是 devguard.io 的重要构建块,但将不会有更新使其对公众可用。


Osaka 是受 Go 和 clay 编程语言启发的 Rust 异步

它围绕 continuation 而非 combinator 设计,允许更易读的流程。

为什么

Rust 的 tokio/futures 生态系统是一个复杂的单体,不适合我工作的 2MB 闪存 MIPS 盒子等受限设备(tokio 本身就有 1MB,加上所有 futures combinators)。Osaka 更多的是为我工作的一个 hack,而不是试图超越 futures.rs。

continuation 比 combinator 更容易理解,并且不需要特定的运行时。

它看起来像什么

最初我计划实现一个 proc macro,它将允许 Go 样式的 chans

#[osaka]
pub fn something(bar: chan String) {
    let foo <- bar;
}

然而,由于对 tokio 的替代品缺乏兴趣,我决定仅以绝对最小的工作量进行操作,因此它看起来像这样

#[osaka]
pub fn something(bar: Channel<String>) {
    let foo = sync!(bar);
}

在实际代码中,你可能想要将某些内容注册到 Poll 实例中,以便在 poll 准备就绪时重新激活闭包。

#[osaka]
pub fn something(poll: Poll) -> Result<Vec<String>, std::io::Error> {
    let sock    = mio::UdpSocket::bind(&"0.0.0.0:0".parse().unwrap())?;
    let token   = poll.register(&sock, mio::Ready::readable(), mio::PollOpt::level()).unwrap();

    loop {
        let mut buf = vec![0; 1024];
        if let Err(e) = sock.recv_from(&mut buf) {
            if e.kind() == std::io::ErrorKind::WouldBlock {
                yield poll.again(token, Some(Duration::from_secs(1)));
            }
        }
    }
}

pub fn main() {
    let poll = osaka::Poll::new();
    something(poll).run().unwrap();
}

请注意,没有后台的单例运行时。整个 executor(poll)被显式地作为参数传递。Osaka 故意比 futures.rs 简单得多。

以下是从 osaka-dns 的实际代码示例

#[osaka]
pub fn resolve(poll: Poll, names: Vec<String>) -> Result<Vec<String>, Error> {
    //...
    let sock = UdpSocket::bind(&"0.0.0.0:0".parse().unwrap()).map_err(|e| Error::Io(e))?;
    let token = poll
        .register(&sock, mio::Ready::readable(), mio::PollOpt::level())
        .unwrap();
    //...

    // wait for a packet
    let pkt = match loop {
        // wait for the token to be ready, or timeout
        yield poll.again(token.clone(), Some(Duration::from_secs(5)));
        if now.elapsed() >= Duration::from_secs(5) {
            // timeout
            break None;
        }
        // now the socket _should_ be ready
        let (len, from) = match sock.recv_from(&mut buf) {
            Ok(v) => v,
            Err(e) => {
                // but just in case it isn't lets re-loop
                if e.kind() == std::io::ErrorKind::WouldBlock {
                    continue;
                }
                return Err(Error::Io(e));
            }
        };
    }

    // do stuff with the pkt
    // ...
}

pub fn test(poll: Poll) -> Result<(), Error> {
    let mut a = resolve(
        poll.clone(),
        vec![
            "3.carrier.devguard.io".into(),
        ],
    );
    let y = osaka::sync!(a);
    println!("resolved: {:?}", y);
    Ok(())
}

pub fn main() {
    tinylogger::init().ok();
    let poll = osaka::Poll::new();
    test(poll).run().unwrap();
}

与 async/await 的区别

最重要的功能之一是所有行为都定义良好。在 Osaka 中,panic 总是代码中的错误,而不是你的代码。Osaka 通常更适用于“它编译,就可以发货”的工作流程。并且更倾向于明确性和“容易争论”而不是试图为了“容易编写”的代码而隐藏事件流程。

  • Osaka 没有隐式依赖
  • osaka::Again 包含一个 continuation 令牌,而不是隐藏的单例“任务”注册表。
  • 就绪性是明确的,这使得代码更容易争论“这里发生了什么”
  • 所有错误都是明确的
  • 没有未定义的行为。恐慌是Osaka的bug,而不是你的代码的bug。
  • 正如RFC2394中描述的“热函数”在Osaka中运行良好,因为延续点是显式的。

依赖项

~3MB
~60K SLoC