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 static
s). 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