#future #async-await #macro #futures-await #definition #async-trait

futures-await-await-macro

await! 宏定义 futures-await

3 个不稳定版本

使用旧的 Rust 2015

0.2.0-alpha2018 年 3 月 8 日
0.1.1 2018 年 4 月 11 日
0.1.0 2017 年 10 月 28 日

#3#futures-await

Download history 72/week @ 2024-03-14 86/week @ 2024-03-21 132/week @ 2024-03-28 107/week @ 2024-04-04 83/week @ 2024-04-11 84/week @ 2024-04-18 89/week @ 2024-04-25 73/week @ 2024-05-02 77/week @ 2024-05-09 82/week @ 2024-05-16 73/week @ 2024-05-23 74/week @ 2024-05-30 63/week @ 2024-06-06 63/week @ 2024-06-13 77/week @ 2024-06-20 39/week @ 2024-06-27

每月 254 次下载
用于 futures-await

MIT/Apache

4KB

futures-await

现在 async/await 已经正式成为 Rust 语言的组成部分,因此这个包不再必要,感谢所有人的努力,使之成为现实!

Rust 的异步/await 语法和 futures

这是什么?

在 Rust 中,今天与 futures 交互的主要方式是通过 Future 特性上的各种组合器。这并不完全是“回调地狱”,但随着每个新闭包的加入,代码的右移趋势有时会让人感觉像那样。async/await 的目的是在保留异步代码所有功能的同时,为代码提供一种更“同步”的感觉!

以下是这个包的一些示例

#[async]
fn fetch_rust_lang(client: hyper::Client) -> io::Result<String> {
    let response = await!(client.get("https://rust-lang.net.cn"))?;
    if !response.status().is_success() {
        return Err(io::Error::new(io::ErrorKind::Other, "request failed"))
    }
    let body = await!(response.body().concat())?;
    let string = String::from_utf8(body)?;
    Ok(string)
}

这里值得注意的点是

  • 函数被标记为 #[async],这意味着它们是 异步的,并且在调用时返回一个 future,而不是返回列出的结果。
  • await! 宏允许在 future 完成时进行阻塞。这实际上并没有阻塞线程,它只是阻止了从 fetch_rust_lang 返回的 future 继续执行。您可以将 await! 宏视为从 future 到其 Result 的函数,沿途消耗 future。
  • #[async] 函数中,错误处理要自然得多。我们可以使用 ? 操作符以及简单的 return Err 语句。在 futures 包中的组合器通常更繁琐。
  • fetch_rust_lang 返回的 future 实际上是在编译时生成的状态机,并且没有进行隐式内存分配。这是在下面描述的生成器之上构建的。

您还可以有异步方法

impl Foo {
    #[async]
    fn do_work(self) -> io::Result<u32> {
        // ...
    }
}

您可以指定您实际上希望返回一个特对象,例如:Box<Future<Item = i32, Error = io::Error>>

#[async(boxed)]
fn foo() -> io::Result<i32> {
    // ...
}

您还可以拥有操作在Stream特上的"异步 for 循环"。

#[async]
for message in stream {
    // ...
}

异步 for 循环会传播函数外的错误,因此 message 具有传入的 streamItem 类型。请注意,异步 for 循环只能用于 #[async] 函数内部。

最后,您可以通过 #[async_stream(item = _)] 创建一个 Stream 而不是 Future

#[async]
fn fetch(client: hyper::Client, url: &'static str) -> io::Result<String> {
    // ...
}

/// Fetch all provided urls one at a time
#[async_stream(item = String)]
fn fetch_all(client: hyper::Client, urls: Vec<&'static str>) -> io::Result<()> {
    for url in urls {
        let s = await!(fetch(client, url))?;
        stream_yield!(s);
    }
    Ok(())
}

#[async_stream] 必须通过 item = some::Path 指定项目类型,并且流输出的值必须包裹在 Result 中并通过 stream_yield! 宏返回。此宏还支持与 #[async] 相同的功能,一个额外的 boxed 参数用于返回一个 Box<Stream>,异步 for 循环等。

如何使用它?

此实现目前基于生成器/协程。此功能已于2017年8月29日刚刚加入,并将作为Rust的夜间频道。您可以通过以下方式获取夜间频道;

