Can assigning new values to a mutable variable in a loop, create a memory leak?

Hi all,

Thanks in advance for the help. Though I have done some plain C programming long ago, I use a GC'd language day-to-day, so I am trying to get my head back in to thinking about memory.

Consider this loop in rust:

let mut current_numbers : Vec<i32> = Vec::new();
current_numbers.push(1);
current_numbers.push(2);
  
for _i in 1..10 {
    let mut next_numbers : Vec<i32> = Vec::new();
    for n in current_numbers {
        let result = some_function(n);
        next_numbers.push(result);
    }
    // does this cause previous value of current_numbers to leak?
    current_numbers = next_numbers; 
}

Does that leak a vector object in every loop?

I understand that variables are freed when they leave scope. So I thought that this way might be better. The idea is that the previous_states vector goes out of scope and is freed in every loop.

let mut current_numbers : Vec<i32> = Vec::new();
current_numbers.push(1);
current_numbers.push(2);
    
for _i in 1..10 {
    let previous_numbers = current_numbers.clone();
    current_numbers.clear();
    for n in previous_numbers {
        let result = some_function(n);
        current_numbers.push(result);
    }
    // previous_numbers is freed at the end of the loop block
}

Am I overthinking this?

Assigning (overwriting) a value will call Drop on the original value in that space, so the line current_numbers = next_numbers; will free the memory for the previous vector.

In general, you should assume Rust will not leak memory any more than a GC language except in the case of shared ownership cycles (which does not apply here).

2 Likes

Assigning (overwriting) a value will call Drop on the original value in that space

Excellent, thanks.

I still have a bit of old C paranoia apparently.

All the ways to leak memory in Rust (that I’m aware of, and that I can think of at the moment):

  • Create a reference cycle with a reference-counted pointer Rc or Arc. This is less straightforward than one might initially think, because Rc or Arc make their contents immutable, so you cannot easily create a cycle. You can though, if you also use interior mutability (e.g. RefCell).
  • Manually call API that leaks memory. This includes (not an exhaustive list)
    • constructing a ManuallyDrop or MaybeUninit value containing a type that owns memory
    • using std::mem::forget on a type that owns memory
    • using Box::leak (on a not zero-sized type)
    • using Box::into_raw (on a not zero-sized type) without any subsequent (and necessarily unsafe) code that makes sure the Box's memory is still manually taken care off and eventually freed
  • Assign a value a static variable. Since static variables are immutable, and cannot (yet) be initialized with “heap-allocated” constants, the only way to have a static variable own memory is by using interior mutability primitives again. A common choice is once_cell::sync::Lazy; or std::sync::Mutex. The values in static variables will not be dropped at the end of the program; on the other hand, if it’s only about leaking memory, skipping destructors of a value that would’ve lived for the whole duration of the program anyway, isn’t really a problem. IDK; maybe just don’t save your handle to a TCP connection there though, because it won’t get closed. A related topic is thread-local variables. I’m not sure if all of those (in particular for the main thread) are actually always dropped… probably depends on the choice of thread-local-storage primitive, too.
    • Note that I’m not saying that everything put into a static is leaked. It’s just about the final value. If you change a static variable (e.g. via a Mutex) then old, overwritten values are of course dropped properly. In other words, there’s no unbounded memory leak potential here, you only leak as many “things” as you have static variables. Well… a single thing can of course contain unbounded data. Say, you’re implementing a string interner with a global ever-growing Mutex<HashMap<…, …>> of some kind or whatever, then that’s of course effectively a memory-leak (though in that case a deliberate one).
  • This thing involving channels, perhaps somewhat comparable to reference-cycles with Rc: A new memory leak example with channels - Rust Internals. Involves a weirdly recursive type definition to make it work in the first place, and then you need to send a channel handle (e.g. receiver) through its own channel.

All of these are quite hard to do accidentally (… or they’re not usually a problem, in case of statics). So don’t worry about memory leaks unless you’re doing any of the abov…… well, actually, don’t worry unless you’re doing lots of Rc+RefCell and not handling cycles (e.g. by inserting cycle-breaking Weak references); since the other cases are either (almost) impossible to run into, or non-problematic, or clear and deliberate memory leaks.

14 Likes