Other than unknown size is there a reason against not using a reference as function argument?

Hello,

I am reading the book and in chapter 13 I was wondering what would happen if I could omit the
reference & of the function argument - I have written the question into the code excerpt.

use std::env;
use std::fs;

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

    let config = parse_config(&args);

    println!("Searching for {}", config.query);
    println!("In file {}", config.file_path);

    let contents = fs::read_to_string(config.file_path)
        .expect("Should have been able to read the file");

    // --snip--
}

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

// if we omit the & in &[String] how is ownership passed around? from the input to query/file_path to //Config? Would there be the need for a lifetime reference 'a for Config? But the compiler would not know
//the end of the lifetime reference right?
fn parse_config(args: &[String]) -> Config {
    let query = args[1].clone();
    let file_path = args[2].clone();

    Config { query, file_path }
}

1 Like

If you could pass a [String], the slice would own the Strings. But you can't moved unsized things like [String]s. The closest things I can think of which you can actually do are arrays or Box<[String]>.

You can't move out of arrays or Box<[_]>, but you can destructure arrays...

fn parse_config(args: [String; 3]) -> Config {
    let [_, query, file_path] = args;
    Config { query, file_path }
}

...and for Box<[_]> you could...

fn parse_config(mut args: Box<[String]>) -> Config {
    let query = std::mem::take(&mut args[1]);
    let file_path = std::mem::take(&mut args[2]);
    Config { query, file_path }
}

(This would work with Vec<String> too.)

And actually, we can modify this to show you can't move out of a slice:

fn parse_config(args: Box<[String]>) -> Config {
    if let [_, query, file_path, ..] = *args {
        Config { query, file_path }
    } else {
        panic!("...")
    }
}
error[E0508]: cannot move out of type `[String]`, a non-copy slice

No need for a lifetime if you're not borrowing anything.

3 Likes

Thanks for your explanation! :slight_smile:

Generally it's a good idea to use references for function argument types.

The exceptions are:

  • if the function is going to keep the value (e.g. it's a constructor or a setter). In such case it would need to clone the argument, because in most cases it can't keep a temporary reference. In that case you can make the argument an owning type, or a generic like impl Into<Vec<String>>.

  • if the type is an integer or a very small array or tuple of small values, or a special SIMD type. When argument types fit in registers that's usually more efficient than passing them by reference.

1 Like