Please explain loop w/ mut borrow and temp value

Hi everybody,

I came from Java world and exploring Rust by trying to implement similar approaches. I understand that something might not work as I expected and my code doesn't make sense, but my aim is to intentionally face such issues. I would greatly appreciate if you point me to the right direction.

So I've started with creating custom file format which stores values as bytes. I have RawFormatBytes struct to hold Vec of tuples (bytes_length, bytes) and business object Rate which holds strings and float prices.

struct Rate<'a> {
    arrival: &'a str,
    price: f32,
    reason: &'a str,
}

struct RawFormatBytes<'a> {
    arrival_dates: Vec<(u8, &'a [u8])>,
    prices: Vec<(u8, &'a [u8])>,
    reasons: Vec<(u8, &'a [u8])>,
}

impl<'a> RawFormatBytes<'a> {
    pub fn new() -> RawFormatBytes<'a> {
        RawFormatBytes {
            arrival_dates: Vec::new(),
            prices: Vec::new(),
            reasons: Vec::new(),
        }
    }
}

What I'm trying to do is to pass RawFormatBytes holder to a Rate's to_byte_format method which fills in RawFormatBytes's byte tuples from self fields.

impl<'a> Rate<'a> {
    fn to_byte_format(&'a self, raw_bytes: &'a mut RawFormatBytes<'a>) {
        raw_bytes.arrival_dates.push((self.arrival.as_bytes().len() as u8, self.arrival.as_bytes()));
        raw_bytes.reasons.push((self.reason.as_bytes().len() as u8, self.reason.as_bytes()));
        raw_bytes.prices.push((self.price.to_ne_bytes().len() as u8, self.price.to_ne_bytes().as_ref()));
    }
}

It works fine until I want to store price bytes.
When I push self.price.to_ne_bytes() to my holder,I got creates a temporary which is freed while still in use and I'm fine with this. So I tried to .copy() my price, but again it doesn't make sense, as the copied value is freed when the method ends. Unfortunately, to_ne_bytes() consumes the value and I can do nothing with it.

error[E0716]: temporary value dropped while borrowed
  --> src\main.rs:46:70
   |
43 | impl<'a> Rate<'a> {
   |      -- lifetime `'a` defined here
...
46 |         raw_bytes.prices.push((self.price.to_ne_bytes().len() as u8, self.price.to_ne_bytes().clone().as_ref()));
   |         -------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------------ temporary value is freed at the end of this statement
   |         |                                                            |
   |         |                                                            creates a temporary which is freed while still in use
   |         argument requires that borrow lasts for `'a`

So my question is, what is the right approach to emerge a variable from a method which will be bound to caller's method argument RawFormatBytes lifetime? I.e. I want my Rate's method to create a var which will be stored in RawFormatBytes struct, thus assuming the struct's lifetime. Is it even possible?


Another question is, I simply don't understand why iter() borrows mutable ref more than one time? I expect that each iteration step borrows it and free after, so it's literately borrowed once-per-iteration, but it's not the case?

    let mut bytes = RawFormatBytes::new();
    let x = &mut bytes;

    for rate in rate_array.iter() {
        rate.to_byte_format(x);
    }

error[E0499]: cannot borrow `*x` as mutable more than once at a time
  --> src\main.rs:86:29
   |
86 |         rate.to_byte_format(x);
   |                             ^ mutable borrow starts here in previous iteration of loop

The main method is

fn main() {
    let s1 = String::from("2020-01-01");
    let s2 = String::from("reason1");
    let r1 = Rate {
        arrival: s1.as_str(),
        reason: s2.as_str(),
        price: 25.03,
    };

    let s3 = String::from("2022-02-02");
    let s4 = String::from("reason2");
    let r2 = Rate {
        arrival: s3.as_str(),
        reason: s4.as_str(),
        price: 356.03,
    };

    let rate_array = [r1, r2];

    let mut bytes = RawFormatBytes::new();
    let x = &mut bytes;

    for rate in rate_array.iter() {
        rate.to_byte_format(x);
    }
}

Again, my examples might not make sense in real world, but I would greatly appreciate what is the right way of thinking.

Thanks!

Well for your first question, the answer is that if you want the RawFormatBytes struct to have ownership of the allocation, you should use an owned type such as Vec<u8>. Anything with a lifetime is owned by something else, and that other thing must stay alive to keep the thing with a lifetime alive.

As for the other error, it happens due to the following: When you have &'a mut RawFormatBytes<'b>, then the 'a lifetime is a lower bound on how long the RawFormatBytes can live, and 'b is an upper bound on how long it can live. So by reusing the same lifetime on both positions, you make the upper and lower bounds equal, forcing the lifetime to be exactly the full duration for which that value is alive. Since x is still alive in the next iteration, the lifetime of that borrow extends into the next iteration.

You probably want to change it to &mut RawFormatBytes<'a> because you want to tie the slices inside the RawFormatBytes to self, but you don't care about the mutable reference after the to_byte_format function returned.

1 Like

To expand on this a bit, the reason this worked for the &strs is that there was no reformatting happening: as_bytes returns a reference to the existing buffer.

f32::to_ne_bytes, on the other hand, returns a new owned buffer; this is the temporary that the compiler is complaining about. This is necessary because network byte order might be different than the computer’s byte order; there’s no guarantee that there is an appropriate sequence of bytes already in memory to refer to.

1 Like

Thank you for the prompt replies and elaborate explanations!

I'm a bit confused here.

In a real application, I'd want to keep my business objects (e.g. when processing a list of json objects) as immutable input to the whole main() method at the top level, operating only on references to that data.

Thus, if I somehow transfer an ownership (e.g. due to .to_ne_bytes() limitation) , I'd rather copy() my immutable input object to avoid touching it? Is it a correct approach?

I suspect that there is no reason to keep immutable input objects in memory, in particular if all want is to make them written to a file.

My question is rather general - if I have to change ownership while preserving initial data, should I just copy an object?

You should not be using references for the purpose of making data immutable. For things you want to own, use ownership. I'm not quite sure what you're referring to with the copies of data.

Yes, that’s essentially correct. Methods like to_ne_bytes take ownership so that the caller can decide whether or not a copy (or clone()) is necessary— if you need to use the original value again later, make a copy to give to to_ne_bytes; if you don’t, give it the original and it’ll be a little bit more efficient.

That doesn’t matter so much in this case, though: f32 always copies (because it implements the Copy trait). The issue here is that you have a new object, returned from to_ne_bytes, that has to be owned by something for as long as there’s a reference to it. Right now, it’s owned by the to_byte_format function, and is destroyed as soon as that function returns.

The easiest fix is to let RawFormatBytes itself own that returned object, instead of just a reference to it, as @alice recommends.

1 Like

@2e71828 Thank you very much indeed!