WarningThis article expresses a personal opinion on language design. It does not argue that Rust’s ownership model or safety guarantees are ineffective, nor is it intended as criticism of any form to the Rust language or its community. Readers are welcome to disagree, this is not objective.
RAII application in Rust was originally going to be the topic of argument until I realized that RAII wasn’t the problem, rust’s ownership model once again was. The topic came from looking into into the differences of how to apply RAII in HPC while on the 9th floor of a library. Granted, this probably would’ve been the 1000th article/discussion out there arguing about C++‘s RAII.
“RAII” stands for “Resource Acquisition is Initialization”
Introduction
While doing some comparisons on the implementations of RAII between C++ and Rust, I noticed an interesting difference that wasn’t RAII itself, but more so was how Rust’s ownership model shapes the way RAII was expressed, with alot more rigid guardrails (aka. stronger compile-time guarantees with more rigid constraints). However there’s some part that shouldn’t be changed or shouldn’t be mandatory. The idea of any concept like RAII being inseparable from the ownership model is… questionable, to say the least. Core concepts should be built alongside the ownership model, not ontop of it; modern languages shouldn’t require compile-time ownership as the only intended path to definite resource management.
Ownership Should be Optional
I get that ownership and safety is the entire model and the language it completely centered around it, but decision of whether or not a concept or technique needs to change due to the model of the language shouldn’t need to be made, there simply shouldn’t be a change. For example using mutexes…
Here’s an example in each of the two languages:
1fn work(m: &std::sync::Mutex<i32>) {2 let guard = m.lock().unwrap();3 // critical section4}1std::mutex m;2
3void work() {4 std::lock_guard<std::mutex> lock(m);5 // critical section6}The only real difference between the two is how that mutex’s life-cycle works. A normal C++ global std::mutex may have its destructor run during program shutdown. A Rust static Mutex generally isn’t dropped at shutdown. But that distinction is unrelated to unlocking at the end of the work function. In the example we do know that Rust’s ownership system statically connects the lifetime of the guard, the mutex it refers to, and access to the protected value which isn’t what C++ does. In C++ the language doesn’t require all of those relationships to be in that same system. Those invariants should be available to programmers who want them, rather than universally enforced by the language.
Complexity
To be fair, the first example doesn’t actually show the difference in complexity. But suppose the following:
Given that you what shared ownership, your C++ would look something along the lines of this:
1auto p = std::make_shared<Foo>();2auto q = p;Simple right? But when you rewrite that in Rust:
1let p = Rc::new(Foo::new());2let q = Rc::clone(&p);We reach a problem where in Rust’s model, there’s a ton of questions that are raised such as “Do we use an Arc here?”, “How do you know you’re supposed to use Rc”, “Do I need weak?“,or “is this a cycle?”. Rust requires these ownership decisions to be explicit and encoded in the type system. C++ leaves more of them to programmer judgment and convention.
An example here in trees of the exact same point that involves ownership. Suppose you want trees, and each node of the tree also stores its parent:
1struct Node {2 std::vector<std::shared_ptr<Node>> children;3 std::weak_ptr<Node> parent;4};We’ll simply ignore safety as that isn’t the focus of these trees (my point isn’t that they’re unnecessary, but that they illustrate additional design complexity caused by making compile-time ownership the main model). In Rust we would end up in a situation like this:
1struct Node {2 parent: Weak<RefCell<Node>>,3 children: Vec<Rc<RefCell<Node>>>,4}And this is caused by the question “Who owns Who?” which the compiler is asking questions that C++ lets you answer implicitly.
Restate
This isn’t to argue that Rust’s ownership model is bad. While I myself find it frustrating at times, it’s a coherent and effective design for the goals Rust sets out to achieve. My argument is instead that compile-time ownership is one possible trade-off, not the universal one. Modern languages should not treat a single ownership model as the only appropriate foundation for deterministic resource management. Depending on the domain, runtime flexibility, garbage collection, arenas, reference counting, or other ownership strategies may be equally valid choices.
Additional Information
You can always go back to the book about this: https://doc.rust-lang.org/rust-by-example/scope/raii.html
Reminder of what ownership is: https://rust-book.cs.brown.edu/ch04-01-what-is-ownership.html
Meme of the post
Some information may be outdated