给定以下函数定义:

// A: Compiles with static lifetime
fn foo() -> &'static i32 {
    let i = &42;
    i
}

// B: Compiles with normal lifetime
fn bar<'a>() -> &'a i32 {
    let i = &42;
    i
}

// C: Does not compile but same as B
fn baz<'a>() -> &'a i32 {
    let i = 42;
    &i
}

// D: Does not compile either
fn qux() -> &i32 {
    let i = &42;
    i
}

我预计这4个函数都会编译失败,因为我们返回的是对局部变量的引用,但令我惊讶的是,A和B都能正确编译,而C和D却不能.

对于A:这里怎么可能使用'static,因为静态数据lives for the lifetime of the program和该数据是在程序执行期间动态(可能多次)创建的?

对于B:为什么这是允许的,'a的生命周期是多少-它也隐含着'static吗?当更改为String等类型时,为什么这不再起作用?

对于C和D:当它们实际上与B相同时,为什么不编译它们?

这些有没有制造悬而未决的指针,或者幕后发生了什么?

它们会因为返回值永远存在而造成内存泄漏吗?

推荐答案

将 comments 中所说的所有内容集中在一个答案中:

case A

fn foo() -> &'static i32 {
    let i = &42;
    i
}

在这里,由于rvalue static promotion,42不是在堆栈上分配的,而是被视为const变量.这意味着它实际上将与编译器生成的二进制代码一起提供,指向它的指针将是一个指针,指向与程序一样长的生命值(因为它是程序的一部分).

case B

fn foo<'a>() -> &'a i32 {
    let i = &42;
    i
}

此示例与前一个示例非常相似,只是稍有修改.这一次,我们想要的不是'static岁,而是通用的'a岁.这是可行的,因为

  1. 根据'static,'static: 'a的定义.
  2. &'a T'a中是协变的.

这意味着(非常直观地)允许编译器将始终有效的生命周期"降级"为有效的when I ask it to be生命周期(因为它在某种程度上是"包含在始终"中的).有关差异的更多信息可以在rustonomicon中找到.

case C

fn foo() -> &'static i32 {
    let i = 42;
    &i
}

再说一次,稍微修改一下.或者看起来是这样.最大的不同在于,这一次我们explicitly42绑定到函数的作用域.因此,它将被分配到堆栈上,并在函数返回时被释放,从而导致编译错误.这是您提到的常规的禁止悬挂指针错误.

case D

fn foo() -> &i32 {
    let i = &42;
    i
}

这是由于一个非常不同的原因而不能编译的.事实上,您甚至可以忘记该函数的内容,它仍然无法编译:

fn foo() -> &i32 {
    panic!()
}

这是因为,in general,所有的生命周期都必须是明确的.在少数情况下,编译器有一些规则将自动分配这些生存期,因此允许它们是隐式的(请注意,这并不意味着编译器会像处理类型那样计算出"正确的"生存期;它可能会产生"错误的"生存期,这将阻止您的程序编译).确切的规则可以在reference中找到.还要注意,尽管我说过一般的生命周期必须是明确的,但事实证明,in practice为数不多的生命周期省略规则既足够,而且往往是正确的.汉迪!

Rust相关问答推荐

如何定义使用拥有的字符串并返回拥有的Split的Rust函数?

关联类型(类型参数)命名约定

为什么std repeat trait绑定在impl块和关联函数之间?

如果LET;使用布尔表达式链接(&Q);

支持TLS的模拟HTTP服务器

你是如何在铁 rust 一侧的金牛座获得应用程序版本的?

Gtk4-rs:将监视器作为gdk::monitor获取,而不是作为glib::对象获取

通过RabbitMQ取消铁 rust 中长时间运行的人造丝任务的策略

如何在Rust中基于字符串 Select struct ?

std::vector::shrink_to_fit 如何在 Rust 中工作?

Some(v) 和 Some(&v) 有什么区别?

从 Axum IntoResponse 获取请求标头

特征中定义的类型与一般定义的类型之间的区别

产生拥有值的迭代器的 Rust 可变borrow 在循环中失败

在 Rust 中如何将值推送到枚举 struct 内的 vec?

从函数返回 u32 的数组/切片

如何在 Rust 的内置函数上实现特征?

当值是新类型包装器时,对键的奇怪 HashMap 生命周期要求

在同一向量 Rust 中用另一个字符串扩展一个字符串

您不能borrow 对只读值的可变引用