Why cannot I use reference & here?

This code compiles without problem:

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

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

fn parse_config(args: &[String]) -> Config {
    let query = args[1].clone();// why cannot I use &args[1].clone()
    let filename = args[2].clone();// why cannot I use &args[2].clone()

    Config {query, filename}
}

but if I change the two lines with comments in parse_confi() into:

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

I will get compile error:

error[E0308]: mismatched types
  --> src/main.rs:35:13
   |
35 |     Config {query, filename}
   |             ^^^^^ expected struct `String`, found `&String`
   |
help: try using a conversion method
   |
35 |     Config {query: query.to_string(), filename}
   |             ++++++      ++++++++++++

error[E0308]: mismatched types
  --> src/main.rs:35:20
   |
35 |     Config {query, filename}
   |                    ^^^^^^^^ expected struct `String`, found `&String`
   |
help: try using a conversion method
   |
35 |     Config {query, filename: filename.to_string()}
   |                    +++++++++         ++++++++++++

I just don't understand why I cannot add that reference there. args is a borrow, I just keep borrowing it. And how could String be different from &String?

I have encountered this similar problem many times, every time, I just easily follow the instruction of compiler and correct them. But I really don't understand why I cannot do that!

Thanks.

You need to understand that a reference to a String is not the same thing as a String.

One is a pointer, one is the "actual thing itself", the value.

1 Like

The operator precedence here may not be what you expect. This is the same as:

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

Each of these lines makes a temporary String that will be dropped at the end of parse_config and stores a reference to the temporary in the variable; this causes two problems with the rest of your code:

  1. Config is defined to hold Strings, and not &Strings, so there is a type mismatch.
  2. Even if you redefined Config to hold &Strings, the references it contains would become invalid when the temporaries are dropped. The compiler will therefore prevent you from returning this modified Config object.
7 Likes

Zooming out a bit, is there a reason you aren't using clap for this? It's a great facility. And if for some reason clap isn't appropriate, check out Itertools::collect_tuple.

use itertools::Itertools;

#[derive(Debug)]
struct Config {
    query: String,
    filename: String,
}

fn main() {
    let config = std::env::args()
        .skip(1)
        .collect_tuple()
        .map(|(query, filename)| Config { query, filename })
        .expect("usage: prog query filename");
    dbg!(config);
}
2 Likes