7

I have a fairly simple program:

fn f<'a>() -> &'a i32 {
    &1
}

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

It doesn't compile (some of the output elided):

$ rustc test.rs
test.rs:2:6: 2:7 error: borrowed value does not live long enough
test.rs:2     &1

I understand why it fails.

  1. I don't know how to return a reference created inside the function scope. Is there way to do that?
  2. Why can't the lifetime be elided for a single return?

EDIT: I changed the title since it suggested returning boxed type would help which is not (see answers).

kopiczko
  • 3,018
  • 1
  • 17
  • 24
  • 1
    *I understand why it fails* — I don't think that's completely true :-) If you did, you should also understand why you cannot do this. As a thought experiment, what would happen if you called `f` with `'a` parameterized with `'static`? – Shepmaster Sep 17 '16 at 19:46

3 Answers3

3

As of Rust 1.21, a new feature named rvalue static promotion means that the code in the question does now compile.

In this instance because 1 is a constant, the compiler promotes it to a static meaning that the returned reference has the 'static lifetime. The function de-sugared looks something like:

fn f<'a>() -> &'a i32 {
    static ONE: i32 = 1;
    &ONE
}

This works for any compile-time constant, including structs:

struct Foo<'a> {
    x: i32,
    y: i32,
    p: Option<&'a Foo<'a>>
}

fn default_foo<'a>() -> &'a Foo<'a> {
    &Foo { x: 12, y: 90, p: None }
}

But this will not compile:

fn bad_foo<'a>(x: i32) -> &'a Foo<'a> {
    /* Doesn't compile as x isn't constant! */
    &Foo { x, y: 90, p: None }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
peterdn
  • 2,386
  • 1
  • 23
  • 24
2

Since Rust uses RAII style resource management, as soon as the program leaves a scope, all values within that scope which did not move will get destroyed. The value has to live somewhere for a reference to be valid. Therefore either return the value as such (if you are worried about having an additional copy when you do this, then don't worry since that copy will get optimized away) or box it and return the box. Unless you are returning a statically allocated string as &str as follows, you simply cannot return a "new" (for the caller) reference:

fn f<'a>() -> &'a str {
    "yo"
}
John
  • 1,856
  • 2
  • 22
  • 33
  • Is returning static value or making it a method returning the field, the only way to make the f valid? Or is there another usecase for defining function like `fn f<'a>() -> &'a T` ? Is the copy optimization documented, how can I be sure it will be preformed? – kopiczko Sep 18 '16 at 09:12
  • @kopiczko There is one really obscure case where `fn foo<'a>() -> &'a T` is more flexible than `&'static` but it's not related to what it can return. As for copies: Copying an `i32` *is as cheap or cheaper* as copying a `&i32`! In fact, most types will be so small that it's usually not worth your time to worry at all about how often it's copied. –  Sep 18 '16 at 09:55
  • @delnan I agree worrying about copying i32 is non-sense. But say I have a struct with 20 fields and I create it 1M times in a loop depending on a condition. I'd like to extract create function to keep the code DRY. Then it makes sense to worry about copying, no? – kopiczko Sep 18 '16 at 11:44
  • It might make sense, yes. (However, one copy of let's say 20*8 = 160 bytes it still most likely dirt cheap compared to whatever work you're actually doing.) Between RVO, inlining, and other optimizations -- none of which are guaranteed, by virtue of being optimization, but definitely common -- the one theoretical copy from the function return almost certainly won't actually be executed. And you shouldn't worry anyway before having hard data!!! –  Sep 18 '16 at 11:59
  • @kopiczko A function which "creates an object and returns it by value" is an extensively used pattern. So it is practically guaranteed to get optimized (in release builds). – John Sep 18 '16 at 12:30
  • 160B * 1M it's actually 160MB. But I don't care about copying I'd like to understand how to use the language. What I understand is the construct `fn f<'a>() -> &'a i32` is allowed, but it has very limited (none in practice?) purpose. Please correct if I'm wrong. And thank you for an explanation. – kopiczko Sep 18 '16 at 15:04
  • @kopiczko Yes, you could say that... Lifetimes are a way to track validity of objects with respect to other objects/references (or their own scope). In a function such as `fn f<'a>() -> &'a i32`, there is no "other" object/reference with respect to which the returned lifetime can be pinned. Therefore it is assumed to be "unbounded" by the compiler which is why only references to static values can be returned (or something which looks like that to the compiler by using `unsafe` operations). – John Sep 18 '16 at 15:18
2

Boxing the reference will not help. Box<T> is virtually identical to an unboxed T in most respects, including ownership and lifetime issues. The fundamental issue is that local variables will stop existing as soon as the function returns. Thus, a reference to a local variable will point to deallocated memory by the time the calling function gets its hand on that reference. Putting wrapping paper around the reference doesn't fix that problem.

I assume this is a simplified example distilled from a real program you're having trouble with. I can't give targeted advice for that for lack of information, but generally it is a very good idea to return things by value (i.e., just -> i32 in this case) instead of a reference.