0

I am pretty certain that there is a dedicated name for this but I have no idea what it is.

When you have a pointer that stops pointing to a valid object, that's a dangling pointer, but what about an object that has no references to it, particularly in Rust?

Take the following code for example:

{ 
    let mut v: Vec<u32> = vec![1, 2, 3];
    v = Vec::new();
    v.push(0);
}

When v is reassigned to a whole new vector, what happens to the old one? In C/C++ that is the birth of a memory leak since nobody is gonna free that memory and you no longer have a way to do so either. However, in Rust, there's all kinds of magic happening when exiting a scope (hence the {} in the sample code.

From a logical point of view, since Rust has no GC, that would dictate that the vector just stays in memory until the process terminates, scanning for unreachable objects when going out of scope would tread on actual GC but I don't yet know enough about Rust internals to make those kinds of guesses (though I'd like to at some point).

What exactly happens in the above code? Is that a memory leak that you have to watch out for just like in C/C++?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Rares Dima
  • 1,575
  • 1
  • 15
  • 38
  • 4
    "In C/C++ that is the birth of a memory leak since nobody is gonna free that memory and you no longer have a way to do so either." In C++ `vector::operator=` will free the old memory, so there'd be no leak. In C there are no vectors and you simply can't assign to arrays, so you wouldn't be able to write code like that. – sepp2k Feb 08 '18 at 20:25
  • @sepp2k I'm sorry for not being clear enough. I'm not referring to the particulars of a vector, but to *something*, a value, an object, you name it, that takes up space in memory. Maybe a vector wasn't the right example if it cleans up after itself, but the idea is still there, if it wouldn't clean up after itself trough the actual implementation, the compiler/runtime is not gonna do it either. I'm sure you inderstand – Rares Dima Feb 08 '18 at 20:29
  • For the code you provided, the Rust compiler will statically (i.e. at compile time) determine that `v` is already initialized whe the statement `v = Vec::new()` is reached, so the compiler will simply emit code to drop the old value before the assignment. – Sven Marnach Feb 08 '18 at 20:33
  • 1
    A proper implementation of `operator=` should *always* handle memory properly. If it doesn't, run away from this type. – mcarton Feb 08 '18 at 20:34
  • @mcarton So did you just tell us that `operator=` isn't implemented properly for raw pointers in C++? I guess you have a point here. :) – Sven Marnach Feb 08 '18 at 20:37
  • 2
    Everything that isn't directly created using `new`, or broken, cleans up after itself in C++. To create a memory leak, you'd have to work with raw pointers to dynamically allocated memory. And once you've realized that and tried to do the same in Rust, you'll find that Rust doesn't allow you to touch raw pointers outside of `unsafe` blocks. – sepp2k Feb 08 '18 at 20:38
  • @SvenMarnach Raw pointers? What are those? Oh, you mean C++ from the older times! But it's 2018 now, nobody uses those. – mcarton Feb 08 '18 at 20:41
  • 2
    The suggested duplicate target demonstrates shadowing, not overwriting. The behavior in this case is different (compare [1](https://play.rust-lang.org/?gist=1f896de77082e3d04ed61df61e596dd4&version=stable) and [2](https://play.rust-lang.org/?gist=7179c02d7b63a062fb2cfabd1bb825e5&version=stable)) – trent Feb 08 '18 at 21:00
  • 1
    @trentcl In your second example (that I believe represents shadowing) -- looks like the memory is kept on the stack (not freed) until the scope ends as in [modified 2](https://play.rust-lang.org/?gist=33044c4a8cf22411269dff967a0b2c31&version=stable). So if you run a large loop -- this approach may accumulate quite large "temporary memory leak", right? – Logan Reed Feb 08 '18 at 21:10
  • 1
    @LoganReed The shadowing variable only exists for a single iteration of the loop and is dropped afterward (each iteration), so no. [Demonstration.](https://play.rust-lang.org/?gist=f239b41680bbc8c68c004daa39513b7a&version=stable) Great question though! – trent Feb 08 '18 at 22:34

1 Answers1

3

Rust (just as C++) uses RAII to handle this. Types that need to be destroyed implement the Drop trait (in C++, they have a destructor), which is automatically called when a variable goes out of scope.

In Rust:

{
    let a = foo();
    a = b; // `b` is moved to `a`,
           // the previous value of `a` is dropped, which frees memory
} // `a` goes out of scope, it is dropped, which frees memory

In C++:

{
    auto a = foo();
    a = b; // `=` is essentially a function call,
           // but a proper `=` implementation should handle memory properly
} // `a` goes out of scope, its destructor is called, which frees memory

For properly implemented types in Rust or C++, there are no memory leaks. C on the other hand has no such concept, and memory always has to be freed explicitly.

mcarton
  • 27,633
  • 5
  • 85
  • 95
  • I see, that makes sense! I'm going on intuition here but I guess that what you said, along with Rust's concept of ownership and how the compiler tends to bark at you if you end up with dangling pointers pretty much assures memory safety. However, what if the variable is in the top level? Like one of the first variable in the `main` function? Meaning it only goes out of scope when the program terminates – Rares Dima Feb 08 '18 at 21:16
  • 1
    @RaresDima The old value of `a` is dropped right before assigning the new value, independently of the scope of `a`. This answer is slightly incomplete, as it only mentions `drop()` being called when a variable goes out of scope. There are many other places where the Rust compiler inserts implicit drops, e.g. right before assignments, or if a variable gets moved out only in one `if` branch it gets implicitly dropped in the other `if` branch. There are even cases that the compiler can't solve statically, so variables called "drop flags" are used to track whether a variable needs to be dropped. – Sven Marnach Feb 09 '18 at 16:47
  • 1
    See also [the chapter on drop flags in the Rustonomicon](https://doc.rust-lang.org/beta/nomicon/drop-flags.html). Also note that RAII in C++ really only calls the destructor at the end of the scope. In other situations (like the assignment) the implementation of `operator=` is responsible for cleaning up, rather than static analysis by the compiler. Simply stating that "Rust (just as C++) uses RAII to handle this" may be accurate (not sure about the terminology), but it is also somewhat misleading since the implementations of the conecpt in Rust and C++ are quite different. – Sven Marnach Feb 09 '18 at 16:52