为什么在 Rust 中允许返回当前函数拥有的引用?

4

我正在学习Rust的生命周期/所有权概念,并想要解释一下在Rust中(rustc 1.37.0)的以下行为。

对于这样的程序:

#[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报告:

17 |   &cheapest_book
   |   ^^^^^^^^^^^^^^ returns a reference to data owned by the current function

我能理解这个错误,它是因为变量cheapest_book拥有价格为0的书籍,而且在函数结束时会被删除,所以返回的引用将在此之后失效。但是如果我将choose_cheaper函数更改为以下内容,很难解释为什么以下内容是允许的:

fn choose_cheaper(b1: &Book) -> &Book {
    if b1.price < 15 {
        b1
    } else {
        let cheapest_book = &Book {price: 0};
        cheapest_book
    }
}

能有人为我解释一下吗?谢谢。


1个回答

8
在代码行let cheapest_book = &Book {price: 0};中,Book并不是新的Book类型实例。每次调用此函数时,它都会返回对Book类型的同一个实例的引用,该实例将存储在可执行文件的只读数据部分(或者在技术上,如果它包含CellAtomicUsize等,则存储在数据部分中)。
在这种情况下,我们可以将代码“展开”为更加明确的内容:
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
    }
}

请注意,对于GLOBAL_BOOK的引用实际上可以是一个&'static Book,但&'a Book是它的超类型,因此将静态引用返回为'a引用是可以的。
如果这看起来有点奇怪,请考虑这正是字符串字面量发生的情况;它们只是没有显式的&字符:在执行let foo = "string!";之后,foo是一个&'static str,它引用可执行文件中只读部分的一些数据,而不是本地对象。因此,您还可以编写在返回任何'a的函数中返回&'a str的语句 return "string!";
Rust会进行这种转换的规则是,每当您在&后面“构造”一个对象(使用元组语法、结构体或枚举或联合初始化语法、数字或字符串字面量,或任何组合 thereof - 不包括调用new()或任何其他函数),它们都会成为匿名静态。因此,实际上&&1_u32是一个指向静态静态引用的'static引用。

1
值得一提的是:这种行为的术语是“静态晋升”。https://github.com/rust-lang/rust/issues/38865 - Wesley Wiser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接