Hacker News new | ask | show | jobs
by lordnaikon 3161 days ago
You're overlooking that Rusts "solution"(borrowing, livetimes, etc. ) is not only solving the same problem that garbage collection tries to solve. Garbage collection is "only" solving the "memory resource problem". Rusts solution is a more general solution to the "general resource problem" .. like file handles, sockets and of course memory and besides that gives you tools to never get bitten by data races. Nothing (besides memory) garbage collection is helping you with. You have to mitigate those problems still in garbage collected languages like Swift, Go, Java, Python (to name a few) and various other solutions to help with this (try with resource, with statement, immutable message passing etc.) Rust tries to give you one solution "to rule them all". Is this better, worse? Idk – nobody does, the Rust people are trying to figure this out :) Nothing is carved in stone. And if you're more productive in another language you should use it! But you can only know by figuring this out for yourself. Rust works well for me – that's no guarantee that you have the same experience. Rust is still hard to learn and one needs to question himself if the effort in learning pays out at the end – it has for me, i guess/hope ;)
3 comments

>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.
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.
You would still need to have lots of resource management problems to consider using Rust, i.e. it is useful to those high performance/embedded domains that still use C/C++. There are high level dynamic solutions even for things like data races these days (e.g. transacations) if you don’t need to juice every last bit of perf out.
GC helps with file handles. Just give the "file" object a destructor that closes the underlying handle.
... and, it will be eventually closed. Maybe. Definitely before the program quits. Not sure that it'll be closed before you run out of file handles, though.