4

I'm using rust 0.8.

Why is it that I can do this:

fn add(num: ~int) -> ~fn(int) -> int { |x|
    *num + x
}

but not this:

fn outer(num: ~int) -> ~fn(int) -> int { |x|
    *inner(num) + x
}

fn inner(num: ~int) -> ~int {
    num
}

the second one fails with, "error: cannot move out of captured outer variable in a heap closure". What makes calling a function special?

Is the concern that the inner function might do something dirty with that boxed function that the static analysis won't catch?

rene
  • 41,474
  • 78
  • 114
  • 152
nnythm
  • 3,280
  • 4
  • 26
  • 36
  • 2
    Not a proper answer so I'll add it as a comment: https://github.com/mozilla/rust/wiki/Doc-under-construction-FAQ – Ercan Erden Oct 21 '13 at 21:38

1 Answers1

3

The problem is the possibility to call the closure twice. On the first run, the captured variable num is moved into inner, that is, moved out of the closure's environment. Then, on the second call, the place where num was is now invalid (since it was moved out), which breaks memory safety.

In more detail, one can regard closures as (approximately)

struct Closure { // stores all the captured variables
    num: ~int
}

impl Closure {
    fn call(&self, x: int) -> int { 
        // valid:
        *self.num + x

        // invalid:
        // *inner(self.num) + x
    }
}

Hopefully this makes it clearer: in the invalid one, one is trying to move self.num out from behind a borrowed pointer into the inner call (after which it is entirely disconnected from the num field). If this were possible, then self would be left in an invalid state, since, e.g., the destructor on self.num may've have been called which frees the memory (violating memory safety).


One resolution of this is "once functions", which are implemented but are hidden behind a compiler flag, since they may be removed in favour of (at it's most basic) adjusting the type of call above to be fn call(self, x: int), that is, calling the closure moves self which means you can then move out of the environment (since call then owns self and its fields) and also means that the function is statically guaranteed to be called only once*.

*Not strictly true if the closure's environment doesn't move ownership, e.g. if it was struct Env { x: int }.

huon
  • 94,605
  • 21
  • 231
  • 225
  • It seems like I shouldn't care about how the function is used as long as the closure I dump it into has the same lifetime. Why can't I just add lifetime parameters and make it work? There isn't anything inherently dangerous about calling a function multiple times unless it's mutable and it's not idempotent. Also, why is moving the function into inner moving it out of the closure's environment? It seems like it's safe since I know when the closure will be freed. It should stay in the same environment. – nnythm Oct 26 '13 at 01:45
  • It's not the closure that's the problem. It's the `num`. The `num` capture is getting moved around and the `~` pointer will be freed after the first call (since it gets moved into `inner` and so the closure's environment loses control & ownership of `num`, i.e. it doesn't get to "choose" when `num` gets dropped). This means that the second (and third, fourth...) will be trying to use already-freed memory, when they handle `num`. – huon Oct 26 '13 at 02:00