It's true. Ever since I got familiar with the ownership concept, I've taken to writing C# IDisposables with disposable member variables like this:
public MyObject(){
this.myResource = new DisposableResource();
this.ownsMyResource = true;
}
public MyObject(DisposableResource resourceToUseThatIDontOwn){
this.myResource = resourceToUseThatIDontOwn;
this.ownsMyResource = false;
}
public void Dispose(){
//not pictured: .NET's boilerplate dispose code
if(ownsMyResource) myResource.Dispose();
}
For the client I'm contracting with right now, mismanagement of disposable resources is the #1 issue in the codebase. 100k lines of code, and it's never clear who should be disposing connections. There are some objects with multiple constructors (like above) that have 'conditional' ownership. In Rust, it's impossible to have this problem, period...although the above C# construct simulates it, lol.>The drop flags are tracked on the stack and no longer stashed in types that implement drop.
But just as a counter-argument, look how a simple lock looks like in Rust [1]:
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = counter.clone();
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
//...
}
[1] https://doc.rust-lang.org/book/second-edition/ch16-03-shared...----- edit: Just to clarify my point. The ownership model is a beautiful solution to the resource management problem. And as the parent pointed out, some concepts can be cleanly borrowed/reused in other languages.
But now, in Rust, in practice, you realize that this model requires a lot of boilerplate code just to make the borrow checker happy, even for simple things like using a lock: create Arc(Mutex(data)); clone the arc reference; move the new Arc reference to the new thread.
Something like
withLock(counter, (num) => {
return num + 1;
});
or in alternative, withLock(counter, (ref num) => {
num++;
});
If C# allowed for trailing lambdas it could look better. extern crate crossbeam;
use std::sync::Mutex;
fn main() {
let counter = Mutex::new(0);
crossbeam::scope(|scope| for _ in 0..10 {
scope.spawn(|| *counter.lock().unwrap() += 1);
});
println!("{:?}", counter);
}
Four lines to declare a mutex, start 10 threads that all lock that mutex, increase value behind mutex, unlock mutex, and join all spawned threads. There is a lot going on here (unwrap is arguably a noise here however, but the rest is straight to the point).Also, it's possible to use atomic ints here instead of mutex. Verbosity of mutex actually helps a bit, because locking is fairly expensive and it's better to avoid locking if possible.