简体   繁体   中英

Why returning a reference owned by the current function is allowed in Rust?

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.

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM