Does `next ()` move or clone elements?

I am reading Chapter 13 of the Rust book . It says strings are clone

less efficient than accessing them through an iterator (i.e. next()

). Comparing the following examples, I have two questions:

  • Does it move the string args.next()

    or clone the string to query

    and filename

    ?
  • If it were a move, it would transfer ownership from something to env::args()

    to query

    , wouldn't this code break other code? If it was a clone, why might it be more efficient than cloning the string directly?

Definition:

struct Config {
    query: String,
    filename: String,
}

      

Ineffective version

fn main() {
    let args: Vec<String> = env::args().collect();  
    let config = Config::new(&args)
}

impl Config {
    fn new(args: &[String]) -> Result<Config, &'static str> {
        // [...]
        let query = args[1].clone();
        let filename = args[2].clone();
        // [...]
    }
}

      

Improved version

fn main() {
    let config = Config::new(env::args())
}

impl Config {
    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"),
        };
        // [...]
    }
}

      

+3


source to share


1 answer


Moving or cloning args.next()

Start by looking at the function signature for Iterator::next

:

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

      

next

transfers ownership of any type Self::Item

to the caller. It has no additional restrictions on Self

, but it can change the internal properties of the iterator.

Next, check the inputs and outputs of a particular iterator. For example, it always returns strings, but has no input values:

struct Greet;

impl Iterator for Greet {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        Some(String::from("hello"))
    }
}

      

In this case, determines how , so each value from the call is . Args

Item

String

next

Option<String>

We know what String

needs distribution. However, since we cannot provide any arguments for env::args()

in order to get the selection, there are only two possibilities:

  • The iterator allocates a value.
  • Some global state changes behind the scenes.

Rust is generally not global state, so anything that actually changes the global state will actually be propagated (printed to stdout) or marked with Big Warning.

Checking the documentation, we don't see that much warning text, so it's safe to assume that the iterator is highlighting.



You can check this by repeating it twice; you will see duplicate values. The argument list is secretly mutating under you.


Although this iterator selects strings, it is even more efficient to use the iterator value directly. When you collect into a vector, you are allocating memory for the vector. Then you clone the value inside the vector again to use it. Both of these selections are not required.

The mid-term version would be to use references to elements in the vector, namely &str

:

let query = &args[1];
let filename = &args[2];

      

This still has the "overhead" of allocating the vector, which may or may not be needed outside of this function.


I like to be too quirky, so I can write something like this:

fn main() {
    let config = Config::new(std::env::args().skip(1));
}

impl Config {
    fn new<I, S>(args: I) -> Result<Config, &'static str> 
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        let mut args = args.into_iter();

        let query = args.next().ok_or("Didn't get a query string")?;
        let filename = args.next().ok_or("Didn't get a file name")?;

        unimplemented!()
    }
}

      

ok_or

usually useful because it makes the iterator type generic and omits the program name outside of it Config::new

. This allows testing Config

without the actual argument string.

Into<String>

just demonstrates.

+3


source







All Articles