Live data from Hacker News

Rust RAII is better than the Haskell bracket pattern

snoyman.com

21–30 of 156 posts

Re: Rust RAII is better than the Haskell bracket pattern

#21
post #7

Earlier quoted context omitted.

> 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 s…

If you are consuming an API that provides an object with a destructor, you are correct, you can determine when destructors will be called.

The issue is when you produce an API that contains objects with destructors. Since you are handing these entities off to unknown code, you cannot ensure that they will be dropped. This was a problem in scoped threads in Rust.

Re: Rust RAII is better than the Haskell bracket pattern

#22
post #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.

> 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?

That is unclear. Currently, `File::drop` ignores all errors and drops them on the floor ([unix], [windows]). This is a concern both long-standing and ongoing[0].

AFAIK discussion has gone no further than https://github.com/rust-lang-nursery/api-guidelines/issues/6...

[unix]: https://github.com/rust-lang/rust/blob/master/src/libstd/sys...

[windows]: https://github.com/rust-lang/rust/blob/master/src/libstd/sys...

[0] https://www.reddit.com/r/rust/comments/5o8zk7/using_stdfsfil...

Re: Rust RAII is better than the Haskell bracket pattern

#23
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. However, this is different than the bracket pattern that the article is taking about. No one in the Haskell community advocates cleaning up resources (like file descriptors, etc) using only destructors.

You misunderstood me. I'm explaining why simply adopting RAII is inappropriate in Haskell, even though the author thinks it's a better approach. I've edited my comment to make this clearer.

Re: Rust RAII is better than the Haskell bracket pattern

#24
post #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.

The error handling in close is platform specific, so you have to convert the file into a raw fd/handle and then pass it to the appropriate libc methods.

Re: Rust RAII is better than the Haskell bracket pattern

#25
post #19
post #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.

I don't think Rust can notify on a failing destructor other than panic!ing. AFAIK the best you can do if you want to handle errors on close is to call `flush()` (which does return errors) before dropping the object. Of course that nullifies the benefits from RAII. I don't know if there's an elegant way to solve this. If Rust had exception you could use that but then again in C++ it's often explicitly discouraged to t…

> I don't think Rust can notify on a failing destructor other than panic!ing.

Much as in C++, this is not really allowed: drop runs during panic unwinding, a panic during a panic will hard-abort the entire program.

> I don't know if there's an elegant way to solve this.

I don't really think there is. Maybe opt-in linear types could be added. That would be at the cost of convenience (the compiler would require explicitly closing every file and handling the result, you could not just forget about it and expect it to be closed) but it would fix the issue and would slightly reduce the holding lifetime of resources.

Furthermore, for convenience we could imagine a wrapper pointer converting a linear structure into an affine one.

> How does Python's "with" handle that?

You'll get the exception from `__exit__` chaining to whatever exception triggered it (if any). Exceptions are the normal error-handling mechanism of Python so it's not out of place.

Re: Rust RAII is better than the Haskell bracket pattern

#26
post #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…

The most obvious ways to leak are calling `Box::leak` which is also a very useful API and `mem::forget` (the latter is mostly useful for working with unsafe code).

Re: Rust RAII is better than the Haskell bracket pattern

#27
post #19

Earlier quoted context omitted.

I don't think Rust can notify on a failing destructor other than panic!ing. AFAIK the best you can do if you want to handle errors on close is to call `flush()` (which does return errors) before dropping the object. Of course that nullifies the benefits from RAII. I don't know if there's an elegant way to solve this. If Rust had exception you could use that but then again in C++ it's often explicitly discouraged to t…

> I don't think Rust can notify on a failing destructor other than panic!ing. Much as in C++, this is not really allowed: drop runs during panic unwinding, a panic during a panic will hard-abort the entire program. > I don't know if there's an elegant way to solve this. I don't really think there is. Maybe opt-in linear types could be added. That would be at the cost of convenience (the compiler would require explici…

>Much as in C++, this is not really allowed: drop runs during panic unwinding, a panic during a panic will hard-abort the entire program.

Right, I didn't really consider that a "drawback" because I'm in the camp that considers that panic! shouldn't unwind but actually abort the process here and there anyway. But you're right that if you rely on the default unwinding behavior panic!ing in destructors is a very bad idea.

Re: Rust RAII is better than the Haskell bracket pattern

#28

Earlier quoted context omitted.

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 s…

If you are consuming an API that provides an object with a destructor, you are correct, you can determine when destructors will be called. The issue is when you produce an API that contains objects with destructors. Since you are handing these entities off to unknown code, you cannot ensure that they will be dropped. This was a problem in scoped threads in Rust.

Can you please dig deeper, that I am not sure I follow.

In which case in rust you cannot be sure that "the drop" will be called?

Re: Rust RAII is better than the Haskell bracket pattern

#29
post #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 re…

ye olde "stick it in a hash map and forget about it". :)

Re: Rust RAII is better than the Haskell bracket pattern

#30
post #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.

Python's "with" construct is analogous to the bracket pattern in Haskell that the article is talking about. It also works in the nested case in the presence of exceptions. Furthermore, the issue that Michael has with the bracket pattern in Haskell can also happen in Python.

True, but in Python the coding mistake would stand out much more because the with block is syntax sugar - it does not look like regular function application, whereas in the Haskell example there is nothing to tell you that withMyResource is using the 'bracket pattern' (except by reading the src)

Also I guess in Haskell there is more expectation that the type system should prevent you from expressing runtime errors

Post reply on HN