rustup update nightly

执行此操作后,您可能需要确保Cargo使用夜间工具链,通过如下方式调用它

cargo +nightly build

接下来,您将将其添加到您的 Cargo.toml

[dependencies]
futures-await = "0.1"

然后...

#![feature(proc_macro, generators)]

extern crate futures_await as futures;

use futures::prelude::*;

#[async]
fn foo() -> Result<i32, i32> {
    Ok(1 + await!(bar())?)
}

#[async]
fn bar() -> Result<i32, i32> {
    Ok(2)
}

fn main() {
    assert_eq!(foo().wait(), Ok(3));
}

这个crate旨在"伪装"为futures crate,重新导出其整个层次结构,并仅通过必要的运行时支持、async 属性和 await! 宏来增强它。当您导入它时,所有这些导入都包含在 futures::prelude::* 中。

有关大量示例和代码的详细信息,您还可以查看async-await 分支的 sccache,这是一个使用异步/等待语法的许多位置的过渡。您通常会发现代码在此之后更加易读,特别是这些更改

技术细节

如前所述,这个库在Rust的生成器特性基础上构建。生成器,在本例中也称为无栈协程,允许编译器为函数生成一个最优的Future实现,将看起来同步的代码块转换为可以异步执行的Future。这里的反语法化过程从你编写的代码到rustc编译的代码非常直接,你可以浏览futures-async-macro库的源代码以获取更多详细信息。

此外,这个库还提供了一些主要的“API”

  • #[async] - 这个属性可以应用于方法和函数,表示它是一个异步函数。函数的签名必须返回某种形式的Result(尽管它可以返回结果的类型定义)。此外,函数的参数必须全部是拥有值,换句话说,不能包含引用。这个限制可能会在未来被取消!

    一些例子包括

    // attribute on a bare function
    #[async]
    fn foo(a: i32) -> Result<u32, String> {
        // ...
    }
    
    // returning a typedef works too!
    #[async]
    fn foo() -> io::Result<u32> {
        // ...
    }
    
    impl Foo {
        // methods also work!
        #[async]
        fn foo(self) -> io::Result<u32> {
            // ...
        }
    }
    
    // For now, however, these do not work, they both have arguments which contain
    // references! This may work one day, but it does not work today.
    //
    // #[async]
    // fn foo(a: &i32) -> io::Result<u32> { /* ... */ }
    //
    // impl Foo {
    //     #[async]
    //     fn foo(&self) -> io::Result<u32> { /* ... */ }
    // }
    

    请注意,#[async]函数的意图是表现得与它的同步版本非常相似。例如,?运算符在内部工作,你可以使用早期的return语句等。

    在底层,一个#[async]函数被编译成一个表示此函数未来的状态机,状态机的挂起点将是await!宏的位置。

  • async_block! - 这个宏与#[async]类似,因为它创建了一个未来,但它可以在表达式上下文中使用,并且不需要一个专门的函数。这个宏可以被认为是“异步运行此代码块”,其中提供的代码块,就像异步函数一样,返回某种形式的结果。例如

    let server = TcpListener::bind(..);
    let future = async_block! {
        #[async]
        for connection in server.incoming() {
            spawn(handle_connection(connection));
        }
        Ok(())
    };
    core.run(future).unwrap();
    

    或者如果你想在函数中做一些预计算

    fn hash_file(path: &Path, pool: &CpuPool)
        -> impl Future<Item = u32, Error = io::Error>
    {
        let abs_path = calculate_abs_path(path);
        async_block! {
            let contents = await!(read_file)?;
            Ok(hash(&contents))
        }
    }
    
  • await! - 这是一个在futures-await-macro库中提供的宏,允许等待一个未来完成。await!宏只能在#[async]函数或一个async_block!中使用,可以被视为一个看起来像函数的宏

    fn await!<F: Future>(future: F) -> Result<F::Item, F::Error> {
        // magic
    }
    

    这个宏的一些例子是

    #[async]
    fn fetch_url(client: hyper::Client, url: String) -> io::Result<Vec<u8>> {
        // note the trailing `?` to propagate errors
        let response = await!(client.get(url))?;
        await!(response.body().concat())
    }
    
  • #[async]循环 - 能够异步遍历一个Stream。你可以通过将#[async]属性应用于一个循环,并且正在迭代的对象实现了Stream特质来完成这一点。

    流中的错误将自动传播,否则,for循环将耗尽流至完成,将每个元素绑定到提供的模式。

    一些例子包括

    #[async]
    fn accept_connections(listener: TcpListener) -> io::Result<()> {
        #[async]
        for connection in server.incoming() {
            // `connection` here has type `TcpStream`
        }
        Ok(())
    }
    

    请注意,像await!这样的#[async]循环只能在异步函数或异步块中使用。

  • #[async_stream(item = ...)] - 定义了一个函数,该函数是Stream的实现,而不是Future。此函数使用stream_yield!宏产生项目,并且与其他await!宏和#[async]循环一起工作。

    声明的函数必须返回一个Result<(), E>,其中E成为返回的Stream的错误。通过从函数返回Ok(())或返回一个错误来终止流。在函数内部,操作如?也可以用于传播错误。

    示例如下

    #[async_stream(item = u32)]
    fn accept_connections(listener: TcpListener) -> io::Result<()> {
        #[async]
        for object in fetch_all_objects() {
            let description = await!(fetch_object_description(object));
            stream_yield!(description);
        }
        Ok(())
    }
    

