[Rust-async-book]--7--Workarounds to Know and Love[翻译](完结)

Rust's async support is still fairly new, and there are a handful of highly-requested features still under active development, as well as some subpar diagnostics. This chapter will discuss some common pain points and explain how to work around them.
Rust的异步支持仍然是相当新的,还有一些高要求的特性仍在积极开发中,以及一些低于标准的诊断。本章将讨论一些常见的痛点,并解释如何解决这些痛点。

Return Type Errors

In a typical Rust function, returning a value of the wrong type will result in an error that looks something like this:
在一个典型的Rust函数中,返回一个错误类型的值将导致类似如下的错误:

error[E0308]: mismatched types
 --> src/main.rs:2:12
  |
1 | fn foo() {
  |           - expected `()` because of default return type
2 |     return "foo"
  |            ^^^^^ expected (), found reference
  |
  = note: expected type `()`
             found type `&'static str`

However, the current async fn support doesn't know to "trust" the return type written in the function signature, causing mismatched or even reversed-sounding errors. For example, the function async fn foo() { "foo" } results in this error:
但是,当前的async fn支持不知道要“trust”函数签名中写入的返回类型,从而导致不匹配甚至反向探测错误。例如,函数async fn foo(){“foo”}会导致以下错误:

error[E0271]: type mismatch resolving `::Output == ()`
 --> src/lib.rs:1:16
  |
1 | async fn foo() {
  |                ^ expected &str, found ()
  |
  = note: expected type `&str`
             found type `()`
  = note: the return type of a function must have a statically known size

The error says that it expected &str and found (), which is actually the exact opposite of what you'd want. This is because the compiler is incorrectly trusting the function body to return the correct type.
这个错误表明它期望&str和found(),这实际上与您想要的完全相反。这是因为编译器错误地相信函数体会返回正确的类型。

The workaround for this issue is to recognize that errors pointing to the function signature with the message "expected SomeType, found OtherType" usually indicate that one or more return sites are incorrect.
这个问题的解决方法是识别指向带有消息“expected SomeType, found OtherType”的函数签名的错误,这通常表示一个或多个返回站点是不正确的。

A fix to this issue is being tracked in this bug.
这个问题的一个修正在这个bug中被跟踪。

Box

Similarly, because the return type from the function signature is not propagated down correctly, values returned from async fn aren't correctly coerced to their expected type.
类似地,因为函数签名的返回类型没有正确地向下传播,所以从async fn返回的值没有正确地强制转换为它们期望的类型

In practice, this means that returning Box objects from an async fn requires manually as-casting from Box to Box.
在实践中,这意味着从异步fn中返回Box对象需要手动地从Box转换到Box

This code will result in an error:
这段代码将导致一个错误:

async fn x() -> Box {
    Box::new("foo")
}

This issue can be worked around by manually casting using as:
这个问题可以解决,通过 使用 as 手动转换:

async fn x() -> Box {
    Box::new("foo") as Box
}

A fix to this issue is being tracked in this bug.

'?' in 'async' Blocks

Just as in async fn, it's common to use ? inside async blocks. However, the return type of async blocks isn't explicitly stated. This can cause the compiler to fail to infer the error type of the async block.
就像在异步fn,它是常用的?在异步块。但是,异步块的返回类型没有显式声明。这可能导致编译器无法推断异步块的错误类型。

For example, this code:
例如,这段代码:

let fut = async {
foo().await?;
bar().await?;
Ok(())
};

will trigger this error:

error[E0282]: type annotations needed
 --> src/main.rs:5:9
  |
4 |     let fut = async {
  |         --- consider giving `fut` a type
5 |         foo().await?;
  |         ^^^^^^^^^^^^ cannot infer type

Unfortunately, there's currently no way to "give fut a type", nor a way to explicitly specify the return type of an async block. To work around this, use the "turbofish" operator to supply the success and error types for the async block:

不幸的是,目前没有办法“give fut a type”,也没有办法显式地指定异步块的返回类型。为了解决这个问题,使用“turbofish”操作符为异步块提供成功和错误类型:

let fut = async {
    foo().await?;
    bar().await?;
    Ok::<(), MyError>(()) // <- note the explicit type annotation here
};

'Send' Approximation

Some async fn state machines are safe to be sent across threads, while others are not. Whether or not an async fn Future is Send is determined by whether a non-Send type is held across an .await point. The compiler does its best to approximate when values may be held across an .await point, but this analysis is too conservative in a number of places today.
一些异步fn状态机可以安全地跨线程发送,而另一些则不能。是否发送异步fn Future取决于是否跨.await点持有非发送类型。编译器尽其所能来估计在.await点上什么时候可以保存值,但是这种分析在今天的很多地方都太保守了。
For example, consider a simple non-Send type, perhaps a type which contains an Rc:
例如,考虑一个简单的非发送类型,可能是一个包含Rc的类型:

use std::rc::Rc;

#[derive(Default)]
struct NotSend(Rc<()>);

Variables of type NotSend can briefly appear as temporaries in async fns even when the resulting Future type returned by the async fn must be Send:
在异步fn中,即使异步fn返回的结果 Future type 必须为Send,NotSend类型的变量也可能短暂地显示为临时变量:

async fn bar() {}
async fn foo() {
    NotSend::default();
    bar().await;
}

fn require_send(_: impl Send) {}

fn main() {
    require_send(foo());
}

However, if we change foo to store NotSend in a variable, this example no longer compiles:
然而,如果我们改变foo来存储NotSend在一个变量,这个例子不再编译:

async fn foo() {
    let x = NotSend::default();
    bar().await;
}
error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
  --> src/main.rs:15:5
   |
15 |     require_send(foo());
   |     ^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely
   |
   = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
   = note: required because it appears within the type `NotSend`
   = note: required because it appears within the type `{NotSend, impl std::future::Future, ()}`
   = note: required because it appears within the type `[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `impl std::future::Future`
note: required by `require_send`
  --> src/main.rs:12:1
   |
12 | fn require_send(_: impl Send) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

This error is correct. If we store x into a variable, it won't be dropped until after the .await, at which point the async fn may be running on a different thread. Since Rc is not Send, allowing it to travel across threads would be unsound. One simple solution to this would be to drop the Rc before the .await, but unfortunately that does not work today.
这个错误是正确的。如果我们将x存储到一个变量中,它将在.await之后被删除,此时async fn可能正在另一个线程上运行。因为Rc不发送,所以允许它跨线程传输是不可靠的。解决这个问题的一个简单方法是在.await之前删除Rc,但不幸的是,这在现在不起作用。

In order to successfully work around this issue, you may have to introduce a block scope encapsulating any non-Send variables. This makes it easier for the compiler to tell that these variables do not live across an .await point.
为了成功地解决这个问题,您可能需要引入一个块作用域来封装任何非发送变量。这使得编译器更容易判断这些变量不存在于.await点上。

async fn foo() {
    {
        let x = NotSend::default();
    }
    bar().await;
}

Recursion 递归

Internally, async fn creates a state machine type containing each sub-Future being .awaited. This makes recursive async fns a little tricky, since the resulting state machine type has to contain itself:

在内部,async fn创建一个状态机类型,其中包含每个子Future being.awaited。这使得递归异步fn有点棘手,因为生成的状态机类型必须包含自身:

// This function:
async fn foo() {
    step_one().await;
    step_two().await;
}
// generates a type like this:
enum Foo {
    First(StepOne),
    Second(StepTwo),
}

// So this function:
async fn recursive() {
    recursive().await;
    recursive().await;
}

// generates a type like this:
enum Recursive {
    First(Recursive),
    Second(Recursive),
}

This won't work-- we've created an infinitely-sized type! The compiler will complain:
这行不通——我们已经创建了一个无限大的类型!编译器将抱怨:

error[E0733]: recursion in an `async fn` requires boxing
 --> src/lib.rs:1:22
  |
1 | async fn recursive() {
  |                      ^ an `async fn` cannot invoke itself directly
  |
  = note: a recursive `async fn` must be rewritten to return a boxed future.

In order to allow this, we have to introduce an indirection using Box. Unfortunately, compiler limitations mean that just wrapping the calls to recursive() in Box::pin isn't enough. To make this work, we have to make recursive into a non-async function which returns a .boxed() async block:
为了实现这一点,我们必须引入一个间接使用Box。不幸的是,编译器的限制意味着仅仅在Box::pin中包装对recursive()的调用是不够的。要使其正常工作,我们必须将递归设置为非异步函数,该函数返回.boxed()异步块:

use futures::future::{BoxFuture, FutureExt};

fn recursive() -> BoxFuture<'static, ()> {
    async move {
        recursive().await;
        recursive().await;
    }.boxed()
}

async in Traits

Currently, async fn cannot be used in traits. The reasons for this are somewhat complex, but there are plans to remove this restriction in the future.
目前,异步fn不能用于traits。原因有点复杂,但有计划在未来取消这一限制。

In the meantime, however, this can be worked around using the async_trait crate from crates.io.
不过,在此期间,可以使用crates.io中的async_trait crate来解决这个问题。

Note that using these trait methods will result in a heap allocation per-function-call. This is not a significant cost for the vast majority of applications, but should be considered when deciding whether to use this functionality in the public API of a low-level function that is expected to be called millions of times a second.
注意,使用这些trait方法将导致每个函数调用的堆分配。对于绝大多数应用程序来说,这不是一个很大的成本,但是在决定是否在一个低层函数的公共API中使用这个功能时,应该考虑到这个低层函数预计每秒被调用数百万次。

你可能感兴趣的:([Rust-async-book]--7--Workarounds to Know and Love[翻译](完结))