2

I'm trying to learn Rust as someone who is somewhat familiar with C++.

I think i grasped the concept of ownership and borrowing pretty well but there is something that confuses me in struct constructors.

struct A{
    b: u32
}
impl A{
    fn constructor(a:u32)->A{
        let obj= A{b:a};
        return obj;
    }
}
fn main(){
    let c= A::constructor(3);
}

In the constructor of A we are allocating an A object in the stack memory of the constructor function and returning the ownership of the object. However, after we return the object, isn't the stack memory of the constructor function going to be de-allocated since it is going to be popped of the stack, leaving us with a dangling pointer c.

I know that passing the ownership outside the the constructor will prevent it from de-allocating when scope of the constructor ends but isn't de-allocation of the stack memory when the function finishes independent from the ownership process?

I would really appreciate any help. I like to continue learning rust with a solid understanding of its memory managment.

Emre Uğur
  • 71
  • 1
  • 6
  • 3
    "Passing the ownership" just means doing a shallow bitwise copy of the object and forgetting about the original. So when you return `obj`, it doesn't matter what happens to the memory where it used to reside; its contents will now be copied to (and become the responsibility of) the caller. – user4815162342 Nov 08 '21 at 10:28
  • @user4815162342 Thank you, this solves my confusion. – Emre Uğur Nov 08 '21 at 11:17
  • @EmreUğur Also note that an actual `memcpy()` doesn't need to occur in optimized code. Ownership-related copies are part of the semantics of the language, but are not observable by user code. The optimizer is perfectly allowed (and often able) to inline the call, or directly construct the returned object on the caller's stack, or never use stack in the first place, but store the values directly in CPU registers. – user4815162342 Nov 08 '21 at 13:39
  • By the way, the "right" way to write that constructor's body is simply `let obj = A{b:a}; obj` (or just `A{b:a}`); the last expression in a function is its return value – BallpointBen Nov 08 '21 at 14:26

1 Answers1

3

In Rust, transferring ownership often translates to "destructive bitwise copies".

When you move a struct from one let binding to another, you're performing a bitwise copy and marking the old binding as "uninitialized".

When you return a struct from a function to its caller, you're performing a bitwise copy to the calling stack frame, and you don't have to worry about uninitializing anything because the scope is gone.

In practice, much of this copying and moving and destroying is optimized away, but you can write your programs as if it isn't. So in your case, Foo::constructor creates a stack frame with enough space for a Foo in it. When main calls Foo::constructor, its stack frame has enough space for a Foo, and when it returns, the bits are copied over.

I'll stress that in practice, something this size would almost certainly get inlined, this is just the mental model. If you're worried about performance, check out a crate called criterion for benchmarking.

cameron1024
  • 9,083
  • 2
  • 16
  • 36