夜间功能

目前,此crate需要两个夜间功能才能使用

  • #![feature(generators)] - 这是一个实验性语言功能,尚未稳定,但它是实现异步/await的基础。最近实现的功能已经落地,其稳定化的进展可以在其跟踪问题上找到。

  • #![feature(proc_macro)] - 这也被称为“宏2.0”,这是如何在此crate中定义#[async]属性而不是编译器本身的。我们还将利用其他宏2.0功能,如通过use导入宏,而不是通过#[macro_use]。此功能的跟踪问题包括#38356#35896

接下来是什么?

此crate仍然相当新,生成器才刚刚在Rust的夜间通道上落地。虽然目前没有计划对此crate进行重大更改,但我们将看看这一切如何发展!将生成器尽快引入语言的主要动机之一是为了间接地使此异步/await实现获得力量,并需要您的帮助来评估这一点!

特别地,我们在这个crate本身以及作为语言特性的生成器中寻找对async/await的反馈。我们想确保,如果我们要稳定生成器或过程宏,它们将提供最佳的async/await体验!

目前,我们鼓励您在生产环境中谨慎使用。这个crate本身仅进行了非常有限的测试,并且到目前为止,语言特性generators也只进行了有限的测试。建议您保持警惕,注意查找错误!如果您遇到任何问题或疑问,非常欢迎您创建一个问题

注意事项

正如许多夜间功能所预期的那样,在处理此项目时有一些需要注意的问题。尽管如此,欢迎提交错误报告或经验报告,知道需要修复什么总是好的!

借用

目前的借用并不太有效。编译器将拒绝许多借用,或者由于生成器特性正在开发中,可能存在一些潜在的不安全性。例如,如果您有一个像这样的函数:

#[async]
fn foo(s: &str) -> io::Result<()> {
    // ..
}

这可能无法编译!原因是返回的未来通常需要遵守'static约束。异步函数在被调用时当前不执行任何代码,它们只有在被轮询时才会取得进展。这意味着当您调用一个异步函数时,所发生的事情是它创建一个未来,将参数打包起来,然后将其返回给您。在这种情况下,它必须返回&str,但这并不总是符合生存期的要求。

要使上述函数编译,可以这样做

#[async]
fn foo(s: String) -> io::Result<()> {
    // ...
}

或以其他方式使用所有权的值而不是借用引用。

请注意,借用问题并不仅限于参数。例如,以下代码今天(或至少不应该)无法编译

for line in string.lines() {
    await!(process(line));
    println!("processed: {}", line);
}

这里的问题是,line是一个在yield点(即对await!的调用)中活跃的借用值。这意味着当未来可能返回到栈上时,它是await!过程的一部分,它必须在重新进入未来时恢复line变量。这还没有完全实现,并且今天可能是不安全的。作为一个经验法则,现在您只需要在调用await!或异步for循环期间有所有权的值(没有内部借用)。

