0

I encountered a lifetime error which I am not able to explain why it is emitted by the compiler. I need this (which works fine):

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

However, when I try to use a float value which is transmuted from a specific byte representation using from_bits, like this:

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

it gives me "creates a temporary which is freed while still in use". Playground here (stable 1.45.2).

My reasoning was that since f64 is Copy type (and it indeed works as expected if I use a constant value), this should work because no freeing should be performed on this value.

So the question is why the compiler emits the error in the second case?

Thanks for any pointers and explanations!

P.S. I need the iterator over references because it fits nicely with my other API.

pnevyk
  • 1
  • 4
  • 1
    Rust's error messages in general contain a lot of useful information, so please read them! Given that you didn't include the full error message in your question, and misquoted the part you did include, I presume you didn't give much value to the error message. In this particular case, the error message isn't quite spot on, but it does contain a hint, and follwing it leads to a much better error message. – Sven Marnach Aug 17 '20 at 08:08
  • Rust's error messages are my primary source of explanations (this is my very first question about Rust after few years of using it). I was confused by the first example being working and thought that something strange is happening. But as answers here explained why the first example works it now makes complete sense. – pnevyk Aug 18 '20 at 13:16

2 Answers2

2

Your code has two related problems:

  1. You return a reference to a temporary object that go out of scope at the end of the function body.
  2. Your return type contains a lifetime parameter that isn't bound to any function input.

References can only live as long as the data they point to. The result of the method call f64::from_bits(0x7fffffffffffffff) is a temporary object which goes out of scope at the end of the expression. Returning a reference to a temporary value or a local variable from a function is not possible, since the value referred to won't be alive anymore once the function returns.

The Copy trait, or whether the object is stored on the heap, is completely unrelated to the fact that values that go out of scope can no longer be referred to. Any value created inside a function will go out of scope at the end of the function body, unless you move it out of the function via the return value. However, you need to move ownership of the value for this to work – you can't simply return a reference.

Since you can't return a reference to any value created inside your function, any reference in the return value necessarily needs to refer to something that was passed in via the function parameters. This implies that the lifetime of any reference in the return value needs to match the lifetime of some reference that was passed to the function. Which gets us to the second point – a lifetime parameter that only occurs in the return type is always an error. The lifetime parameter is chosen by the calling code, so you are essentially saying that your function returns a reference that lives for an arbitrary time chosen by the caller, which is only possible if the reference refers to static data that lives as long as the program.

This also gives an explanation why your first example works. The literal [3.14] defines a constant static array. This array will live as long as the program, so you can return references with arbitrary lifetime to it. However, you'd usually express this by explicitly specifying the static lifetime to make clear what is happening:

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

A lifetime parameter that only occurs in the return value isn't ever useful.

So how do you fix your problem? You probably need to return an iterator over an owned type, since your iter() function doesn't accept any arguments.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • More concretely, you probably want the [`into_iter()`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) method. – apilat Aug 18 '20 at 06:48
  • 1
    @apilat Allocating a `Vec` and calling `into_iter()` on it would be one option. Calling `into_iter()` on the array directly wouldn't make any difference, though, since you'd still get an iterator over references. – Sven Marnach Aug 18 '20 at 07:23
  • Thanks for pointing that out, I didn't know that's what happened. – apilat Aug 18 '20 at 07:40
  • The explanation why the first example works is what I was missing and it now all makes complete sense! The `'static` lifetime ellision didn't come into my mind. I generally understand the ownership inside a function, but the first example working confused me. Thanks for the answer. – pnevyk Aug 18 '20 at 12:59
0

It is Copy, it is semantically copied, and that is indeed the problem, as it semantically only exists on the stack until function returns, reference is now, semantically, pointing to memory that is outside the stack and is very likely to be overwritten soon, and if Rust allowed it, that would result in undefined behaviour.

On top of that, from_bits isn't const, and that means that you cannot convert static values at compile time, it is a runtime operation. Why do you want to convert each time when you already know the value?


Why is this the case anyway?

from_bits:

This is currently identical to transmute::<u64, f64>(v) on all platforms.

If you take a look at transmute, you will find:

transmute is semantically equivalent to a bitwise move of one type into another. It copies the bits from the source value into the destination value, then forgets the original. It's equivalent to C's memcpy under the hood, just like transmute_copy.

While generated code may indeed just be a simple reinterpretation of static value, Rust cannot semantically allow this value to be moved onto stack, then dropped, while a reference is still pointing to it.


Solution.

Since you want to return aNaN, you should just do the same thing you did in the first example:

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

This will iterate over a static slice directly, and there will be no issues.

Yamirui
  • 169
  • 6
  • I suspected that `from_bits` not being const may be related to the problem, but didn't figure out that borrow checker ellide `'a` lifetime to be `'static` in the first case, which is the reason why it works in that case and why it could not in the `from_bits` case. The solution you propose was the one I used before, but in my use case what is intuitive is the bit representation rather than that it is actually NaN. But thanks for the explanation. – pnevyk Aug 18 '20 at 13:05
  • @pnevyk why would format specific representation be more intuitive than readable, well known name for which not everyone remembers value of? Also this cannot ever be elided to `'static` no matter what, `std::mem::transmute` I linked to explains why in detail. – Yamirui Aug 18 '20 at 13:13
  • I am having fun making a compiler with code generator. I compile `abs` function as `and $value 0x7fff...` which effectively clears the sign bit. That's why I think the bit representation is more intuitive in this case. – pnevyk Aug 19 '20 at 18:00