Hacker News new | ask | show | jobs
by Mouse47 3155 days ago
>Rusts solution is a more general solution to the "general resource problem"

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.
2 comments

Fun fact: what you did is a hand implementation of Rust's "drop flag" (https://doc.rust-lang.org/nomicon/drop-flags.html).
That's pretty cool - it never occurred to me that determining drops required anything other than static analysis. Nice that it doesn't affect the layout of the types, either...which I'm guessing is why they added:

>The drop flags are tracked on the stack and no longer stashed in types that implement drop.

I agree and I appreciate what they are trying to do.

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.

The issue really is lack of scoped threads forcing usage of reference counting to figure out when to drop a value. With crossbeam scoped threads implementation this looks much nicer.

    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.

You can simulate something like that using lambdas in C#.

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.