Live data from Hacker News

Rust RAII is better than the Haskell bracket pattern

snoyman.com

131–140 of 156 posts

Re: Rust RAII is better than the Haskell bracket pattern

#131

Earlier quoted context omitted.

I can see why you might think that, being built into the language, using 'with' in Python in a broken way would be easier to spot. However, having used both languages extensively, I can tell you that, at least for me, there's no discernible difference. I think the reason for this is might be that, in Haskell, a function starting with 'with' is, by convention, using the bracket pattern and the way that you might use s…

I only mean it's visibly more obvious, you have an indented block... what is the purpose of the indented block unless to say "do all your stuff with the resource _inside_ this block". Using the with block is very 'intentional' feeling. I'm not very familiar with Haskell but it seems like you'd get used to the type system telling you everything you need to know. But in this case it doesn't. In Python world we talk abo…

> I only mean it's visibly more obvious, you have an indented block...

Haskell is more similar than you realise, it's the difference between this:

    withSomeResource $ \resource -> do
      someFunctionOn resource
and this:

    with some_resource() as resource:
        some_function_on(resource)
> I'm not very familiar with Haskell but it seems like you'd get used to the type system telling you everything you need to know

As an outsider, you might expect a type-error to mean that you made a logic error, in practice it usually means you made a typo.

What happens is that the type system forces you to write things in a certain way. You internalize its rules and it moulds your style. You don't try random things until they stick, you write code expecting it to work and knowing why it should, just like you would in Python. It's just that more of your reasoning is being verified. "Verified" is the operative word here - the type system doesn't tell how to do anything.

> it seems like it's maybe quite unhaskellish to have to rely on a naming convention and remembering not to use the return value of the function?

The Python equivalent of the problem here would be:

    current_resource = a_resource

    with some_resource() as resource:
        current_resource = resource

    current_resource.some_method()
So it's not that using the return value of the withSomeResource function is a problem, it's the resource escaping from the scope where it is valid.

I think the crux of our discussion is about checked vs unchecked constraints.

When you work on (successful) large codebases, whether in a static or dynamically typed language, there are always rules about style (and I mean this in a broader way than how your code is laid out). For example, in large Python projects, there might be rules about when it is acceptable to monkey-patch. These rules make reasoning about the behaviour of these programs possible without having to read through everything.

Large Haskell projects also have these rules, but Haskellers like to enforce at least some of them using the type system. It takes effort to encode these rules in the type system and it is more difficult to write code that demonstrably follows the rules than implicitly follows them, but the reward for this effort is that it gives you some assurance that the rules are actually being followed everywhere.

For some rules this extra effort makes sense and other times it doesn't. The type system is just another way to communicate intent. Writing the best Haskell doesn't necessarily mean writing the most straight-jacketly typed Haskell, but it does give you that option. Beginners often fall into the trap of wanting to try out the new-and-shiny and making everything more strict than is helpful.

For one-man projects, there's really no advantage to Haskell over Python (with the caveat that you may not remember all of the intricacies of your code in six months and using Haskell you may have encoded more of your assumptions in the type system).

Re: Rust RAII is better than the Haskell bracket pattern

#132

Earlier quoted context omitted.

Haskell has such an extensive set of language extensions, I would say adding new features to the type system is probably the MOST Haskell-ish way of doing things. The explicit purpose of Haskell is to be a basis for research into functional language design (edit: among other purposes). By "explicit purpose" I mean exactly that... people got together in 1987 to come up with a language for research. Haskell was never s…

I do normal, boring line-of-business programming in Haskell every day. I think Haskell does have a good model for bringing together practical application of theoretical research. Parent's comment is spreading the myth that Haskell is an academic language. It's not wrong but it's not Haskell's only stated purpose or utility by far.

For what its worth, when I read the parent comment, I did not at all get the impression that it was "spreading the myth that Haskell is an academic language."

Re: Rust RAII is better than the Haskell bracket pattern

#133
post #9

Earlier quoted context omitted.

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". :)

Or ye olde “register a callback and never remove it”.

Re: Rust RAII is better than the Haskell bracket pattern

#134

Earlier quoted context omitted.

As I mentioned in my other reply to Steve Klabnik, documenting this edge case would have been a sufficient “resolution” to the bug at hand. I may call it a bug, and you may call it undocumented silent data loss behavior; either way, we’re talking about the same thing. Silent data loss from undocumented behavior is not good, wouldn’t you agree? I certainly was not aware that drops in Rust could throw away potentially…

I mean, I guess it's not documented as well as it could be, but the only alternative to dropping close errors would be panicking, and that would be significantly worse. Destructors aren't supposed to fail.

> the only alternative to dropping close errors would be panicking, and that would be significantly worse.

There are plenty of cases where you'd prefer an application to crash upon an unhanded write error, rather than silently losing data that could be highly important and irrecoverable.

(Of course, actually handling the errors is preferred above both.)

> Destructors aren't supposed to fail.

