Futures, Tasks, and Threads
+Futures, Tasks, and Threads
As we saw in the previous chapter, threads provide one approach to concurrency. We have seen another approach to concurrency in this chapter, using async with futures and streams. You might be wondering why you would choose one or the @@ -331,7 +331,6 @@
Filename: src/main.rs
extern crate trpl; // for mdbook test
diff --git a/img/trpl17-03.svg b/img/trpl17-03.svg
new file mode 100644
index 0000000000..fed6c36040
--- /dev/null
+++ b/img/trpl17-03.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/img/trpl17-04.svg b/img/trpl17-04.svg
new file mode 100644
index 0000000000..e3472baa7c
--- /dev/null
+++ b/img/trpl17-04.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
diff --git a/img/trpl17-05.svg b/img/trpl17-05.svg
new file mode 100644
index 0000000000..443bb568dc
--- /dev/null
+++ b/img/trpl17-05.svg
@@ -0,0 +1,69 @@
+
+
+
+
+
diff --git a/img/trpl17-06.svg b/img/trpl17-06.svg
new file mode 100644
index 0000000000..712e3006f6
--- /dev/null
+++ b/img/trpl17-06.svg
@@ -0,0 +1,86 @@
+
+
+
+
+
diff --git a/img/trpl17-07.svg b/img/trpl17-07.svg
new file mode 100644
index 0000000000..b2275ac19f
--- /dev/null
+++ b/img/trpl17-07.svg
@@ -0,0 +1,57 @@
+
+
+
+
+
diff --git a/img/trpl17-08.svg b/img/trpl17-08.svg
new file mode 100644
index 0000000000..997d9b82e1
--- /dev/null
+++ b/img/trpl17-08.svg
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/print.html b/print.html
index d6ff2db55c..dda319d4f4 100644
--- a/print.html
+++ b/print.html
@@ -20647,7 +20647,7 @@ #![allow(unused)]
fn main() {
extern crate trpl; // required for mdbook test
+
enum PageTitleFuture<'a> {
- GetAwaitPoint {
- url: &'a str,
- },
- TextAwaitPoint {
- response: trpl::Response,
- },
+ Initial { url: &'a str },
+ GetAwaitPoint { url: &'a str },
+ TextAwaitPoint { response: trpl::Response },
}
}
Writing that out by hand would be tedious and error-prone, especially when
@@ -21033,8 +21031,15 @@
+Note: Under the hood, race
is built on a more general function, select
,
+which you will encounter more often in real-world Rust code. A select
+function can do a lot of things that trpl::race
function cannot, but it also
+has some additional complexity that we can skip over for now.
+
Either future can legitimately “win,” so it does not make sense to return a
Result
. Instead, race
returns a type we have not seen before,
trpl::Either
. The Either
type is somewhat like a Result
, in that it has
@@ -21111,7 +21116,7 @@
Counting
Then we write two loops within that block, each with a trpl::sleep
call in it,
which waits for half a second (500 milliseconds) before sending the next
message. We put one loop in the body of a trpl::spawn_task
and the other in a
-top-level for
loop. We also add an .await
after the sleep
calls.
+top-level for
loop. We also add an await
after the sleep
calls.
This does something similar to the thread-based implementation—including the
fact that you may see the messages appear in a different order in your own
terminal when you run it.
@@ -21159,7 +21164,7 @@ Counting
handle.await.unwrap();
});
}
-Listing 17-7: Using .await
with a join handle to run a task to completion
+Listing 17-7: Using await
with a join handle to run a task to completion
This updated version runs till both loops finish.
)
-like this:
-match hello("async").poll() {
- Ready(_) => {
- // We’re done!
+Under the hood, when you see code which uses await
, Rust compiles that to code
+which calls poll
. If you look back at Listing 17-4, where we printed out the
+page title for a single URL once it resolved, Rust compiles it into something
+kind of (although not exactly) like this:
+match page_title(url).poll() {
+ Ready(page_title) => match page_title {
+ Some(title) => println!("The title for {url} was {title}"),
+ None => println!("{url} had no title"),
}
Pending => {
// But what goes here?
@@ -23422,18 +23422,19 @@ Future
What should we do when the Future
is still Pending
? We need some way to try
again… and again, and again, until the future is finally ready. In other words,
a loop:
-let hello_fut = hello("async");
+let mut page_title_fut = page_title(url);
loop {
- match hello_fut.poll() {
- Ready(_) => {
- break;
+ match page_title_fut.poll() {
+ Ready(value) => match page_title {
+ Some(title) => println!("The title for {url} was {title}"),
+ None => println!("{url} had no title"),
}
Pending => {
// continue
}
}
}
-If Rust compiled it to exactly that code, though, every .await
would be
+
If Rust compiled it to exactly that code, though, every await
would be
blocking—exactly the opposite of what we were going for! Instead, Rust needs
makes sure that the loop can hand off control to something which can pause work
on this future and work on other futures and check this one again later. That
@@ -23457,7 +23458,7 @@
#![allow(unused)]
fn main() {
use std::pin::Pin;
@@ -23504,31 +23504,35 @@ }
-This is the first time we have seen a method where self
has a type annotation
-like this. When we specify the type of self
like this, we are telling Rust
-what type self
must be to call this method. These kinds of type annotations
-for self
are similar to those for other function parameters, but with the
-restriction that the type annotation has to be the type on which the method is
-implemented, or a reference or smart pointer to that type, or a Pin
wrapping a
-reference to that type. We will see more on this syntax in Chapter 18. For now,
-it is enough to know that if we want to poll a future (to check whether it is
-Pending
or Ready(Output)
), we need a mutable reference to the type, which is
-wrapped in a Pin
.
+The cx
parameter and its Context
type is interesting, but is beyond the
+scope of this chapter: you generally only need to worry about it when writing a
+custom Future
implementation.
+Instead, we will focus on the type for self
. This is the first time we have
+seen a method where self
has a type annotation. A type annotation for self
+is similar to type annotations for other function parameters, with two key
+differences. First, when we specify the type of self
like this, we are telling
+Rust what type self
must be to call this method. Second, a type annotation on
+self
cannot be just any type. It is only allowed to be the type on which the
+method is implemented, or a reference or smart pointer to that type, or a Pin
+wrapping a reference to that type. We will see more on this syntax in Chapter
+18. For now, it is enough to know that if we want to poll a future (to check
+whether it is Pending
or Ready(Output)
), we need a mutable reference to the
+type, which is wrapped in a Pin
.
Pin
is a wrapper type. In some ways, it is like the Box
, Rc
, and other
smart pointer types we saw in Chapter 15, which also wrap other types. Unlike
-those, however, Pin
only works with other pointer types like reference (&
-and &mut
) and smart pointers (Box
, Rc
, and so on). To be precise, Pin
-works with types which implement the Deref
or DerefMut
traits, which we
-covered in Chapter 15. You can think of this restriction as equivalent to only
-working with pointers, though, since implementing Deref
or DerefMut
means
-your type behaves like a pointer type. Pin
is also not a pointer itself, and
-it does not have any behavior of its own like the ref counting of Rc
or Arc
.
-It is purely a tool the compiler can use to uphold the relevant guarantees, by
+those, however, Pin
only works with pointer types like references (&
and
+&mut
) and smart pointers (Box
, Rc
, and so on). To be precise, Pin
works
+with types which implement the Deref
or DerefMut
traits, which we covered in
+Chapter 15. You can think of this restriction as equivalent to only working with
+pointers, though, since implementing Deref
or DerefMut
means your type
+behaves like a pointer type. Pin
is also not a pointer itself, and it does not
+have any behavior of its own like the ref counting of Rc
or Arc
. It is
+purely a tool the compiler can use to uphold the relevant guarantees, by
wrapping pointers in the type.
-Recalling that .await
is implemented in terms of calls to poll
, this
-starts to explain the error message we saw above—but that was in terms of
-Unpin
, not Pin
. So what exactly are Pin
and Unpin
, how do they relate,
-and why does Future
need self
to be in a Pin
type to call poll
?
+Recalling that await
is implemented in terms of calls to poll
, this starts
+to explain the error message we saw above—but that was in terms of Unpin
, not
+Pin
. So what exactly are Pin
and Unpin
, how do they relate, and why does
+Future
need self
to be in a Pin
type to call poll
?
In Our First Async Program, we described how a series of await
points in a future get compiled into a state machine—and noted how the compiler
helps make sure that state machine follows all of Rust’s normal rules around
@@ -23541,30 +23545,58 @@
+Figure 17-3: A self-referential data type.
+
+
By default, though, any object which has a reference to itself is unsafe to
+move, because references always point to the actual memory address of the thing
+they refer to. If you move the data structure itself, those internal references
+will be left pointing to the old location. However, that memory location is now
+invalid. For one thing, its value will not be updated when you make changes to
+the data structure. For another—and more importantly!—the computer is now free
+to reuse that memory for other things! You could end up reading completely
+unrelated data later.
+
+In principle, the Rust compiler could try to update every reference to an object
+every time it gets moved. That would potentially be a lot of performance
+overhead, especially given there can be a whole web of references that need
+updating. On the other hand, if we could make sure the data structure in
+question does not move in memory, we do not have to update any references.
+This is exactly what Rust’s borrow checker requires: you cannot move an item
+which has any active references to it using safe code.
Pin
builds on that to give us the exact guarantee we need. When we pin a
-value by wrapping a pointer to it in Pin
, it can no longer move. Thus, if you
-have Pin<Box<SomeType>>
, you actually pin the SomeType
value, not the
-Box
pointer. In fact, the pinned box pointer can move around freely. Remember:
-we care about making sure the data ultimately being referenced stays in its
-place. If a pointer moves around, but the data it points to is in the same
-place, there is no problem.
+value by wrapping a pointer to that value in Pin
, it can no longer move. Thus,
+if you have Pin<Box<SomeType>>
, you actually pin the SomeType
value, not
+the Box
pointer. Figure 17-5 illustrates this:
+
+In fact, the Box
pointer can still move around freely. Remember: we care about
+making sure the data ultimately being referenced stays in its place. If a
+pointer moves around, but the data it points to is in the same place, as in
+Figure 17-6, there is no potential problem. (How you would do this with a Pin
+wrapping a Box
is more than we will get into in this particular discussion,
+but it would make for a good exercise! If you look at the docs for the types as
+well as the std::pin
module, you might be able to work out how you would do
+that.) The key is that the self-referential type itself cannot move, because it
+is still pinned.
+
However, most types are perfectly safe to move around, even if they happen to be
behind a Pin
pointer. We only need to think about pinning when items have
internal references. Primitive values like numbers and booleans do not have any
@@ -23572,10 +23604,10 @@
+Figure 17-7: Pinning a String, with a dotted line indicating that the String implements the `Unpin` trait, so it is not pinned.
+
+
This means that we can do things like replace one string with another at the
+exact same location in memory as in Figure 17-8. This does not violate the Pin
+contract because String
—like most other types in Rust—implements Unpin
,
+because it has no internal references that make it unsafe to move around!
+
+Now we know enough to understand the errors reported for that join_all
call
+from back in Listing 17-17. We originally tried to move the futures produced by
+an async blocks into a Vec<Box<dyn Future<Output = ()>>>
, but as we have seen,
+those futures may have internal references, so they do not implement Unpin
.
+They need to be pinned, and then we can pass the Pin
type into the Vec
,
+confident that the underlying data in the futures will not be moved.
Pin
and Unpin
are mostly important for building lower-level libraries, or
when you are building a runtime itself, rather than for day to day Rust code.
When you see them, though, now you will know what to do!
@@ -23601,9 +23648,10 @@ Asynchronous Programming in Rust book has
you covered:
@@ -23685,13 +23733,13 @@ The Stream
in traits, since the lack thereof is the reason they do not yet have this.
-->
-Note: The actual definition we will use looks slightly different than this,
-because it supports versions of Rust which did not yet support using async
-functions in traits. As a result, it looks like this:
+Note: The actual definition we used earlier in the chapter looks slightly
+different than this, because it supports versions of Rust which did not yet
+support using async functions in traits. As a result, it looks like this:
fn next(&mut self) -> Next<'_, Self> where Self: Unpin;
That Next
type is just a simple struct
which implements Future
and gives
a way to name the lifetime of the reference to self
with Next<'_, Self>
,
-so that .await
can work with this!
+so that await
can work with this!
The StreamExt
trait is also the home of all the interesting methods available
to use with streams. StreamExt
is automatically implemented for every type
@@ -23706,7 +23754,7 @@
The Stream
That’s all we’re going to cover for the lower-level details on these traits. To
wrap up, let’s consider how futures (including streams), tasks, and threads all
fit together!
-Futures, Tasks, and Threads
+Futures, Tasks, and Threads
As we saw in the previous chapter, threads provide one approach to concurrency.
We have seen another approach to concurrency in this chapter, using async with
futures and streams. You might be wondering why you would choose one or the
@@ -23857,7 +23905,6 @@
The Stream
choose between threads and async. You can use them together freely, letting each
one serve the part it is best at. For example, Listing 17-TODO shows a fairly
common example of this kind of mix in real-world Rust code.
-
extern crate trpl; // for mdbook test
diff --git a/img/trpl17-03.svg b/img/trpl17-03.svg
new file mode 100644
index 0000000000..fed6c36040
--- /dev/null
+++ b/img/trpl17-03.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/img/trpl17-04.svg b/img/trpl17-04.svg
new file mode 100644
index 0000000000..e3472baa7c
--- /dev/null
+++ b/img/trpl17-04.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
diff --git a/img/trpl17-05.svg b/img/trpl17-05.svg
new file mode 100644
index 0000000000..443bb568dc
--- /dev/null
+++ b/img/trpl17-05.svg
@@ -0,0 +1,69 @@
+
+
+
+
+
diff --git a/img/trpl17-06.svg b/img/trpl17-06.svg
new file mode 100644
index 0000000000..712e3006f6
--- /dev/null
+++ b/img/trpl17-06.svg
@@ -0,0 +1,86 @@
+
+
+
+
+
diff --git a/img/trpl17-07.svg b/img/trpl17-07.svg
new file mode 100644
index 0000000000..b2275ac19f
--- /dev/null
+++ b/img/trpl17-07.svg
@@ -0,0 +1,57 @@
+
+
+
+
+
diff --git a/img/trpl17-08.svg b/img/trpl17-08.svg
new file mode 100644
index 0000000000..997d9b82e1
--- /dev/null
+++ b/img/trpl17-08.svg
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/print.html b/print.html
index d6ff2db55c..dda319d4f4 100644
--- a/print.html
+++ b/print.html
@@ -20647,7 +20647,7 @@ #![allow(unused)]
fn main() {
extern crate trpl; // required for mdbook test
+
enum PageTitleFuture<'a> {
- GetAwaitPoint {
- url: &'a str,
- },
- TextAwaitPoint {
- response: trpl::Response,
- },
+ Initial { url: &'a str },
+ GetAwaitPoint { url: &'a str },
+ TextAwaitPoint { response: trpl::Response },
}
}
Note: Under the hood, race
is built on a more general function, select
,
+which you will encounter more often in real-world Rust code. A select
+function can do a lot of things that trpl::race
function cannot, but it also
+has some additional complexity that we can skip over for now.
Either future can legitimately “win,” so it does not make sense to return a
Result
. Instead, race
returns a type we have not seen before,
trpl::Either
. The Either
type is somewhat like a Result
, in that it has
@@ -21111,7 +21116,7 @@
Counting
Then we write two loops within that block, each with a trpl::sleep
call in it,
which waits for half a second (500 milliseconds) before sending the next
message. We put one loop in the body of a trpl::spawn_task
and the other in a
-top-level for
loop. We also add an .await
after the sleep
calls.
for
loop. We also add an await
after the sleep
calls.
This does something similar to the thread-based implementation—including the fact that you may see the messages appear in a different order in your own terminal when you run it.
@@ -21159,7 +21164,7 @@Counting
handle.await.unwrap(); }); } -.await
with a join handle to run a task to completionawait
with a join handle to run a task to completionThis updated version runs till both loops finish.
) -like this: -match hello("async").poll() {
- Ready(_) => {
- // We’re done!
+Under the hood, when you see code which uses await
, Rust compiles that to code
+which calls poll
. If you look back at Listing 17-4, where we printed out the
+page title for a single URL once it resolved, Rust compiles it into something
+kind of (although not exactly) like this:
+match page_title(url).poll() {
+ Ready(page_title) => match page_title {
+ Some(title) => println!("The title for {url} was {title}"),
+ None => println!("{url} had no title"),
}
Pending => {
// But what goes here?
@@ -23422,18 +23422,19 @@ Future
What should we do when the Future
is still Pending
? We need some way to try
again… and again, and again, until the future is finally ready. In other words,
a loop:
-let hello_fut = hello("async");
+let mut page_title_fut = page_title(url);
loop {
- match hello_fut.poll() {
- Ready(_) => {
- break;
+ match page_title_fut.poll() {
+ Ready(value) => match page_title {
+ Some(title) => println!("The title for {url} was {title}"),
+ None => println!("{url} had no title"),
}
Pending => {
// continue
}
}
}
-If Rust compiled it to exactly that code, though, every .await
would be
+
If Rust compiled it to exactly that code, though, every await
would be
blocking—exactly the opposite of what we were going for! Instead, Rust needs
makes sure that the loop can hand off control to something which can pause work
on this future and work on other futures and check this one again later. That
@@ -23457,7 +23458,7 @@
#![allow(unused)]
fn main() {
use std::pin::Pin;
@@ -23504,31 +23504,35 @@ }
-This is the first time we have seen a method where self
has a type annotation
-like this. When we specify the type of self
like this, we are telling Rust
-what type self
must be to call this method. These kinds of type annotations
-for self
are similar to those for other function parameters, but with the
-restriction that the type annotation has to be the type on which the method is
-implemented, or a reference or smart pointer to that type, or a Pin
wrapping a
-reference to that type. We will see more on this syntax in Chapter 18. For now,
-it is enough to know that if we want to poll a future (to check whether it is
-Pending
or Ready(Output)
), we need a mutable reference to the type, which is
-wrapped in a Pin
.
+The cx
parameter and its Context
type is interesting, but is beyond the
+scope of this chapter: you generally only need to worry about it when writing a
+custom Future
implementation.
+Instead, we will focus on the type for self
. This is the first time we have
+seen a method where self
has a type annotation. A type annotation for self
+is similar to type annotations for other function parameters, with two key
+differences. First, when we specify the type of self
like this, we are telling
+Rust what type self
must be to call this method. Second, a type annotation on
+self
cannot be just any type. It is only allowed to be the type on which the
+method is implemented, or a reference or smart pointer to that type, or a Pin
+wrapping a reference to that type. We will see more on this syntax in Chapter
+18. For now, it is enough to know that if we want to poll a future (to check
+whether it is Pending
or Ready(Output)
), we need a mutable reference to the
+type, which is wrapped in a Pin
.
Pin
is a wrapper type. In some ways, it is like the Box
, Rc
, and other
smart pointer types we saw in Chapter 15, which also wrap other types. Unlike
-those, however, Pin
only works with other pointer types like reference (&
-and &mut
) and smart pointers (Box
, Rc
, and so on). To be precise, Pin
-works with types which implement the Deref
or DerefMut
traits, which we
-covered in Chapter 15. You can think of this restriction as equivalent to only
-working with pointers, though, since implementing Deref
or DerefMut
means
-your type behaves like a pointer type. Pin
is also not a pointer itself, and
-it does not have any behavior of its own like the ref counting of Rc
or Arc
.
-It is purely a tool the compiler can use to uphold the relevant guarantees, by
+those, however, Pin
only works with pointer types like references (&
and
+&mut
) and smart pointers (Box
, Rc
, and so on). To be precise, Pin
works
+with types which implement the Deref
or DerefMut
traits, which we covered in
+Chapter 15. You can think of this restriction as equivalent to only working with
+pointers, though, since implementing Deref
or DerefMut
means your type
+behaves like a pointer type. Pin
is also not a pointer itself, and it does not
+have any behavior of its own like the ref counting of Rc
or Arc
. It is
+purely a tool the compiler can use to uphold the relevant guarantees, by
wrapping pointers in the type.
-Recalling that .await
is implemented in terms of calls to poll
, this
-starts to explain the error message we saw above—but that was in terms of
-Unpin
, not Pin
. So what exactly are Pin
and Unpin
, how do they relate,
-and why does Future
need self
to be in a Pin
type to call poll
?
+Recalling that await
is implemented in terms of calls to poll
, this starts
+to explain the error message we saw above—but that was in terms of Unpin
, not
+Pin
. So what exactly are Pin
and Unpin
, how do they relate, and why does
+Future
need self
to be in a Pin
type to call poll
?
In Our First Async Program, we described how a series of await
points in a future get compiled into a state machine—and noted how the compiler
helps make sure that state machine follows all of Rust’s normal rules around
@@ -23541,30 +23545,58 @@
+Figure 17-3: A self-referential data type.
+
+
By default, though, any object which has a reference to itself is unsafe to
+move, because references always point to the actual memory address of the thing
+they refer to. If you move the data structure itself, those internal references
+will be left pointing to the old location. However, that memory location is now
+invalid. For one thing, its value will not be updated when you make changes to
+the data structure. For another—and more importantly!—the computer is now free
+to reuse that memory for other things! You could end up reading completely
+unrelated data later.
+
+In principle, the Rust compiler could try to update every reference to an object
+every time it gets moved. That would potentially be a lot of performance
+overhead, especially given there can be a whole web of references that need
+updating. On the other hand, if we could make sure the data structure in
+question does not move in memory, we do not have to update any references.
+This is exactly what Rust’s borrow checker requires: you cannot move an item
+which has any active references to it using safe code.
Pin
builds on that to give us the exact guarantee we need. When we pin a
-value by wrapping a pointer to it in Pin
, it can no longer move. Thus, if you
-have Pin<Box<SomeType>>
, you actually pin the SomeType
value, not the
-Box
pointer. In fact, the pinned box pointer can move around freely. Remember:
-we care about making sure the data ultimately being referenced stays in its
-place. If a pointer moves around, but the data it points to is in the same
-place, there is no problem.
+value by wrapping a pointer to that value in Pin
, it can no longer move. Thus,
+if you have Pin<Box<SomeType>>
, you actually pin the SomeType
value, not
+the Box
pointer. Figure 17-5 illustrates this:
+
+In fact, the Box
pointer can still move around freely. Remember: we care about
+making sure the data ultimately being referenced stays in its place. If a
+pointer moves around, but the data it points to is in the same place, as in
+Figure 17-6, there is no potential problem. (How you would do this with a Pin
+wrapping a Box
is more than we will get into in this particular discussion,
+but it would make for a good exercise! If you look at the docs for the types as
+well as the std::pin
module, you might be able to work out how you would do
+that.) The key is that the self-referential type itself cannot move, because it
+is still pinned.
+
However, most types are perfectly safe to move around, even if they happen to be
behind a Pin
pointer. We only need to think about pinning when items have
internal references. Primitive values like numbers and booleans do not have any
@@ -23572,10 +23604,10 @@
+Figure 17-7: Pinning a String, with a dotted line indicating that the String implements the `Unpin` trait, so it is not pinned.
+
+
This means that we can do things like replace one string with another at the
+exact same location in memory as in Figure 17-8. This does not violate the Pin
+contract because String
—like most other types in Rust—implements Unpin
,
+because it has no internal references that make it unsafe to move around!
+
+Now we know enough to understand the errors reported for that join_all
call
+from back in Listing 17-17. We originally tried to move the futures produced by
+an async blocks into a Vec<Box<dyn Future<Output = ()>>>
, but as we have seen,
+those futures may have internal references, so they do not implement Unpin
.
+They need to be pinned, and then we can pass the Pin
type into the Vec
,
+confident that the underlying data in the futures will not be moved.
Pin
and Unpin
are mostly important for building lower-level libraries, or
when you are building a runtime itself, rather than for day to day Rust code.
When you see them, though, now you will know what to do!
@@ -23601,9 +23648,10 @@ Asynchronous Programming in Rust book has
you covered:
@@ -23685,13 +23733,13 @@ The Stream
in traits, since the lack thereof is the reason they do not yet have this.
-->
-Note: The actual definition we will use looks slightly different than this,
-because it supports versions of Rust which did not yet support using async
-functions in traits. As a result, it looks like this:
+Note: The actual definition we used earlier in the chapter looks slightly
+different than this, because it supports versions of Rust which did not yet
+support using async functions in traits. As a result, it looks like this:
fn next(&mut self) -> Next<'_, Self> where Self: Unpin;
That Next
type is just a simple struct
which implements Future
and gives
a way to name the lifetime of the reference to self
with Next<'_, Self>
,
-so that .await
can work with this!
+so that await
can work with this!
The StreamExt
trait is also the home of all the interesting methods available
to use with streams. StreamExt
is automatically implemented for every type
@@ -23706,7 +23754,7 @@
The Stream
That’s all we’re going to cover for the lower-level details on these traits. To
wrap up, let’s consider how futures (including streams), tasks, and threads all
fit together!
-Futures, Tasks, and Threads
+Futures, Tasks, and Threads
As we saw in the previous chapter, threads provide one approach to concurrency.
We have seen another approach to concurrency in this chapter, using async with
futures and streams. You might be wondering why you would choose one or the
@@ -23857,7 +23905,6 @@
The Stream
choose between threads and async. You can use them together freely, letting each
one serve the part it is best at. For example, Listing 17-TODO shows a fairly
common example of this kind of mix in real-world Rust code.
-