Live data from Hacker News

Rust RAII is better than the Haskell bracket pattern

snoyman.com

1–10 of 156 posts

Re: Rust RAII is better than the Haskell bracket pattern

#2
The thing is that, in Haskell, even when you attach a function to run during destruction, the runtime doesn't guarantee that the function will be called promptly, or even at all. Rust drops (runs destructor and deallocates) values as soon as they go out of scope; C++ too. In Haskell you depend on the whims of the GC, which makes RAII unusable. (The Haskell approach of not guaranteeing destructors being called does have its merits; when many C++ and Rust programs are about to end, they spend the last few cycles uselessly deallocating memory that would've immediately been freed via _exit(2)).

Therefore the RAII style wouldn't really work in Haskell. The current bracket approach is still better than RAII in Haskell.

That said, the ST-style trick of a phantom type variable is pretty well-known. Unfortunately not many people knew the same trick can be used for non-ST as well. I feel like as a community we should be encouraging this style more often.

UPDATE: I wrote the original comment with the incorrect assumption that drop functions will always be called in Rust. This is wrong. Please see child comments.

Re: Rust RAII is better than the Haskell bracket pattern

#4
post #3

Would be interesting to know why memory leaks are possible in Rust, if RAII is so deeply integrated into language.

Because the standard library has reference counting with no static checking to avoid cycles, and it was decided to also have safe mem::forget since it can be (mostly) emulated with the former.

It has no such static checking because it was deemed to reduce expressiveness, while not impacting memory safety.

Re: Rust RAII is better than the Haskell bracket pattern

#5
post #3

Would be interesting to know why memory leaks are possible in Rust, if RAII is so deeply integrated into language.

Like this:

  fn leak() {
      // Create a 1KiB heap-allocated vector
      let b = Box::new(vec![0u8; 1024]);
      // Turn it into a raw pointer
      let p = Box::into_raw(b);
      // Then leak the pointer
  }
Obviously that's kind of blatant, but there are more subtle ways to leak memory. Memory leaks aren't considered unsafe, so even though they're undesirable the compiler doesn't guarantee you won't have any.

Reference cycles when using Rc are a big one, but generally it's pretty hard to cause leaks by accident. I've only run into one instance of leaking memory outside of unsafe code, and that was caused by a library issue.

Re: Rust RAII is better than the Haskell bracket pattern

#6
post #2

The thing is that, in Haskell, even when you attach a function to run during destruction, the runtime doesn't guarantee that the function will be called promptly, or even at all. Rust drops (runs destructor and deallocates) values as soon as they go out of scope; C++ too. In Haskell you depend on the whims of the GC, which makes RAII unusable. (The Haskell approach of not guaranteeing destructors being called does ha…

I don't know if it's fair to call that "the Haskell approach", per-se. That destructors are not guaranteed to run, or run predictably, is generally a property of all fast garbage collectors. If you want a GC that can run quickly, which in a language like haskell where you're going to get lots of small allocations in contexts it would be difficult-to-impossible to efficiently determine the exact moment scopes die, or get the programmer to, you absolutely do, then one of the costs of that is you can't afford to run code for every destroyed object.

The linked post is interesting, because I didn't realise "RAII is a much better way of managing resources than destructors" was controversial. It absolutely is, RAII is fast, predictable, and flexible. It's also one of the tradeoffs some languages make to achieve more flexibility in their design by enabling performant automatic garbage collection that doesn't require perfect escape analysis.

Re: Rust RAII is better than the Haskell bracket pattern

#7
post #2

The thing is that, in Haskell, even when you attach a function to run during destruction, the runtime doesn't guarantee that the function will be called promptly, or even at all. Rust drops (runs destructor and deallocates) values as soon as they go out of scope; C++ too. In Haskell you depend on the whims of the GC, which makes RAII unusable. (The Haskell approach of not guaranteeing destructors being called does ha…

> The thing is that, in Haskell, even when you attach a function to run during destruction, the runtime doesn't guarantee that the function will be called promptly, or even at all.

There's also no guarantee for Rust/C++ destructors to be called. It's certainly less of an issue then depending on the GC to being called, but if you need absolute correctness, then you shouldn't rely on the destructors.

Re: Rust RAII is better than the Haskell bracket pattern

#8
post #7
post #2

The thing is that, in Haskell, even when you attach a function to run during destruction, the runtime doesn't guarantee that the function will be called promptly, or even at all. Rust drops (runs destructor and deallocates) values as soon as they go out of scope; C++ too. In Haskell you depend on the whims of the GC, which makes RAII unusable. (The Haskell approach of not guaranteeing destructors being called does ha…

> The thing is that, in Haskell, even when you attach a function to run during destruction, the runtime doesn't guarantee that the function will be called promptly, or even at all. There's also no guarantee for Rust/C++ destructors to be called. It's certainly less of an issue then depending on the GC to being called, but if you need absolute correctness, then you shouldn't rely on the destructors.

If a variable has block scope in C++ (i.e. it is a local variable in a function) then its destructor is guaranteed to be called when the block is finished, regardless of whether that is due to a `return` statement or an exception being thrown (or a `break` or `continue`). In what sense do you disagree?

If you allocate an object on the heap with `new` then its destructor isn't called automatically unless you make it so through some other mechanism, but GP comment clearly want claiming that.

There are some situations where objects with block scope do not have their destructor called e.g. `_exit()` called, segfault, power cable pulled out. But in that sense nothing is guaranteed.

Re: Rust RAII is better than the Haskell bracket pattern

#9
post #3

Would be interesting to know why memory leaks are possible in Rust, if RAII is so deeply integrated into language.

The same way that memory leaks are possible in Java: rather than a technical bug (you forgot to `free` some buffer), instead you have a semantical bug (you're holding on to a pointer to the data after you're done with it, and that keeps the data alive).

Granted, the ownership/borrowing semantics of rust make this a lot harder, but anything that uses Rc/Arc can easily fall prey to it — you can use those to create a reference cycle.

Re: Rust RAII is better than the Haskell bracket pattern

#10
How do you handle errors at resource release? When you close a file, the final writes take place, and they can fail. What's the idiom in Rust for getting them out?

Python's "with" clause, and the way it interacts with exceptions, is the only system I've seen that gets this right for the nested case.

Post reply on HN