8

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Thomas Baruchel
  • 7,236
  • 2
  • 27
  • 46

2 Answers2

9

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));
}
Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I agree Cow is a clever solution, but in this case I think just assigning to n (instead of to m) would be more simplistic: https://play.rust-lang.org/?gist=d1def91992515ac5a409c24b9bc41af6&version=stable&backtrace=0 – Jess Jan 19 '17 at 16:56
  • 1
    @Ben only if that did what you thought it does ^_^. That defines a **new binding** (that's the meaning of `let`), thus the type of `n` changes and you never get the new value. See that [this version seemingly never increments the value](http://play.integer32.com/?gist=37936638d30435da3a72749c59d3adbc&version=stable); which is why it (and your solution) complain that `n` is a unused variable. For further information, [see this other question](http://stackoverflow.com/q/31798737/155423). – Shepmaster Jan 19 '17 at 17:05
4

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Chris Emerson
  • 13,041
  • 3
  • 44
  • 66