Live data from Hacker News

Why should I have written ZeroMQ in C, not C++ (2012)

250bpm.com

151–160 of 170 posts

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#151

Earlier quoted context omitted.

> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=…

Well, rarely done isn't the same as never done. docs.rs has exactly the tradeoffs the parent comment is talking about: we have a long-running daemon thread that uses `catch_unwind` and builder threads that occasionally panic. We've had [issues with memory leaks]( https://github.com/rust-lang/docs.rs/issues/656 ) in the past - they weren't related to unwinding that I know of, but it's still possible that they were. Ho…

> However I'm not in favor of the proposed solution - if the docs.rs server aborted every time a thread panicked we would have a lot of outages!

Where is the proposed solution?

Where and why does docs.rs unwind from destructors?

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#152

Earlier quoted context omitted.

Conceptually, panics should be rare, because one firing means that some sort of unexpected problem has occurred. However, the real world is not "conceptually." I don't think there's any real data about how often they happen, but at least my experience is that tools I write in Rust rarely end up showing me panic output. > That's not the case in C++ where "throw" is a keyword you are expected to use for error handling.…

A fun example here is rust-analyzer: we implement cancellation via unwinding. This is not technically a panic, but the mechanism is the same, and it more or less is invoked every time a user types something in a file.

Where and why does rust-analyzer unwind from destructors ?

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#153
post #85
post #22

1. That "C equivalent" for error handling is not an equivalent at all. If one could handle the error in the same function then there is no reason to throw. The "C equivalent" is returning an error code, which has other problems. 2. One can handle errors in initialization without exceptions and half-initialized objects: Make your constructor private and expose a static member function that returns an optional (where T…

Completely agree. I was about to write something along these lines but you did a great job. :-) In a decade using C++, I haven't encountered these problems. I never use exceptions and I do exactly what you suggested in #2: if construction can fail, have a factory method and a private constructor (I use a simple ValueOrError type, rather than optional , to be able to communicate information about the error, though: ht…

I like the way how google does it with StatusOr and some status macros

https://github.com/protocolbuffers/protobuf/blob/master/src/...

https://github.com/protocolbuffers/protobuf/blob/master/src/...

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#154

Earlier quoted context omitted.

In Rust, this destructor is perfectly fine: impl Drop for Foo { fn drop(..) { self.cleanup(); self.free_memory(); } } even if `self.cleanup()` panics. When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. If there is a `catch_unwind` somewhere, these leaks will grow over time. The same code in C++: ~foo() { this->cleanup(); this->free_m…

> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=…

> It will be leaked if panic=unwind and if you use catch_panic.

An uncaught panic terminates the thread. If that's the main thread the process terminates, but on other threads it continues with leaked memory, even without `catch_unwind`.

> catch_panic is not used very much.

Are you sure about that? I'd expect most multi-threaded (web)servers to continue after a panic. Otherwise any bug causing a panic (common in my experience) would force a potentially expensive restart of the server.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#155
post #140

Earlier quoted context omitted.

I was going to talk about some panics we've had recently, but those will hopefully be fixed soon so I don't think that quite fits my message. Instead I want to talk about why 'handling errors correctly and not panicking' wouldn't work in general. In order for that work, we'd have to have 0 panics - not just few, but none. That requires none of our code to panic, none of our dependencies to panic, and none of our uses…

Look how Erlang handles this. Crashing on error is the encouraged policy. A managing process will notice a crashed process and restart it. Basically crashing is the safe way to release all resources in a problematic situation, at the cost of terminating the process. It's easy when you don't have shared resources at all (Erlang's case), and harder with threads: one threads crashes and frees its resources, another trie…

I think we're saying the same thing from two different perspectives :) The 'managing process' is the daemon thread. The 'crashed process' is a worker thread. There's no need to worry about corrupted memory since all state is shared through the database.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#156

Earlier quoted context omitted.

> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=…

> It will be leaked if panic=unwind and if you use catch_panic. An uncaught panic terminates the thread. If that's the main thread the process terminates, but on other threads it continues with leaked memory, even without `catch_unwind`. > catch_panic is not used very much. Are you sure about that? I'd expect most multi-threaded (web)servers to continue after a panic. Otherwise any bug causing a panic (common in my e…

> If that's the main thread the process terminates, but on other threads it continues with leaked memory

Yep, my bad, I simply made a mistake here.

> Are you sure about that? I'd expect most multi-threaded (web)servers to continue after a panic.

The two main use-cases for panics seem to be:

1. web servers 2. FFI, since panic across the boundary is UB

On 1, well, this is actually a contentious point. In general, the web world has moved more and more to more and more disposable web servers. You have to be resilient to something killing your web serer process, so you need the infrastructure here anyway. Servers re-starting isn't very expensive because you have a bunch of them already, and they don't start serving requests until they're up. Of course, "defense in depth" is a good idea, so doing both makes more sense than just one, but you can't get away with just catching panics if you want a robust service. And, a lot of web servers are single threaded these days....

Regardless, while web services are a big market for Rust, they're only one thing that it does, so I still think of this as "not that popular." Maybe that's wrong :)

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#157

Earlier quoted context omitted.

> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=…

> Fallability in Rust is spelled "Result ", and drop does not return one. This isn't really true. All Rust functions that can panic are fallible, independently of whether their return type is `Result` or not. There are idioms to use `Result` for recoverable failures and panics for "harder-to-recover" failures, but that's about it, and people do use `catch_unwind` on `main` to make their web-servers live forever, and…

> This isn't really true. All Rust functions that can panic are fallible,

Yes, in the abstract sense of fallible, but not in the terminology of Rust, or how the features are used generally. Defaults and language matter, and we've seen the actual usage follow. I don't ever remember seeing a crate suggest catch_unwind for error handling.

> our program has multiple threads, and resources leaked by one thread continues to be leaked

Yep, my bad, I simply made a mistake here.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#158

Earlier quoted context omitted.

> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=…

> It will be leaked if panic=unwind and if you use catch_panic. This isn't fully accurate. It is leaked if you use `panic=unwind` and the panic unwinds the function. At that point, the program is still running, but the memory is unreachable through a pointer in the program, and therefore leaked. Whether you actually catch the panic afterwards doesn't matter. Not catching it will terminate the program, but then the pr…

(This comment is basically a duplicate of one of your other ones)

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#159

Earlier quoted context omitted.

> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=…

> There isn't, which is why it isn't done, which is why your point confused me :) This shows why it was a bad decision. It allows doing something in the language (unwinding from destructors), that a lot of code needs to protect against (e.g. all the standard library collections, all collections in general, all types that own a resource, etc.), for absolutely no added value (doing that isn't a useful thing to do). Abo…

> for absolutely no added value (doing that isn't a useful thing to do).

You yourself said that web servers are a place where this is a good idea.

> That doing that was a good idea was known (CERT C++ requires non-throwing destructors), and C++ actually made a backward incompatible change in C++11 to fix that (making destructors `noexcept(true)` by default, e.g., see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n316...).

Again, throwing in C++ and panicking in Rust are used very differently idiomatically, and in practice, due to the double panic issue, as I mentioned, this is effectively the same for Rust. Yes in theory you can panic in Drop but it will often end in an abort, so doing it for some kind of recoverable problem makes nearly no sense.

Re: Why should I have written ZeroMQ in C, not C++ (2012)

#160

Earlier quoted context omitted.

> There isn't, which is why it isn't done, which is why your point confused me :) This shows why it was a bad decision. It allows doing something in the language (unwinding from destructors), that a lot of code needs to protect against (e.g. all the standard library collections, all collections in general, all types that own a resource, etc.), for absolutely no added value (doing that isn't a useful thing to do). Abo…

> for absolutely no added value (doing that isn't a useful thing to do). You yourself said that web servers are a place where this is a good idea. > That doing that was a good idea was known (CERT C++ requires non-throwing destructors), and C++ actually made a backward incompatible change in C++11 to fix that (making destructors `noexcept(true)` by default, e.g., see http://www.open-std.org/jtc1/sc22/wg21/docs/papers…

> You yourself said that web servers are a place where this is a good idea.

I said that "panicking from Drop::drop is a bad idea, but that panicking is a good idea in general (e.g. in web servers)".

We are in agreement that panicking is a good idea in general, but you seem to be turning that around, arguing that "Panicking is a good idea in general, therefore panicking from Drop::drop is a good idea". One does not follow from the other.

If you believe that unwinding from Drop::drop is a good idea, enumerate the value this feature adds, its costs, and make a case about why this trade-off is worth it.

All languages with unwinding in the same space as Rust do not allow unwinding from destructors, because it adds no value, and adds significant costs. For example, C++ and D do not support it. C++ used to support it, like Rust, but considered the value it added as "negative" (that code deserved to be broken), and changed its semantics to forbid this by default. AFAICT, the same arguments that apply there, apply 1:1 to Rust.

In Rust, the cost of this feature is real, e.g., from basic impls like the impl of `Drop` for slices, reused by most collections, to pretty much every Drop impl of every type guarding an important resource in a module that uses unsafe (e.g. the many Iterator drop guards, etc.).

So I stand by my original claim: panicking from Drop::drop is a bad idea: there are no worthy use-cases for it, its costs are real, pervasive, and unnecessary, because in practice nobody does this, but every writer of unsafe code must write code to defend from it, which makes trickier code even trickier to write, read, and properly test.

C++ is the sane language here. Rust is not.

Post reply on HN