【问题标题】:Why calling iter on array of floats gives "temporary value is freed" when using f64::from_bits?为什么在使用 f64::from_bits 时在浮点数组上调用 iter 会给出“临时值被释放”?
【发布时间】:2020-08-17 07:41:43
【问题描述】:

我遇到了一个生命周期错误,我无法解释为什么它是由编译器发出的。我需要这个(效果很好):

fn iter<'a>() -> impl Iterator<Item = &'a f64> {
    [3.14].iter()
}

但是,当我尝试使用使用 from_bits 从特定字节表示转换而来的浮点值时,如下所示:

fn iter<'a>() -> impl Iterator<Item = &'a f64> {
    [f64::from_bits(0x7fffffffffffffff)].iter()
}

它给了我“创建一个临时的,在使用中被释放”。游乐场here(稳定版 1.45.2)。

我的理由是,由于f64Copy 类型(如果我使用常量值,它确实可以按预期工作),这应该可以工作,因为不应对此值执行释放。

那么问题是为什么编译器会在第二种情况下发出错误?

感谢您的任何指示和解释!

附:我需要 references 上的迭代器,因为它非常适合我的其他 API。

【问题讨论】:

  • Rust 的错误信息通常包含很多有用的信息,所以请阅读它们!鉴于您没有在问题中包含完整的错误消息,并且错误地引用了您确实包含的部分,我认为您没有对错误消息给予太多重视。在这种特殊情况下,错误消息不是很准确,但它确实包含一个提示,并且跟随它会导致更好的错误消息。
  • Rust 的错误信息是我解释的主要来源(这是我使用 Rust 几年后的第一个问题)。我对第一个正在运行的示例感到困惑,并认为发生了一些奇怪的事情。但正如这里的答案解释了为什么第一个示例有效,它现在完全有意义。

标签: rust copy lifetime drop


【解决方案1】:

你的代码有两个相关的问题:

  1. 您在函数体末尾返回对超出范围的临时对象的引用。
  2. 您的返回类型包含未绑定到任何函数输入的生命周期参数。

引用只能在它们指向的数据中存在。方法调用f64::from_bits(0x7fffffffffffffff) 的结果是一个临时对象,它在表达式末尾超出范围。从函数返回对临时值或局部变量的引用是不可能的,因为一旦函数返回,引用的值将不再存在。

Copy 特征,或者对象是否存储在堆上,与超出范围的值不能再被引用这一事实完全无关。 在函数内部创建的任何 值都将在函数体末尾超出范围,除非您通过返回值将其移出函数。但是,您需要移动该值的所有权才能使其正常工作——您不能简单地返回一个引用。

由于您无法返回对函数内部创建的任何值的引用,因此返回值中的任何引用都必须引用通过函数参数传入的内容。这意味着返回值中任何引用的 生命周期 都需要与传递给函数的某个引用的生命周期相匹配。这让我们来到了第二点——一个只出现在返回类型中的生命周期参数总是一个错误。生命周期参数由调用代码选择,因此您实际上是在说您的函数返回一个在调用者选择的任意时间内存在的引用,这只有在引用引用与程序一样长的静态数据时才有可能.

这也解释了为什么您的第一个示例有效。文字[3.14] 定义了一个常量静态数组。该数组将与程序一样长,因此您可以将具有任意生命周期的引用返回给它。但是,您通常会通过显式指定静态生命周期来表达这一点,以明确发生了什么:

fn iter() -> impl Iterator<Item = &'static f64> {
    [3.14].iter()
}

只出现在返回值中的生命周期参数是没有用的。

那么你如何解决你的问题?您可能需要在拥有的类型上返回一个迭代器,因为您的 iter() 函数不接受任何参数。

【讨论】:

  • 更具体地说,您可能需要into_iter() 方法。
  • @apilat 分配Vec 并在其上调用into_iter() 是一种选择。但是,直接在数组上调用 into_iter() 不会有任何区别,因为您仍然会得到一个迭代器。
  • 感谢您指出这一点,我不知道这是怎么回事。
  • 第一个示例有效的解释是我所缺少的,现在这一切都变得完全有意义了! 'static 终生省略号并没有出现在我的脑海中。我通常了解函数内部的所有权,但第一个工作示例让我感到困惑。感谢您的回答。
【解决方案2】:

它是Copy,它在语义上被复制了,这确实是问题所在,因为它在语义上只存在于堆栈中,直到函数返回,引用现在,在语义上,指向堆栈外的内存并且非常可能很快就会被覆盖,如果 Rust 允许,那将导致未定义的行为。

最重要的是,from_bits 不是 const,这意味着您不能在编译时转换静态值,它是运行时操作。既然已经知道值,为什么还要每次都转换?


为什么会这样?

from_bits:

目前这与 transmute::(v) 相同 平台。

如果你看看transmute,你会发现:

transmute 在语义上等价于一种类型的按位移动 进入另一个。它将源值中的位复制到 目标值,然后忘记原始值。相当于C的 引擎盖下的 memcpy,就像 transmute_copy 一样。

虽然生成的代码确实可能只是对静态值的简单重新解释,但 Rust 不能在语义上允许将该值移动到堆栈上,然后丢弃,而引用仍然指向它。


解决方案。

既然你想返回一个NaN,你应该做和第一个例子一样的事情:

fn iter<'a>() -> impl Iterator<Item = &'a f64> {
    [f64::NAN].iter()
}

这将直接遍历静态切片,不会有任何问题。

【讨论】:

  • 我怀疑from_bits不是const可能与问题有关,但没有弄清楚借用检查器ellide 'a生命周期在第一种情况下是'static,即它在这种情况下工作的原因以及为什么在from_bits 情况下不能工作的原因。您提出的解决方案是我之前使用的解决方案,但在我的用例中,直观的是位表示,而不是它实际上是 NaN。但是感谢您的解释。
  • @pnevyk 为什么格式特定的表示比可读的、众所周知的名称更直观,不是每个人都记得它的价值?无论如何,这也不能被忽略到'staticstd::mem::transmute 我链接到详细解释了原因。
  • 我很开心用代码生成器制作编译器。我将abs 函数编译为and $value 0x7fff...,它有效地清除了符号位。这就是为什么我认为在这种情况下位表示更直观。
猜你喜欢
  • 1970-01-01
  • 2020-02-16
  • 2011-05-20
  • 1970-01-01
  • 1970-01-01
  • 2019-12-28
  • 2012-12-22
  • 2019-07-28
  • 1970-01-01
相关资源
最近更新 更多