TIL: Rust does not prevent memory leaks
I'm not a Rust developer so I have no clues about what Rust does, how it does or why it is the way it is.
I had a short interaction on another thread and some user told that Rust does not prevent memory leaks.
People talk so much about Rust memory safety and how everything must be rewritten in Rust, but if Rust does not prevent memory leaks why are people so crazy about it?
25 comments
[ 2.5 ms ] story [ 59.2 ms ] threadDisclaimer: I am not a fan of Rust, this is just my understanding of this aspect of the benefits of it.
Think in terms of sharing memory across threads and what you need to do to avoid contention, use after free, races etc - and you will realize Rust helps you avoid all of that - and more.
Granted Rust has its idiosyncracies - which language doesn’t?
Try Rust yourself and don’t worry about the hype and what others are saying. Using LLMs would help you understand, write, troubleshoot some advanced concepts. We live in an amazing time where this is possible. Take full advantage of this.
Looks like https://cglab.ca/%7Eabeinges/blah/everyone-poops/ has more background on why this is allowed.
[0]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.l...
Rust can absolutely prevent memory leaks. It depends on how much unsafe rust code you use. Many rust developers find the convenience outweighs the risk of leaks, and take the tradeoff offered by the `Rc` type.
What does the term "unsafe" mean. If a language can have "unsafe code", then it is not safe. I heard the term "unsafe code" used everywhere in relation to c as a excuse for bad code.
So, if you are correct, you can still have leaks in rust, thus removing the number 1 reason to use rust.
Up and down this tread there are people saying "it cannot have leaks" and people saying "yes it can". That tells me moving to rust is not worth the expense and risk because no one knows how safe it is.
Unsafe has a strong name to encourage care about using it but that still doesn’t make it as unsafe as C is by default. You’re telling the compiler that there are certain areas where you have to do something which would normally not be allowed because of some information available to the developer but not the compiler. For example, Rust targets systems programming and it’s likely that you’ll need to do things such as dereferencing a pointer if you have to interact with a C API. Unsafe lets you do that without giving up other protections like the borrow checker and it confines that to specific blocks which you can audit more carefully.
https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
If I might use a car analogy, going from a mid-20th century language model like C to Rust is like buying a car which has automatic braking and lane assist. Yes, you can still crash if you turn those features off but the ratio of preventable problems to special circumstances where you know you’re doing something tricky is incredibly high.
You can have leaks in every language including garbage collected ones. Allocate a shared OS resource like a semaphore and don't give it back, there you go you have a leak that survives until the next reboot.
Safety as Rust means requires below:
- The program cannot call an operation that accesses invalid memory i.e. uninitialized memory and invalid memory areas.
- The program cannot substitute a memory location that defined as a particular type as another type.
- The program cannot make calls to an operation that causes the runtime environment (OS, CPU interrupts, running modes, system registers etc) that causes the two issues above.
If you have already allocated memory that just stays as it is, this is safe. The action of allocating it can be unsafe if the program makes a call to libc allocator since libc calls are outside of the Rust compilers' control. Similarly directly calling the page allocator of the OS will also be unsafe since Rust compiler doesn't have complete control of the OS and internal structure of the CPU. Similarly freeing memory may also change the page tables.
Moreover Rust makes it easier to create data structures that clean after themselves. This is not a direct safety issue. It is a language feature. The standard library makes extensive use of these. So the common programming patterns usually mitigate the risks of leaking resources but not eliminate.
While this is not the direction the standard library took, I think it would be possible to write an alternative stdlib that doesn't choose to allow leaks.
My original statement was probably too strong though. Leak-free code might be possible with rust-the-language, but is probably not possible with rust-the-ecosystem without significant development.
To elaborate a little, consider security. If your software's only vulnerability is potential memory leaks, an attacker can only get away with denial-of-service attacks. If your software is vulnerable to buffer overflows, an attacker can use return-oriented programming to get arbitrary code execution, exfiltrate all your data, and add your machine to a botnet to perform subsequent attacks on yet other systems.
That's not memory safety that rust advertises.
There is no actual difference between a memory "leak" and intentionally allocated memory except the INTENT of the developer. It is important for good language design to avoid accidental memory leaks via well-designed constructs. Ultimately there will always need to be a mechanism for a developer to create a block of allocated memory and free it only on their schedule.
Rust's language design does make it harder to leak memory, and well written Rust would rarely do so. However, if Rust made arbitrary allocations impossible, certain use cases would also be nearly impossible and performance for others would suffer greatly (e.g. allocating a buffer ahead of time, for the lifetime of a worker). It is a balance, at some point you have to trust the developer to make decisions.
The primary goal of rust is to provide a low-overhead, low-level language which is memory safe without the need for a runtime. Memory leaks are not a memory safety concerns[0], although they can cause programs to crash they are not UBs, they don't cause grave vulnerabilities (they might lead to DOS but not something like a compromised device)..
Furthermore while the Rust team and community discovered they could not reasonably prevent memory leaks[2], at least not without a very different languages and years if not decades of development, a normally constituted program is unlikely to have unintended leaks[1], the main vehicle to get that being a refcount cycle.
[0] although in Rust the realisation that leaks could not be prevented did force the reconsideration and binning of entire APIs because they could not be made safe anymore
[1] since it was realised that leaks were a fact of life, the standard library added a few APIs whose entire purpose is to leak objects
[2] much to everyone's dismay and displeasure, I assume you can still look for "leakpocalypse" for recountings of the events
What Rust and most other modern languages do is make it very difficult to unintentionally and/or unknowingly leak memory. There are times when even this can't be guaranteed without breaking other constraints (e.g. one of the solutions to the setenv unsafety issue involves leaking memory instead), but they are rare and documented.
To Rust's defense, both are extraordinary hard problems, and Rust's tooling is better than that of other languages in the space.
For memory leaks we have (above and beyond the usual suspects from the C/C++ world like valgrind) the leak detection in MIRI. For deadlocks we have the parking_lot crate.
Historically, what happened in Rust is that shortly before 1.0 was released, someone found a situation where you could get a reference-count cycle with the standard container types, and there was a lot of consternation as to whether or not this was actually a bug. This is termed the "leakpocalypse." The ultimate resolution was to accept that destructors are not guaranteed to be run, and therefore the kind of leak that was created is not actually a bug.
1. Access memory that hasn't been explicitly allocated to your program 2. Access memory that was was previously allocated and freed by your program 3. Access memory that could be modified by a separate thread / function of your program, outside of some locking mechanism.
I'm sure there's a more academic definition, and stronger guarantees, but those are the basics.
Now, these guarantees impose restrictions on how code is written, and runtime overhead to enforce. There are many cases where a human can write optimizations and ensure those guarantees in a way the compiler cannot verify. This is why Rust has an "escape" of writing "unsafe Rust", where the compiler does validate those guarantees. Unsafe Rust includes expanded primitives, such as "Box::leak()", that enable writing these optimizations, and may explicitly leak memory without maintaining a reference
If a program is written using purely safe Rust, it should not be possible for a malicious input / request to corrupt internal data structures, read memory that shouldn't be read, or all the other "interesting" things that get highlighted by Heartbleed and other big security vulnerabilities.
Note that I say "should not be possible" - the Rust compiler can and has had bugs that break those memory safety guarantees under certain conditions. They aren't common, but it's unfair to say "Rust is perfect and 100% safe"
I strongly recommend everyone learn Rust, at least to the point where you "grok" the borrow checker and what it's trying to enforce, even if you have no intention with regularly using Rust. After learning Rust, I found the patterns the borrow checker enforces incredibly useful when writing C++, and ensuring I don't make silly memory mistakes that may result in a crash or vulnerability.
[1] https://doc.rust-lang.org/std/mem/fn.forget.html
[2] https://doc.rust-lang.org/std/boxed/struct.Box.html#method.l...