当前位置:网站首页>Demystifying Closures, Futures and async-await in Rust–Part 3: Async & Await
Demystifying Closures, Futures and async-await in Rust–Part 3: Async & Await
2022-07-21 01:53:00 【爱学习的佳】
The Async you’ve been Awaiting for
Let’s pretend we don’t know anything and jump right in and try to write our first async
function. To do this, we simply prepend the async
keyword to our fn
declaration:
async fn async_hello() { debug!("Hello, asynchronously!"); }
That’s all it takes, really!
But what happens if we try to call that function directly? In our main()
let’s try writing:
fn main() { // ... async_hello(); }
We get a compiler warning:
warning: unused implementer of `core::future::future::Future` that must be used --> src/main.rs:169:5 | 169 | async_hello(); | ^^^^^^^^^^^^^^ | = note: `#[warn(unused_must_use)]` on by default = note: futures do nothing unless you `.await` or poll them
Rust is telling us that async_hello()
is returning a Future
that is unused. (You may recall we’ve seen this warning before, when we tried to simply call our returns_delayed_future()
in rt.enter()
.)
But what happens if we try to .await
it just like it says?
Now we get a compiler error, since our main()
is not async:
error[E0728]: `await` is only allowed inside `async` functions and blocks --> src/main.rs:169:5 | 102 | fn main() { | ---- this is not `async` ... 169 | async_hello().await; | ^^^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks
The error mentions async
blocks, let’s try wrapping that line in an async {}
:
async { async_hello().await; }
If that block is at the end of fn main() { ... }
, we immediately run into:
error[E0308]: mismatched types --> src/main.rs:169:5 | 102 | fn main() { | - expected `()` because of default return type ... 169 | / async { 170 | | async_hello().await; 171 | | } | | ^- help: try adding a semicolon: `;` | |_____| | expected `()`, found opaque type
Now if we add a semicolon after the block as it says:
async { async_hello().await; };
We once again get:
warning: unused implementer of `core::future::future::Future` that must be used --> src/main.rs:169:5 | 169 | / async { 170 | | async_hello().await; 171 | | }; | |______^ | = note: `#[warn(unused_must_use)]` on by default = note: futures do nothing unless you `.await` or poll them
But this time, at the end of the async
block.
We’re just running around in circles!
async -> Future
The key thing to remember is that whether we use async
for a block or a function, both times we get a Future
.
We already know what to do from Part 2 when we tried to call a function that returns a Future
directly in main()
.
For a Future
to be executed or polled, we need to pass our future to the Tokio runtime. Thus we need to write:
let mut rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async_hello());
So we get the output we desire:
08:10:29 [DEBUG] (1) Hello, asynchronously!
Now let’s try that async
block again. First we’ll print something directly in the block:
let async_block = async { debug!("in async_block"); }; rt.block_on(async_block);
That works as expected. We can even ‘capture’ surrounding variables (Rust has a different and specific definition of async closure
though and as of this writing, it’s a feature still deemed unstable):
{ let x = 42; let async_capture = async { debug!("in async_capture, x => {}", x); }; rt.block_on(async_capture); }
You can think of the above code as equivalent to:
{ let x = 42; let async_capture = future::lazy(|_| { debug!("in async_capture, x => {}", x); }); rt.block_on(async_capture); }
To recap we can think of async {...}
as ‘desugaring’ to future::lazy(|_| {...})
. They’re not exactly the same but for practical purposes they exhibit the same behavior.
Async Functions
Similarly, we can now begin to understand async fn
as merely ‘sugar’ that returns a Future<Output=T>
.
That is, the following functions are practically equivalent:
async fn async_returns_i32() -> i32 { 42 }fn returns_future_i32() -> impl Future<Output = i32> { future::ready(42) }
So the way to read an async fn f() -> T
is simply as being equivalent to an ordinary fn f() -> impl Future<Output = T>
.
We can even return an async
block as a Future
:
fn returns_async_block_i32() -> impl Future<Output = i32> { async { 42 } }
All three above functions when called behave pretty much exactly the same way.
From Futures to Async-Await
Up until now, we’ve been calling rt.block_on()
multiple times.
Suppose our code looks like this:
rt.block_on(future::lazy(|_| debug!("in rt.block_on()"))); let r0 = rt.block_on(future::ready("Hello from rt.block_on()")); debug!("{}", r0); let r1 = rt.block_on(returns_impl_future_i32()); debug!("returns_impl_future_i32() -> {}", r1); let r2 = rt.block_on(returns_dyn_future_i32()); debug!("returns_dyn_future_i32() -> {}", r2); let r3 = rt.block_on(returns_future_result()); debug!("returns_future_result() -> {}", r3.unwrap()); let r4 = rt.block_on(returns_future_result_dyn_error()); debug!("returns_future_result_dyn_error() -> {}", r4.unwrap()); let r5 = rt.block_on(returns_delayed_future())); debug!("returns_delayed_future() -> {}", r5)) let r6 = rt.block_on(wait_a_sec(future::ready(42))); debug!("wait_a_sec(future::ready(42)) -> {}", r6)); rt.block_on(async_hello()); let async_block = async { debug!("in async_block"); }; rt.block_on(async_block); let x = 42; let async_capture = async { debug!("in async_capture, x => {}", x); }; rt.block_on(async_capture); let r7 = rt.block_on(async_returns_i32()); debug!("async_returns_i32 -> {}", r7); let r8 = rt.block_on(returns_future_i32()); debug!("returns_future_i32 -> {}", r8); let r9 = rt.block_on(returns_async_block_i32()); debug!("returns_async_block_i32 -> {}", r9);
We can rewrite all the above using future combinators to return a single future chain, or we can now truly start making use of the power and convenience async
and await
provide us.
As we’ve seen, an async {}
and a future::lazy(|_| {…})
are practically interchangeable.
So first, we need to surround all those lines starting with future::lazy
in a single async {...}
block which we will block_on()
.
We then simply replace all the inner calls to rt.block_on(f)
with f.await
The above code now becomes:
rt.block_on( async { debug!("in rt.block_on()"); let r0 = future::ready("Hello from rt.block_on()").await; debug!("{}", r0); let r1 = returns_impl_future_i32().await; debug!("returns_impl_future_i32() -> {}", r1); let r2 = returns_dyn_future_i32().await; debug!("returns_dyn_future_i32() -> {}", r2); let r3 = returns_future_result().await; debug!("returns_future_result() -> {}", r3.unwrap()); let r4 = returns_future_result_dyn_error().await; debug!("returns_future_result_dyn_error() -> {}", r4.unwrap()); let r5 = returns_delayed_future().await; debug!("returns_delayed_future() -> {}", r5); let r6 = wait_a_sec(future::ready(42)).await; debug!("wait_a_sec(future::ready(42)) -> {}", r6); async_hello().await; let async_block = async { debug!("in async_block"); }; async_block.await; let x = 42; let async_capture = async { debug!("in async_capture, x => {}", x); }; async_capture.await; let r7 = async_returns_i32().await; debug!("async_returns_i32 -> {}", r7); let r8 = returns_future_i32().await; debug!("returns_future_i32 -> {}", r8); let r9 = returns_async_block_i32().await; debug!("returns_async_block_i32 -> {}", r9); });
Like rt.block_on()
, .await
works just as well whether for a Future
or an async
block or an async fn
.
#[tokio::main]
Now if all our main()
function does is equivalent to the following:
fn main() { let mut rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { // do async stuff }); }
Then Tokio lets us pull another trick to further simplify our code and make it as if Rust had an asynchronous runtime built-in.
It uses a custom Procedural Macro that allows us to attach the #[tokio::main]
attribute macro to our main()
function, which lets Tokio ‘inject’ all the boilerplate code into the resulting binary.
This is how Tokio lets us simply rewrite the above as:
#[tokio::main] async fn main() { // do async stuff }
Which is now how most contemporary examples of Rust asynchronous code starts.
Async Error Handling
As with Futures, we also need to handle errors gracefully. Fortunately, Rust already provides us with a way to reduce boilerplate via the ?
operator for error handling, which works just as well with .await
ed Result
s.
That is, suppose we have a function that may return an error:
fn fallible() -> Result<(), Box<dyn Error>> { let _f = std::fs::File::open("foo.txt")?; Ok(()) }
We can call it directly from main()
as such:
let _ = fallible().unwrap();
If we don’t care much for graceful error handling (we should, but this is just an exercise), we can avoid unwrapping by also changing main()
to return a Result<(), Box<dyn Error>>
:
#[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { // ... let _ = fallible()?; Ok(()) }
Now we can easily turn fallible
asynchronous, and we can just as easily .await?
:
async fn fallible() -> Result<(), Box<dyn Error>> { let _f = std::fs::File::open("foo.txt")?; Ok(()) }#[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { // ... let _ = fallible().await?; Ok(()) }
The key to error handling in Rust is being able to make sense of both the Result
success and error types.
边栏推荐
- FeCl3 modified mil-101 (CR) ferric chloride | zirconium metal organic framework compound (uio-68-sazide) | cobalt based metal organic framework (zif-67) | MOF custom material
- JSON值的获取以及遍历 [JSON]
- Pattern code lock
- Kwai wants to grab the shell cake. Is online selling a good business?
- php mb_ Detailed explanation of strpos() function
- Les enregistrements d'une période de temps sont interrogés dans la base de données Oracle. S'il n'y a pas d'enregistrements d'un jour donné, les enregistrements du dernier jour seront remplis. Comment
- NepCTF2022
- onmousemove=alert(1) style='width
- SkiaSharp 之 WPF 自绘弹跳球(案例版)
- MFC calculator
猜你喜欢
Getting started with ctfshow web (PHP deserialization)
CTFHub-信息泄露
GDB usage details
乙二胺改性金属有机骨架材料MIL-101(Cr)|功能MOFs/聚合物复合材料|ZIF-8/丙烯酸十四-十六酯共聚物(ZIF-8/P(TDA--HDA)
基于ssm框架的大学生社团管理系统
ctfshow web入门(文件包含)
Metal organic framework mil-101 (CR) loaded chitosan material | mil-101 (CR) @cs | glycyrrhetinic acid (GA) modified metal organic framework material uio-66-nh2 (uio-66- NH2 GA)
5-carboxyl tetramethyl rhodamine labeled peptide nucleic acid PNA | TMR ahx- (Tamra ahx-) -pna | fluorescein labeled peptide nucleic acid mechanism
什么?多商户系统不适配APP?这不就来了么!
5-羧基四甲基罗丹明标记多肽核酸PNA|TMR-Ahx-(TAMRA-Ahx-)-PNA|荧光素标记肽核酸作用机理
随机推荐
Balancing and scheduling of NuMA
JNDI注入之略微学学
USB眼图常识
Ctfshow getting started with the web (SSRF)
金属有机框架MIL-100(Cr)和MIL-101(Cr)负载磷钨酸|锆基金属有机骨架[email protected]多孔陶瓷复合材料(齐岳mof材料)
CY5-PNA花菁染料CY5标记肽核酸PNA的实验要求
Synthesis of tetramethyl rhodamine TRITC modified peptide nucleic acid PNA | TRITC PNA | fluorescein labeled PNA
Starfish OS:以现实为纽带,打造元宇宙新范式
Database table design (II): index specification and SQL statement specification
Leetcode 201 Digit range bitwise and (2022.07.20)
C4D用云渲染快不快?
写代码将三个整数数按从大到小输出。
CAPL中的CAN消息:声明、发送和接收
齐岳mof|CdS [email protected]|甲基丙烯酸磺基甜菜碱(pSBMA)接枝改性UiO-66-PEI|活性氧化铝负载FeMOF绿色复合粒料
从新零售到社区团购,这中间发生了多少变化?
ctfshow web入门(SSRF)
JSON定义的语法 [JSON]
EasyNVS定制项目中的播放器更新及相应新功能增加
Binary search principle, template, exercise
Mil-101-fe @uio-66-nh2 metal organic complexes (MOFs) composites | poly (1-h benzoindole) /zn mof/wo3 ternary Nanocomposites