What are the differences between these two function? They look the same

69 Views Asked by At

I am a newbie of Rust. There are two function here. When I compile, foo1 compiles pass and foo2 displays an error. But they only add an irrelevant parameter to foo1 compared to foo2, what is the reason for this?

fn main() {
    let a = 33;
    let x = foo1(&a);
    let y = foo2();
    println!("{x} {y}");
}

fn foo1(_a: &i32) -> &str {
    let x = "ddd";
    return x;
}

fn foo2() -> &str {
    let x = "ddd";
    return x;
}
➜  src git:(master) ✗ rustc -V     
rustc 1.73.0 (cc66ad468 2023-10-03)

➜  src git:(master) ✗ rustc main.rs
error[E0106]: missing lifetime specifier
  --> main.rs:13:14
   |
13 | fn foo2() -> &str {
   |              ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
   |
13 | fn foo2() -> &'static str {
   |               +++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
3

There are 3 best solutions below

0
prog-fh On BEST ANSWER

Because foo1() has a reference as parameter, by convenience the compiler assumes the returned reference has the same lifetime as the parameter (this is almost always correct in practice).
You can see this with these slightly modified lines:

    let x_out = {
        let a = 33;
        let x_in = foo1(&a);
        x_in
    };

Here, the compiler complains because x_out will outlive a.
Of course, looking into foo1(), we can see that the result does not reference anything related to the parameter.
The convenience is misleading here, and we should have written -> &'static str.

In foo2(), this convenience cannot even take place because there is no reference as parameter.
We have no choice but to explicitly express the lifetime -> &'static str.

0
Milos Stojanovic On

The reason foo2() displays an error while foo1() compiles successfully is because the return type of foo2() involves a borrowed reference (&str), but Rust needs to know the lifetime of that reference. In Rust, every borrowed reference must have a known lifetime, which indicates how long the borrowed value lives.

In foo1(), you are taking a reference to an i32 parameter and returning a &str. Since the parameter a has a lifetime associated with it (it's a reference), Rust can infer the lifetime of the returned &str slice. It's called lifetime elision.

However, in foo2(), you are returning a &str without any associated parameter or context. Rust cannot infer the lifetime of the returned &str because it doesn't know where it's borrowing the str slice from. Hence, it gives an error about the missing lifetime specifier.

To fix this error, you can either specify a lifetime parameter explicitly:

fn foo2<'a>() -> &'a str {
    let x = "ddd";
    return x;
}

or change the return type to &'static str, indicating that the returned string slice will have a static lifetime:

fn foo2() -> &'static str {
    let x = "ddd";
    return x;
}

By specifying 'static, you're essentially saying that the returned string slice will live for the entire duration of the program. This eliminates the need for Rust to infer the lifetime from any context.

0
ouuan On

To resolve the lifetime issue, you should follow the suggestion given in the error message to use the 'static lifetime: fn foo2() -> &'static str. This specifies that the returned &str is available during the whole lifetime of the program.

In foo1, the lifetime of the unused parameter is automatically picked for the returned &str. So fn foo1(_a: &i32) -> &str is equivalent to fn foo1<'a>(_a: &'a i32) -> &'a str. This appears to solve the compilation error but may actually cause further problems. Let's see this piece of codes:

fn main() {
    let x;
    let y;
    {
        let a = 33;
        x = foo1(&a); // error[E0597]: `a` does not live long enough
        y = foo2();
        println!("{x}");
        println!("{y}");
    } // `a` dropped here while still borrowed
    println!("{x}"); // borrow later used here
    println!("{y}");
}

fn foo1(_a: &i32) -> &str {
    let x = "ddd";
    return x;
}

fn foo2() -> &'static str {
    let x = "ddd";
    return x;
}

Here y has the 'static lifetime, while x only has the lifetime of a. a is dropped at the end of the inner code block, and Rust would think that you cannot use x outside of this block, either.

error[E0597]: `a` does not live long enough
  --> src/main.rs:6:18
   |
5  |         let a = 33;
   |             - binding `a` declared here
6  |         x = foo1(&a);
   |                  ^^ borrowed value does not live long enough
...
10 |     }
   |     - `a` dropped here while still borrowed
11 |     println!("{x}");
   |               --- borrow later used here

To learn more about lifetime, you can read the corresponding chapter in the book: Validating References with Lifetimes - The Rust Programming Language