But isn't the whole point of this discussion is that the destructor of fs::File (and probably any other buffered IO writer) can and does fail in some cases?

Re: Rust RAII is better than the Haskell bracket pattern

#135
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…

> In what sense do you disagree?

Not the parent, but it is trivial to write C++ and Rust examples in which destructors of variables with block scope are not called. The std library of both languages do even come with utilities to do this:

C++ structs:

    struct Foo {
      Foo() { std::cout  foo;
        new(&foo) Foo;
        /* destructor never called even though a Foo
           lives in block scope and its storage is
           free'd
        */
    }
C++ unions:

    union Foo {
      Foo() { std::cout 
Rust:

    struct Foo;
    impl Drop for Foo {
        fn drop(&mut self) {
            println!("drop!");
        }
    }

    {
      let _ = std::mem::ManuallyDrop::::new(Foo);
      /* destructor never called */
    }
etc.

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

This is pretty much why it is impossible for a programming language to guarantee that destructors will be called.

Might seem trivial, but even when you have automatic storage, any of the things you mention can happen, such that destructors won't be reached.

In general, C++, Rust, etc. cannot guarantee that destructors will be called, because it is also trivial to make that impossible once you start using the heap (e.g. a `shared_ptr` cycle will never be freed).

Re: Rust RAII is better than the Haskell bracket pattern

#136

Earlier quoted context omitted.

Furthermore, it's not clear that this can really be implemented in a reasonable way Which is why RAII in a language without exceptions is inappropriate for a resource which has a status on closeout.

The alternative to closing the file in the destructor is leaking OS resources in the event of unexpected control flow destroying the file object. I fail to see how that is preferable.

A linear resource would require that the resource is explicitly released on all codepaths no?

Re: Rust RAII is better than the Haskell bracket pattern

#137

Earlier quoted context omitted.

The alternative to closing the file in the destructor is leaking OS resources in the event of unexpected control flow destroying the file object. I fail to see how that is preferable.

A linear resource would require that the resource is explicitly released on all codepaths no?

Not in Rust, and not in any language where you can put resources in objects with shared ownership (i.e. any remotely popular general purpose language).

Throwing exceptions isn't a particularly good solution either, for the same reason. Exceptions are hard to reliably handle when you can't easily reason about where they will be thrown from.

Re: Rust RAII is better than the Haskell bracket pattern

#138

Earlier quoted context omitted.

I mean, I guess it's not documented as well as it could be, but the only alternative to dropping close errors would be panicking, and that would be significantly worse. Destructors aren't supposed to fail.

> the only alternative to dropping close errors would be panicking, and that would be significantly worse. There are plenty of cases where you'd prefer an application to crash upon an unhanded write error, rather than silently losing data that could be highly important and irrecoverable. (Of course, actually handling the errors is preferred above both.) > Destructors aren't supposed to fail. But isn't the whole point…

> There are plenty of cases where you'd prefer an application to crash upon an unhanded write error, rather than silently losing data that could be highly important and irrecoverable.

Not in the programs I write. I grant that perhaps this should be configurable.

Re: Rust RAII is better than the Haskell bracket pattern

#139

Earlier quoted context omitted.

I do normal, boring line-of-business programming in Haskell every day. I think Haskell does have a good model for bringing together practical application of theoretical research. Parent's comment is spreading the myth that Haskell is an academic language. It's not wrong but it's not Haskell's only stated purpose or utility by far.

I used to do normal, line-of-business programming and I stand by the comment. If it sounds like I'm saying that Haskell is not useful for boring, line-of-business programs then I wasn't clear... Haskell is a research language, yes, and not exclusively so. But I'm confused why it's objectionable to spread a "myth" if that myth is, in your words, "not wrong". The stated purpose of Haskell, when it was created, is a mat…

The myth that gets circulated by critics of Haskell is that it is an academic language and has no practical use in industry.

I think your post was unclear and supported that myth. After reading your reply I understand better what you meant!

I agree -- extensions do seem to be working rather well. I hope the new Haskell standard, Haskell2020, will include some of them into the language proper!

I'm looking forward to seeing how linear types work/interact with the rest of the language.

Re: Rust RAII is better than the Haskell bracket pattern

#140

Earlier quoted context omitted.

I mean, I guess it's not documented as well as it could be, but the only alternative to dropping close errors would be panicking, and that would be significantly worse. Destructors aren't supposed to fail.

> the only alternative to dropping close errors would be panicking, and that would be significantly worse. There are plenty of cases where you'd prefer an application to crash upon an unhanded write error, rather than silently losing data that could be highly important and irrecoverable. (Of course, actually handling the errors is preferred above both.) > Destructors aren't supposed to fail. But isn't the whole point…

> But isn't the whole point of this discussion is that the destructor of fs::File (and probably any other buffered IO writer) can and does fail in some cases?

And the choices to handle such failed destructors are: blockingly retry until the problem goes away or just plain ignore it. Either way you can't rely on destructors for persistence/durability in case of a crash or power loss.

Post reply on HN