Owned self, is it on stack or heap in this case?

Having the following exaple code:

pub struct TestAlloc {
   func: Box<dyn FnMut()>,
}

impl TestAlloc {
   pub fn new(sf: impl FnMut() + 'static) -> Self {
       TestAlloc {
           func: Box::new(sf),
       }
   }
   
   pub fn call_func(&mut self) {
       println!("invoking callback");
       (self.func)();
   }
   
   pub fn map(mut self) -> Self {
       TestAlloc::new(
           move || {
               println!("In callback 2");
               
               let _local: i32 = 1; // allocated on stack at the invocation point than moved to heap within new()?
               
               self.call_func();   // stack allocated self moved here
           }
       )
   }
}

fn main() {
   let mut t = TestAlloc::new(|| println!("In callback 1"));
   let mut wrapped_t = t.map();
   wrapped_t.call_func();
}

So new() function takes closure and puts it on the heap.
map() method takes ownership of self and returns new TestAlloc object with the owned self moved into closure which is moved to heap after.
My guess is that variable _local is first allocated on stack than moved to heap when callback is boxed?

In the map() moved self is originally allocated on the stack, when moved into callback that is moved to heap, is:
a) self also moved to heap meaning completely copied from one memory address to new one and heap allocated callback holds pointer to different heap allocation containing self?
b) heap allocated callback holds owned pointer to stack allocated self, no copying?
c) self moved to heap as a part of the callback, it is in the same memory block with _local?
d) something else?

Each time you call map, the number of heap allocations increase by one. So after calling map four times, you will have five heap allocations.

Yes, that part is clear, what I'm wandering is where exactly wrapped self ends up in the memory?

You end up creating something that looks like this:

struct Inner {}

struct Wrapper1 {
    inner: Box<Inner>,
}

struct Wrapper2 {
    inner: Box<Wrapper1>,
}

struct Wrapper3 {
    inner: Box<Wrapper2>,
}

struct Wrapper4 {
    inner: Box<Wrapper3>,
}
1 Like

It's d): self is moved to heap as a part of the callback (referred more idiomatically as "closure"), but _local is never moved to heap. As you have guessed, _local is allocated on the stack (ignoring dead code elimination for now), but this allocation is only done when the closure is actually called. TestAlloc::new moves the closure into the heap, but all of this happens before wrapped_t.call_func(); is called.

More generally speaking, a piece of code within a closure is not invoked when the closure is created. If you were parsing the code's control flow from its syntactic structure, this might have confused you. If that was the case, you should revisit your mental model of the inherent way closures work.

2 Likes

That was my rookie confusion yes, _local is always on stack (when closure is invoked), I got confused by invoking closure being on the heap.
While the initial stack allocated self is moved right away on the heap as soon map is called?

So if I get it correctly basically each wrapper holds pointer to heap allocated wrapped.

Yes. Since you have used a move closure, any outside variable used in the closure becomes a part of closure as soon as it is created.

1 Like

Got it, thanks @alice and @doublequartz