Reputation: 9591
I am learning Rust's lifetime/ownership concepts, and would like to explain the following behavior in Rust (rustc 1.37.0).
For a program like this:
#[derive(Debug)]
struct Book {
price: i32,
}
fn main() {
let book1 = Book {price: 12};
let cheaper_book = choose_cheaper(&book1);
println!("{:?}", cheaper_book);
}
fn choose_cheaper(b1: &Book) -> &Book {
if b1.price < 15 {
b1
} else {
let cheapest_book = Book {price: 0};
&cheapest_book
}
}
Rust reports:
17 | &cheapest_book
| ^^^^^^^^^^^^^^ returns a reference to data owned by the current function
And I can understand this error and it is because variable cheapest_book
is the owner of the Book with price 0, and it will be dropped at the end of this function, so the returned reference will become invalid after that. But it is hard for me to explain why the following is allowed if I change the choose_cheaper
function to be:
fn choose_cheaper(b1: &Book) -> &Book {
if b1.price < 15 {
b1
} else {
let cheapest_book = &Book {price: 0};
cheapest_book
}
}
Could some one shed me some light on it? Thanks.
Upvotes: 4
Views: 2185
Reputation: 442
In the line let cheapest_book = &Book {price: 0};
, the Book
is not a "new" instance of the Book
type. Every time this function is called it will return a reference to the same instance of the Book
type, which will be stored in the read-only data section of the executable (or, technically, the data section if it contains a Cell
or AtomicUsize
or the like).
We can in this instance "expand" the code into something a little more explicit:
static GLOBAL_BOOK: Book = Book { price: 0 };
fn choose_cheaper<'a>(b1: &'a Book) -> &'a Book {
if b1.price < 15 {
b1
} else {
let cheapest_book = &GLOBAL_BOOK;
cheapest_book
}
}
Note that the reference to GLOBAL_BOOK
could actually be a &'static Book
, but &'a Book
is a supertype of that so it's okay to return the static reference as if it were an 'a
reference.
If this seems weird, consider that this is exactly what happens with string literals; they just don't have the explicit &
character: After let foo = "string!";
, foo
is a &'static str
referencing some data in the read-only section of the executable, not a local object. So you can also write return "string!";
in functions returning &'a str
for any 'a
.
The rule for whether rust will make this transformation is whenever you "construct" an object (using tuple syntax, or struct or enum or union initialization syntax, or numeric or string literals, or any combinations thereof - not function calls to new()
or any other function) behind a &
, they'll become an anonymous static. So in fact &&1_u32
is a 'static
reference to a static 'static
reference to a static u32
.
Upvotes: 8