0

I am trying to make a simple function to return the command line arguments. Code looks like this:

use std::env;

fn main() {
    let (query, filename) = parse();
}

fn parse() -> (&str, &str) {
    let args: Vec<String> = env::args().collect();
    let query = args[1];
    let filename = args[2];

    return (query, filename)
}

However it will not compile, the errors look like this:

error[E0106]: missing lifetime specifier
  --> src/main.rs:15:22
   |
15 | fn parse() -> (&str, &str) {
   |                      ^ help: consider giving it a 'static lifetime: `&'static`
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

It suggests that I need to add &'static to the function declaration, like this:

fn parse() -> (&'static str, &'static str) {

But that does not work either;

error[E0308]: mismatched types
  --> src/main.rs:20:13
   |
20 |     return (query, filename)
   |             ^^^^^
   |             |
   |             expected reference, found struct `std::string::String`
   |             help: consider borrowing here: `&query`
   |
   = note: expected type `&'static str`
              found type `std::string::String`

error[E0308]: mismatched types
  --> src/main.rs:20:20
   |
20 |     return (query, filename)
   |                    ^^^^^^^^
   |                    |
   |                    expected reference, found struct `std::string::String`
   |                    help: consider borrowing here: `&filename`
   |
   = note: expected type `&'static str`
              found type `std::string::String`

It says I need to add borrowing, like this:

return (&query, &filename)

But that does not work either;

warning: unused variable: `query`
 --> src/main.rs:5:10
  |
5 |     let (query, filename) = parse();
  |          ^^^^^ help: consider prefixing with an underscore: `_query`
  |
  = note: #[warn(unused_variables)] on by default

warning: unused variable: `filename`
 --> src/main.rs:5:17
  |
5 |     let (query, filename) = parse();
  |                 ^^^^^^^^ help: consider prefixing with an underscore: `_filename`

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:17:17
   |
17 |     let query = args[1];
   |                 ^^^^^^^
   |                 |
   |                 cannot move out of borrowed content
   |                 help: consider borrowing here: `&args[1]`

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:18:20
   |
18 |     let filename = args[2];
   |                    ^^^^^^^
   |                    |
   |                    cannot move out of borrowed content
   |                    help: consider borrowing here: `&args[2]`

error[E0515]: cannot return value referencing local variable `filename`
  --> src/main.rs:20:12
   |
20 |     return (&query, &filename)
   |            ^^^^^^^^^---------^
   |            |        |
   |            |        `filename` is borrowed here
   |            returns a value referencing data owned by the current function

error[E0515]: cannot return value referencing local variable `query`
  --> src/main.rs:20:12
   |
20 |     return (&query, &filename)
   |            ^------^^^^^^^^^^^^
   |            ||
   |            |`query` is borrowed here
   |            returns a value referencing data owned by the current function

No clue what is going on or why it does not work, I copied the example straight from the tutorial as well.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user5359531
  • 3,217
  • 6
  • 30
  • 55
  • [The duplicate applied to your situation](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=470002f8bde074ada9709524a83f0ea8), but I [wouldn't use `collect` at all](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=617ccdade56b3dd6469f9881d044d203) – Shepmaster Aug 01 '19 at 03:26
  • did not realize that this had anything to do with the strings – user5359531 Aug 01 '19 at 18:48

1 Answers1

2

Strings are tricky in rust. First, let's review "What is Ownership?", "The Slice Type", and "Lifetime Syntax" from the Rust Book.

The problem is that an &str is a borrow (indicated by the &) of an object. The rust compiler needs to know how long it will be borrowed for. Now, rust will actually attempt "lifetime elision", by assuming that the lifetime of the return value will match the lifetime of an input value, but in this case there are no input values, so it can't infer. It needs you to annotate the return type with an lifetime. You can use 'static, but you can also give your function a generic lifetime specifier. Here's a simplified example:

Rust Playground

fn main() {
    println!("{}", gimme_str());
}

fn gimme_str() -> &str {
    return "foo";
}

That won't compile, either. But all you need to do to get it to compile is to add a lifetime, like so:

with Lifetime Specifier

fn main() {
    println!("{}", gimme_str());
}

fn gimme_str<'a>() -> &'a str {
    return "foo";
}

Alternatively, you could let the lifetime be inferred from the input, although in this case that's pretty weird since the input is unused:

with Lifetime Elision (inferred from input)

fn main() {
    println!("{}", gimme_str("bar"));
}

fn gimme_str(_input: &str) -> &str {
    return "foo";
}

So that's the lifetime specifier. But you have another issue, which is that your return values are slices of strings that are owned by the function. We can demonstrate the error in simplified form by modifying the previous rust playground code (the one with lifetime specifiers):

can't borrow local variables

fn main() {
    println!("{}", gimme_str());
}

fn gimme_str<'a>() -> &'a str {
    let foo = String::from("foo");
    return &foo;
}

A borrow is always a borrow of something. In the previous example code, where I used a string literal, the &str is a borrow of the immutable string literal. That's a part of the rust binary the compiler generates, and the &str can just point to that part of the binary. You can think of the underlying memory as being "owned" by the binary itself, meaning that it never goes out of scope (i.e., the lifetime of a string literal is 'static).

However, in the modified example, &foo is a borrow not of a string literal, but of a String object. That String object is assigned to a local variable, so its owner is the owner of that variable -- the function. That means that it will go out of scope and be dropped when the function exits. But once the String object no longer exists, it's not valid to borrow it.

In order for the String to live beyond the scope of the function, we have to return the String itself, so that ownership is transferred to the caller. We can't just return a borrow. See example code:

transferring ownership from a function

fn main() {
    println!("{}", gimme_str());
}

fn gimme_str() -> String {
    let foo = String::from("foo");
    return foo;
}

To apply that to your case, you're going to have to return String from your function, not &str.

gregates
  • 6,607
  • 1
  • 31
  • 31
  • Thanks. In the line in `parse`, `let query = args[1];`, does this mean that `query` is a reference? – user5359531 Aug 01 '19 at 18:27
  • 1
    What shows that `query` is a reference is the doc for [std::ops::Index](https://doc.rust-lang.org/std/ops/trait.Index.html#tymethod.index), which is the trait that allows you to write `args[1]`. The return type for the `index` method (`[index]` is syntactic sugar) is `&Self::Output`, which is indeed a reference. – gregates Aug 01 '19 at 22:53