Files
docs-rust/ch13/ch13-03-improving-our-io-project.html
2026-06-22 21:27:36 +05:30

532 lines
28 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>Improving Our I/O Project</title>
</head>
<body>
<h2 id="improving-our-io-project"><a class="header" href="#improving-our-io-project">Improving Our I/O Project</a></h2>
<p>With this new knowledge about iterators, we can improve the I/O project in
Chapter 12 by using iterators to make places in the code clearer and more
concise. Lets look at how iterators can improve our implementation of the
<code>Config::build</code> function and the <code>search</code> function.</p>
<h3 id="removing-a-clone-using-an-iterator"><a class="header" href="#removing-a-clone-using-an-iterator">Removing a <code>clone</code> Using an Iterator</a></h3>
<p>In Listing 12-6, we added code that took a slice of <code>String</code> values and created
an instance of the <code>Config</code> struct by indexing into the slice and cloning the
values, allowing the <code>Config</code> struct to own those values. In Listing 13-17,
weve reproduced the implementation of the <code>Config::build</code> function as it was
in Listing 12-23.</p>
<figure class="listing" id="listing-13-17">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore"><span class="boring">use std::env;
</span><span class="boring">use std::error::Error;
</span><span class="boring">use std::fs;
</span><span class="boring">use std::process;
</span><span class="boring">
</span><span class="boring">use minigrep::{search, search_case_insensitive};
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> let args: Vec&lt;String&gt; = env::args().collect();
</span><span class="boring">
</span><span class="boring"> let config = Config::build(&amp;args).unwrap_or_else(|err| {
</span><span class="boring"> println!("Problem parsing arguments: {err}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> });
</span><span class="boring">
</span><span class="boring"> if let Err(e) = run(config) {
</span><span class="boring"> println!("Application error: {e}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">pub struct Config {
</span><span class="boring"> pub query: String,
</span><span class="boring"> pub file_path: String,
</span><span class="boring"> pub ignore_case: bool,
</span><span class="boring">}
</span><span class="boring">
</span>impl Config {
fn build(args: &amp;[String]) -&gt; Result&lt;Config, &amp;'static str&gt; {
if args.len() &lt; 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
<span class="boring">
</span><span class="boring">fn run(config: Config) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
</span><span class="boring"> let contents = fs::read_to_string(config.file_path)?;
</span><span class="boring">
</span><span class="boring"> let results = if config.ignore_case {
</span><span class="boring"> search_case_insensitive(&amp;config.query, &amp;contents)
</span><span class="boring"> } else {
</span><span class="boring"> search(&amp;config.query, &amp;contents)
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> for line in results {
</span><span class="boring"> println!("{line}");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> Ok(())
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-13-17">Listing 13-17</a>: Reproduction of the <code>Config::build</code> function from Listing 12-23</figcaption>
</figure>
<p>At the time, we said not to worry about the inefficient <code>clone</code> calls because
we would remove them in the future. Well, that time is now!</p>
<p>We needed <code>clone</code> here because we have a slice with <code>String</code> elements in the
parameter <code>args</code>, but the <code>build</code> function doesnt own <code>args</code>. To return
ownership of a <code>Config</code> instance, we had to clone the values from the <code>query</code>
and <code>file_path</code> fields of <code>Config</code> so that the <code>Config</code> instance can own its
values.</p>
<p>With our new knowledge about iterators, we can change the <code>build</code> function to
take ownership of an iterator as its argument instead of borrowing a slice.
Well use the iterator functionality instead of the code that checks the length
of the slice and indexes into specific locations. This will clarify what the
<code>Config::build</code> function is doing because the iterator will access the values.</p>
<p>Once <code>Config::build</code> takes ownership of the iterator and stops using indexing
operations that borrow, we can move the <code>String</code> values from the iterator into
<code>Config</code> rather than calling <code>clone</code> and making a new allocation.</p>
<h4 id="using-the-returned-iterator-directly"><a class="header" href="#using-the-returned-iterator-directly">Using the Returned Iterator Directly</a></h4>
<p>Open your I/O projects <em>src/main.rs</em> file, which should look like this:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust ignore"><span class="boring">use std::env;
</span><span class="boring">use std::error::Error;
</span><span class="boring">use std::fs;
</span><span class="boring">use std::process;
</span><span class="boring">
</span><span class="boring">use minigrep::{search, search_case_insensitive};
</span><span class="boring">
</span>fn main() {
let args: Vec&lt;String&gt; = env::args().collect();
let config = Config::build(&amp;args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --snip--
<span class="boring">
</span><span class="boring"> if let Err(e) = run(config) {
</span><span class="boring"> eprintln!("Application error: {e}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> }
</span>}
<span class="boring">
</span><span class="boring">pub struct Config {
</span><span class="boring"> pub query: String,
</span><span class="boring"> pub file_path: String,
</span><span class="boring"> pub ignore_case: bool,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Config {
</span><span class="boring"> fn build(args: &amp;[String]) -&gt; Result&lt;Config, &amp;'static str&gt; {
</span><span class="boring"> if args.len() &lt; 3 {
</span><span class="boring"> return Err("not enough arguments");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> let query = args[1].clone();
</span><span class="boring"> let file_path = args[2].clone();
</span><span class="boring">
</span><span class="boring"> let ignore_case = env::var("IGNORE_CASE").is_ok();
</span><span class="boring">
</span><span class="boring"> Ok(Config {
</span><span class="boring"> query,
</span><span class="boring"> file_path,
</span><span class="boring"> ignore_case,
</span><span class="boring"> })
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">fn run(config: Config) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
</span><span class="boring"> let contents = fs::read_to_string(config.file_path)?;
</span><span class="boring">
</span><span class="boring"> let results = if config.ignore_case {
</span><span class="boring"> search_case_insensitive(&amp;config.query, &amp;contents)
</span><span class="boring"> } else {
</span><span class="boring"> search(&amp;config.query, &amp;contents)
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> for line in results {
</span><span class="boring"> println!("{line}");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> Ok(())
</span><span class="boring">}</span></code></pre>
<p>Well first change the start of the <code>main</code> function that we had in Listing
12-24 to the code in Listing 13-18, which this time uses an iterator. This
wont compile until we update <code>Config::build</code> as well.</p>
<figure class="listing" id="listing-13-18">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore does_not_compile"><span class="boring">use std::env;
</span><span class="boring">use std::error::Error;
</span><span class="boring">use std::fs;
</span><span class="boring">use std::process;
</span><span class="boring">
</span><span class="boring">use minigrep::{search, search_case_insensitive};
</span><span class="boring">
</span>fn main() {
let config = Config::build(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --snip--
<span class="boring">
</span><span class="boring"> if let Err(e) = run(config) {
</span><span class="boring"> eprintln!("Application error: {e}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> }
</span>}
<span class="boring">
</span><span class="boring">pub struct Config {
</span><span class="boring"> pub query: String,
</span><span class="boring"> pub file_path: String,
</span><span class="boring"> pub ignore_case: bool,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Config {
</span><span class="boring"> fn build(args: &amp;[String]) -&gt; Result&lt;Config, &amp;'static str&gt; {
</span><span class="boring"> if args.len() &lt; 3 {
</span><span class="boring"> return Err("not enough arguments");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> let query = args[1].clone();
</span><span class="boring"> let file_path = args[2].clone();
</span><span class="boring">
</span><span class="boring"> let ignore_case = env::var("IGNORE_CASE").is_ok();
</span><span class="boring">
</span><span class="boring"> Ok(Config {
</span><span class="boring"> query,
</span><span class="boring"> file_path,
</span><span class="boring"> ignore_case,
</span><span class="boring"> })
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">fn run(config: Config) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
</span><span class="boring"> let contents = fs::read_to_string(config.file_path)?;
</span><span class="boring">
</span><span class="boring"> let results = if config.ignore_case {
</span><span class="boring"> search_case_insensitive(&amp;config.query, &amp;contents)
</span><span class="boring"> } else {
</span><span class="boring"> search(&amp;config.query, &amp;contents)
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> for line in results {
</span><span class="boring"> println!("{line}");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> Ok(())
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-13-18">Listing 13-18</a>: Passing the return value of <code>env::args</code> to <code>Config::build</code></figcaption>
</figure>
<p>The <code>env::args</code> function returns an iterator! Rather than collecting the
iterator values into a vector and then passing a slice to <code>Config::build</code>, now
were passing ownership of the iterator returned from <code>env::args</code> to
<code>Config::build</code> directly.</p>
<p>Next, we need to update the definition of <code>Config::build</code>. Lets change the
signature of <code>Config::build</code> to look like Listing 13-19. This still wont
compile, because we need to update the function body.</p>
<figure class="listing" id="listing-13-19">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore does_not_compile"><span class="boring">use std::env;
</span><span class="boring">use std::error::Error;
</span><span class="boring">use std::fs;
</span><span class="boring">use std::process;
</span><span class="boring">
</span><span class="boring">use minigrep::{search, search_case_insensitive};
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> let config = Config::build(env::args()).unwrap_or_else(|err| {
</span><span class="boring"> eprintln!("Problem parsing arguments: {err}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> });
</span><span class="boring">
</span><span class="boring"> if let Err(e) = run(config) {
</span><span class="boring"> eprintln!("Application error: {e}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">pub struct Config {
</span><span class="boring"> pub query: String,
</span><span class="boring"> pub file_path: String,
</span><span class="boring"> pub ignore_case: bool,
</span><span class="boring">}
</span><span class="boring">
</span>impl Config {
fn build(
mut args: impl Iterator&lt;Item = String&gt;,
) -&gt; Result&lt;Config, &amp;'static str&gt; {
// --snip--
<span class="boring"> if args.len() &lt; 3 {
</span><span class="boring"> return Err("not enough arguments");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> let query = args[1].clone();
</span><span class="boring"> let file_path = args[2].clone();
</span><span class="boring">
</span><span class="boring"> let ignore_case = env::var("IGNORE_CASE").is_ok();
</span><span class="boring">
</span><span class="boring"> Ok(Config {
</span><span class="boring"> query,
</span><span class="boring"> file_path,
</span><span class="boring"> ignore_case,
</span><span class="boring"> })
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">fn run(config: Config) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
</span><span class="boring"> let contents = fs::read_to_string(config.file_path)?;
</span><span class="boring">
</span><span class="boring"> let results = if config.ignore_case {
</span><span class="boring"> search_case_insensitive(&amp;config.query, &amp;contents)
</span><span class="boring"> } else {
</span><span class="boring"> search(&amp;config.query, &amp;contents)
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> for line in results {
</span><span class="boring"> println!("{line}");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> Ok(())
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-13-19">Listing 13-19</a>: Updating the signature of <code>Config::build</code> to expect an iterator</figcaption>
</figure>
<p>The standard library documentation for the <code>env::args</code> function shows that the
type of the iterator it returns is <code>std::env::Args</code>, and that type implements
the <code>Iterator</code> trait and returns <code>String</code> values.</p>
<p>Weve updated the signature of the <code>Config::build</code> function so that the
parameter <code>args</code> has a generic type with the trait bounds <code>impl Iterator&lt;Item = String&gt;</code> instead of <code>&amp;[String]</code>. This usage of the <code>impl Trait</code> syntax we
discussed in the <a href="../ch10/ch10-02-traits.html#traits-as-parameters">“Using Traits as Parameters”</a><!-- ignore -->
section of Chapter 10 means that <code>args</code> can be any type that implements the
<code>Iterator</code> trait and returns <code>String</code> items.</p>
<p>Because were taking ownership of <code>args</code> and well be mutating <code>args</code> by
iterating over it, we can add the <code>mut</code> keyword into the specification of the
<code>args</code> parameter to make it mutable.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="using-iterator-trait-methods-instead-of-indexing"></a></p>
<h4 id="using-iterator-trait-methods"><a class="header" href="#using-iterator-trait-methods">Using <code>Iterator</code> Trait Methods</a></h4>
<p>Next, well fix the body of <code>Config::build</code>. Because <code>args</code> implements the
<code>Iterator</code> trait, we know we can call the <code>next</code> method on it! Listing 13-20
updates the code from Listing 12-23 to use the <code>next</code> method.</p>
<figure class="listing" id="listing-13-20">
<span class="file-name">Filename: src/main.rs</span>
<pre><code class="language-rust ignore noplayground"><span class="boring">use std::env;
</span><span class="boring">use std::error::Error;
</span><span class="boring">use std::fs;
</span><span class="boring">use std::process;
</span><span class="boring">
</span><span class="boring">use minigrep::{search, search_case_insensitive};
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> let config = Config::build(env::args()).unwrap_or_else(|err| {
</span><span class="boring"> eprintln!("Problem parsing arguments: {err}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> });
</span><span class="boring">
</span><span class="boring"> if let Err(e) = run(config) {
</span><span class="boring"> eprintln!("Application error: {e}");
</span><span class="boring"> process::exit(1);
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">pub struct Config {
</span><span class="boring"> pub query: String,
</span><span class="boring"> pub file_path: String,
</span><span class="boring"> pub ignore_case: bool,
</span><span class="boring">}
</span><span class="boring">
</span>impl Config {
fn build(
mut args: impl Iterator&lt;Item = String&gt;,
) -&gt; Result&lt;Config, &amp;'static str&gt; {
args.next();
let query = match args.next() {
Some(arg) =&gt; arg,
None =&gt; return Err("Didn't get a query string"),
};
let file_path = match args.next() {
Some(arg) =&gt; arg,
None =&gt; return Err("Didn't get a file path"),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
<span class="boring">
</span><span class="boring">fn run(config: Config) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
</span><span class="boring"> let contents = fs::read_to_string(config.file_path)?;
</span><span class="boring">
</span><span class="boring"> let results = if config.ignore_case {
</span><span class="boring"> search_case_insensitive(&amp;config.query, &amp;contents)
</span><span class="boring"> } else {
</span><span class="boring"> search(&amp;config.query, &amp;contents)
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> for line in results {
</span><span class="boring"> println!("{line}");
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> Ok(())
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-13-20">Listing 13-20</a>: Changing the body of <code>Config::build</code> to use iterator methods</figcaption>
</figure>
<p>Remember that the first value in the return value of <code>env::args</code> is the name of
the program. We want to ignore that and get to the next value, so first we call
<code>next</code> and do nothing with the return value. Then, we call <code>next</code> to get the
value we want to put in the <code>query</code> field of <code>Config</code>. If <code>next</code> returns
<code>Some</code>, we use a <code>match</code> to extract the value. If it returns <code>None</code>, it means
not enough arguments were given, and we return early with an <code>Err</code> value. We do
the same thing for the <code>file_path</code> value.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="making-code-clearer-with-iterator-adapters"></a></p>
<h3 id="clarifying-code-with-iterator-adapters"><a class="header" href="#clarifying-code-with-iterator-adapters">Clarifying Code with Iterator Adapters</a></h3>
<p>We can also take advantage of iterators in the <code>search</code> function in our I/O
project, which is reproduced here in Listing 13-21 as it was in Listing 12-19.</p>
<figure class="listing" id="listing-13-21">
<span class="file-name">Filename: src/lib.rs</span>
<pre><code class="language-rust ignore">pub fn search&lt;'a&gt;(query: &amp;str, contents: &amp;'a str) -&gt; Vec&lt;&amp;'a str&gt; {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
<span class="boring">
</span><span class="boring">#[cfg(test)]
</span><span class="boring">mod tests {
</span><span class="boring"> use super::*;
</span><span class="boring">
</span><span class="boring"> #[test]
</span><span class="boring"> fn one_result() {
</span><span class="boring"> let query = "duct";
</span><span class="boring"> let contents = "\
</span><span class="boring">Rust:
</span><span class="boring">safe, fast, productive.
</span><span class="boring">Pick three.";
</span><span class="boring">
</span><span class="boring"> assert_eq!(vec!["safe, fast, productive."], search(query, contents));
</span><span class="boring"> }
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-13-21">Listing 13-21</a>: The implementation of the <code>search</code> function from Listing 12-19</figcaption>
</figure>
<p>We can write this code in a more concise way using iterator adapter methods.
Doing so also lets us avoid having a mutable intermediate <code>results</code> vector. The
functional programming style prefers to minimize the amount of mutable state to
make code clearer. Removing the mutable state might enable a future enhancement
to make searching happen in parallel because we wouldnt have to manage
concurrent access to the <code>results</code> vector. Listing 13-22 shows this change.</p>
<figure class="listing" id="listing-13-22">
<span class="file-name">Filename: src/lib.rs</span>
<pre><code class="language-rust ignore">pub fn search&lt;'a&gt;(query: &amp;str, contents: &amp;'a str) -&gt; Vec&lt;&amp;'a str&gt; {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
<span class="boring">
</span><span class="boring">pub fn search_case_insensitive&lt;'a&gt;(
</span><span class="boring"> query: &amp;str,
</span><span class="boring"> contents: &amp;'a str,
</span><span class="boring">) -&gt; Vec&lt;&amp;'a str&gt; {
</span><span class="boring"> let query = query.to_lowercase();
</span><span class="boring"> let mut results = Vec::new();
</span><span class="boring">
</span><span class="boring"> for line in contents.lines() {
</span><span class="boring"> if line.to_lowercase().contains(&amp;query) {
</span><span class="boring"> results.push(line);
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> results
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">#[cfg(test)]
</span><span class="boring">mod tests {
</span><span class="boring"> use super::*;
</span><span class="boring">
</span><span class="boring"> #[test]
</span><span class="boring"> fn case_sensitive() {
</span><span class="boring"> let query = "duct";
</span><span class="boring"> let contents = "\
</span><span class="boring">Rust:
</span><span class="boring">safe, fast, productive.
</span><span class="boring">Pick three.
</span><span class="boring">Duct tape.";
</span><span class="boring">
</span><span class="boring"> assert_eq!(vec!["safe, fast, productive."], search(query, contents));
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> #[test]
</span><span class="boring"> fn case_insensitive() {
</span><span class="boring"> let query = "rUsT";
</span><span class="boring"> let contents = "\
</span><span class="boring">Rust:
</span><span class="boring">safe, fast, productive.
</span><span class="boring">Pick three.
</span><span class="boring">Trust me.";
</span><span class="boring">
</span><span class="boring"> assert_eq!(
</span><span class="boring"> vec!["Rust:", "Trust me."],
</span><span class="boring"> search_case_insensitive(query, contents)
</span><span class="boring"> );
</span><span class="boring"> }
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-13-22">Listing 13-22</a>: Using iterator adapter methods in the implementation of the <code>search</code> function</figcaption>
</figure>
<p>Recall that the purpose of the <code>search</code> function is to return all lines in
<code>contents</code> that contain the <code>query</code>. Similar to the <code>filter</code> example in Listing
13-16, this code uses the <code>filter</code> adapter to keep only the lines for which
<code>line.contains(query)</code> returns <code>true</code>. We then collect the matching lines into
another vector with <code>collect</code>. Much simpler! Feel free to make the same change
to use iterator methods in the <code>search_case_insensitive</code> function as well.</p>
<p>For a further improvement, return an iterator from the <code>search</code> function by
removing the call to <code>collect</code> and changing the return type to <code>impl Iterator&lt;Item = &amp;'a str&gt;</code> so that the function becomes an iterator adapter.
Note that youll also need to update the tests! Search through a large file
using your <code>minigrep</code> tool before and after making this change to observe the
difference in behavior. Before this change, the program wont print any results
until it has collected all of the results, but after the change, the results
will be printed as each matching line is found because the <code>for</code> loop in the
<code>run</code> function is able to take advantage of the laziness of the iterator.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="choosing-between-loops-or-iterators"></a></p>
<h3 id="choosing-between-loops-and-iterators"><a class="header" href="#choosing-between-loops-and-iterators">Choosing Between Loops and Iterators</a></h3>
<p>The next logical question is which style you should choose in your own code and
why: the original implementation in Listing 13-21 or the version using
iterators in Listing 13-22 (assuming were collecting all the results before
returning them rather than returning the iterator). Most Rust programmers
prefer to use the iterator style. Its a bit tougher to get the hang of at
first, but once you get a feel for the various iterator adapters and what they
do, iterators can be easier to understand. Instead of fiddling with the various
bits of looping and building new vectors, the code focuses on the high-level
objective of the loop. This abstracts away some of the commonplace code so that
its easier to see the concepts that are unique to this code, such as the
filtering condition each element in the iterator must pass.</p>
<p>But are the two implementations truly equivalent? The intuitive assumption
might be that the lower-level loop will be faster. Lets talk about performance.</p>
</body>
</html>