Files
docs-rust/ch17/ch17-04-streams.html
2026-06-22 21:27:36 +05:30

126 lines
7.0 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Streams: Futures in Sequence</title>
</head>
<body>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="streams"></a></p>
<h2 id="streams-futures-in-sequence"><a class="header" href="#streams-futures-in-sequence">Streams: Futures in Sequence</a></h2>
<p>Recall how we used the receiver for our async channel earlier in this chapter
in the <a href="ch17-02-concurrency-with-async.html#message-passing">“Message Passing”</a><!-- ignore --> section. The async
<code>recv</code> method produces a sequence of items over time. This is an instance of a
much more general pattern known as a <em>stream</em>. Many concepts are naturally
represented as streams: items becoming available in a queue, chunks of data
being pulled incrementally from the filesystem when the full data set is too
large for the computers memory, or data arriving over the network over time.
Because streams are futures, we can use them with any other kind of future and
combine them in interesting ways. For example, we can batch up events to avoid
triggering too many network calls, set timeouts on sequences of long-running
operations, or throttle user interface events to avoid doing needless work.</p>
<p>We saw a sequence of items back in Chapter 13, when we looked at the Iterator
trait in <a href="../ch13/ch13-02-iterators.html#the-iterator-trait-and-the-next-method">“The Iterator Trait and the <code>next</code> Method”</a><!--
ignore --> section, but there are two differences between iterators and the
async channel receiver. The first difference is time: iterators are
synchronous, while the channel receiver is asynchronous. The second difference
is the API. When working directly with <code>Iterator</code>, we call its synchronous
<code>next</code> method. With the <code>trpl::Receiver</code> stream in particular, we called an
asynchronous <code>recv</code> method instead. Otherwise, these APIs feel very similar,
and that similarity isnt a coincidence. A stream is like an asynchronous form
of iteration. Whereas the <code>trpl::Receiver</code> specifically waits to receive
messages, though, the general-purpose stream API is much broader: it provides
the next item the way <code>Iterator</code> does, but asynchronously.</p>
<p>The similarity between iterators and streams in Rust means we can actually
create a stream from any iterator. As with an iterator, we can work with a
stream by calling its <code>next</code> method and then awaiting the output, as in Listing
17-21, which wont compile yet.</p>
<figure class="listing" id="listing-17-21">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore does_not_compile"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let iter = values.iter().map(|n| n * 2);
let mut stream = trpl::stream_from_iter(iter);
while let Some(value) = stream.next().await {
println!("The value was: {value}");
}
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-17-21">Listing 17-21</a>: Creating a stream from an iterator and printing its values</figcaption>
</figure>
<p>We start with an array of numbers, which we convert to an iterator and then
call <code>map</code> on to double all the values. Then we convert the iterator into a
stream using the <code>trpl::stream_from_iter</code> function. Next, we loop over the
items in the stream as they arrive with the <code>while let</code> loop.</p>
<p>Unfortunately, when we try to run the code, it doesnt compile but instead
reports that theres no <code>next</code> method available:</p>
<!-- manual-regeneration
cd listings/ch17-async-await/listing-17-21
cargo build
copy only the error output
-->
<pre><code class="language-text">error[E0599]: no method named `next` found for struct `tokio_stream::iter::Iter` in the current scope
--&gt; src/main.rs:10:40
|
10 | while let Some(value) = stream.next().await {
| ^^^^
|
= help: items from traits can only be used if the trait is in scope
help: the following traits which provide `next` are implemented but not in scope; perhaps you want to import one of them
|
1 + use crate::trpl::StreamExt;
|
1 + use futures_util::stream::stream::StreamExt;
|
1 + use std::iter::Iterator;
|
1 + use std::str::pattern::Searcher;
|
help: there is a method `try_next` with a similar name
|
10 | while let Some(value) = stream.try_next().await {
| ~~~~~~~~
</code></pre>
<p>As this output explains, the reason for the compiler error is that we need the
right trait in scope to be able to use the <code>next</code> method. Given our discussion
so far, you might reasonably expect that trait to be <code>Stream</code>, but its
actually <code>StreamExt</code>. Short for <em>extension</em>, <code>Ext</code> is a common pattern in the
Rust community for extending one trait with another.</p>
<p>The <code>Stream</code> trait defines a low-level interface that effectively combines the
<code>Iterator</code> and <code>Future</code> traits. <code>StreamExt</code> supplies a higher-level set of APIs
on top of <code>Stream</code>, including the <code>next</code> method as well as other utility
methods similar to those provided by the <code>Iterator</code> trait. <code>Stream</code> and
<code>StreamExt</code> are not yet part of Rusts standard library, but most ecosystem
crates use similar definitions.</p>
<p>The fix to the compiler error is to add a <code>use</code> statement for
<code>trpl::StreamExt</code>, as in Listing 17-22.</p>
<figure class="listing" id="listing-17-22">
<span class="file-name">Filename: src/main.rs</span>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span>use trpl::StreamExt;
fn main() {
trpl::block_on(async {
let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// --snip--
<span class="boring"> let iter = values.iter().map(|n| n * 2);
</span><span class="boring"> let mut stream = trpl::stream_from_iter(iter);
</span><span class="boring">
</span><span class="boring"> while let Some(value) = stream.next().await {
</span><span class="boring"> println!("The value was: {value}");
</span><span class="boring"> }
</span><span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-17-22">Listing 17-22</a>: Successfully using an iterator as the basis for a stream</figcaption>
</figure>
<p>With all those pieces put together, this code works the way we want! Whats
more, now that we have <code>StreamExt</code> in scope, we can use all of its utility
methods, just as with iterators.</p>
</body>
</html>