[resolved] Confused about rust book's chapter 13.3: clone vs iterator

Here's the link to the chapter: Improving Our I/O Project - The Rust Programming Language

The text mentions it would remove the .clone calls because they are inefficient:

we said not to worry about the inefficient clone calls because we would remove them in the future

Here's the original code:

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let filename = args[2].clone();

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config { query, filename, case_sensitive })
    }
}

Here's the new code using iterators:

impl Config {
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config { query, filename, case_sensitive })
    }
}

I don't get why the second version is more efficient. Isn't it just doing a clone under the hood?

1 Like

A bit further down, they explain what they mean (emphasis mine):

Once Config::new takes ownership of the iterator and stops using indexing operations that borrow, we can move the String values from the iterator into Config rather than calling clone and making a new allocation.

In the first example, you index into the args slice, which returns a reference to a String. Since you want to own the String for your struct, you have to clone it.

In the second example, Args is an Iterator which yields the type String - note that it doesn't yield a reference. This is the key part! When you call next on the Args iterator, it yields the original String that was allocated for the argument, giving you full ownership of it. You can't retrieve it from that instance of Args again, as it's been moved out into your function, and after you call next enough times it'll start returning None to denote that it's empty.

This is why the second example is more efficient - you reuse the strings that were allocated when you called std::env::args().

2 Likes

Ohh, that makes sense. It was too subtle for me to notice.
Guess it's still not clear to me when a move happens.

Thanks for the help @17cupsofcoffee !

2 Likes