目前正在努力寻找减轻这种限制的方法!借用是Rust中许多便利模式的核心,我们希望它能正常工作!

作为最后一个要点,由于今天“不允许借用参数”,函数签名

#[async]
fn foo(&self) -> io::Result<()> {
    // ...
}

很遗憾,将无法工作。您可能需要以值的方式获取self,或者将职责委托给另一个#[async]函数。

特性行为中的未来

假设您有一个这样的特质

trait MyStuff {
    fn do_async_task(??self) -> Box<Future<...>>;
}

在这里我们将暂时忽略self的细节,但从本质上讲,我们在特质中有一个想要返回未来的函数。遗憾的是,实际上很难使用它!目前有几个需要注意的问题

  • 理想情况下,您希望标记这个 #[async],但这不起作用,因为目前编译器中没有实现返回 impl Future 的 trait 函数。不过,我被告知这最终会工作!
  • 那么,接下来最好的选择是返回一个封装的 trait 对象,而不是返回 impl Future,使用 #[async] (boxed)。这可能在运行时不是我们想要的,但我们也有...
  • 但现在这让我们不得不面对 self 的处理。由于目前 #[async] 的限制,我们只有两种选择,selfself: Box<Self>。前者不幸地不是对象安全的(现在我们无法使用这个 trait 的虚拟调度),后者通常很浪费(每次调用现在都需要新的分配)。理想情况下,我们希望使用 self: Rc<Self>,这正是我们想要的!但遗憾的是,编译器还没有实现 😦

所以,基本上总结一下,您现在有两种选择在 trait 中返回 futures

trait MyStuff {
    // Trait is not object safe because of `self` so can't have virtual
    // dispatch, and the allocation of `Box<..>` as a return value is required
    // until the compiler implements returning `impl Future` from traits.
    //
    // Note that the upside of this approach, though, is that `self` could be
    // something like `Rc` or have a bunch fo `Rc` inside of `self`, so this
    // could be cheap to call.
    #[async]
    fn do_async_task(self) -> Box<Future<...>>;
}

或者,另一种选择

trait MyStuff {
    // Like above we returned a trait object but here the trait is indeed object
    // safe, allowing virtual dispatch. The downside is that we must have a
    // `Box` on hand every time we call this function, which may be costly in
    // some situations.
    #[async]
    fn do_async_task(self: Box<Self>) -> Box<Future<...>>;
}

在 trait 中实现 futures 的 理想最终目标 是这个

trait MyStuff {
    #[async]
    fn do_async_task(self: Rc<Self>) -> Result<i32, u32>;
}

但这需要两个部分来实现

  • 编译器必须接受返回 impl Trait 的 trait 函数
  • 编译器需要支持 self: Rc<Self>,基本上是 trait 中的对象安全自定义智能指针。

您今天可以通过使用非对象安全的实现来模拟这一点

trait Foo {
    #[async]
    fn do_async_task(me: Rc<Self>) -> Result<i32, u32>;
}

fn foo<T: Foo>(t: Rc<T>) {
    let x = Foo::trait_fn(t);
    // ...
}

但这并不十分方便!

关联类型

使用 traits 与 futures 一起使用时的另一个限制是与关联类型相关。比如说,您有一个这样的 trait

trait Service {
    type Request;
    type Response;
    type Error;
    type Future: Future<Item = Self::Response, Error = Self::Error>;

    fn call(&self, req: Self::Request) -> Self::Future;
}

如果您想使用 callasync_block! 实现,或者通过返回另一个使用 #[async] 生成的函数的 futures,您可能会想使用 impl Future。不幸的是,目前无法使用 impl Trait 表达像 Service::Future 这样的关联常量。

目前最好的解决方案是使用 Box<Future<...>>

impl Service for MyStruct {
    type Request = ...;
    type Response = ...;
    type Error = ...;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, req: Self::Request) -> Self::Future {
        // ...
        Box::new(future)
    }
}

许可证

此项目根据以下其中之一进行许可:

任选其一。

贡献

除非您明确声明,否则根据Apache-2.0许可证定义,您有意提交用于包含在futures-await中的任何贡献,应按上述方式双重许可,不附加任何额外的条款或条件。

无运行时依赖