Thomas Baruchel
Thomas Baruchel

Reputation: 7507

How do I update a variable in a loop to a reference to a value created inside the loop?

I want to enter a loop with a variable n which is borrowed by the function. At each step, n takes a new value; when exiting the loop, the job is done, with the help of other variables, and n will never be used again.

If I don't use references, I have something like this:

fn test(n: Thing) -> usize {
    // stuff
    let mut n = n;
    for i in 1..10 {
        let (q, m) = n.do_something(...);
        n = m;
        // stuff with x
    }
    x
}

x is the result of some computation with q and m but it is an usize type and I didn't encounter any issue in this part of the code. I didn't test this code, but this is the idea. I could make code written like this work.

Since I want to do it with a reference; I tried to write:

fn test(n: &Thing) -> usize {
    // stuff
    let mut n = n;
    for i in 1..10 {
        let (q, m) = (*n).do_something(...);
        n = &m;
        // stuff with x
    }
    x
}

Now the code will not compile because m has a shorter lifetime than n. I tried to make it work by doing some tricky things or by cloning things, but this can't be the right way. In C, the code would work because we don't care about what n is pointing to when exiting the loop since n isn't used after the loop. I perfectly understand that this is where Rust and C differ, but I am pretty sure a clean way of doing it in Rust exists.

Consider my question as very general; I am not asking for some ad-hoc solution for a specific problem.

Upvotes: 9

Views: 10076

Answers (2)

Shepmaster
Shepmaster

Reputation: 430348

As Chris Emerson points out, what you are doing is unsafe and it is probably not appropriate to write code like that in C either. The variable you are taking a reference to goes out of scope at the end of each loop iteration, and thus you would have a dangling pointer at the beginning of the next iteration. This would lead to all of the memory errors that Rust attempts to prevent; Rust has prevented you from doing something bad that you thought was safe.

If you want something that can be either borrowed or owned; that's a Cow:

use std::borrow::Cow;

#[derive(Clone)]
struct Thing;

impl Thing {
    fn do_something(&self) -> (usize, Thing) {
        (1, Thing)
    }
}

fn test(n: &Thing) -> usize {
    let mut n = Cow::Borrowed(n);
    let mut x = 0;

    for _ in 1..10 {
        let (q, m) = n.do_something();
        n = Cow::Owned(m);
        x = x + q;
    }

    x
}

fn main() {
    println!("{}", test(&Thing));
}

Upvotes: 10

Chris Emerson
Chris Emerson

Reputation: 14021

If I understand this right, the problem is not related to life outside of the loop; m doesn't live long enough to keep a reference for the next iteration.

let mut n = n;
for i in 1..10 {
    let (q,m) = (*n).do_something(...)
    n = &m
}  // At this point m is no longer live, i.e. doesn't live until the next iteration.

Again it depends on the specific types/lifetimes, but you could potentially assign m to a variable with a longer lifetime, but then you're back to the first example.

Upvotes: 4

Related Questions