Hacker News new | ask | show | jobs
by WalterBright 1 day ago
It's been a while since I looked at Zig. I couldn't say anything intelligent about it without some study.

I used to be a big fan of C++ exceptions, but eventually soured on it for various reasons. Here's an article partially addressing it from a while back:

https://dlang.org/articles/exception-safe.html

1 comments

I think there is nothing better than exceptions + RAII for error handling since exceptions cannot be ignored by accident.

I would classify D's scope exit/failure/success as RAII actually, even if D uses a GC.

Sometimes you might not need exceptions and something like std::expected or optional is better.

In my case I use expected for some network APIs since I expect failures to happen out of my control aspart of the flow of my program, but I do not see why I would not use exceptions in many other situations, such as for non-ignorsble errors. I could think of a lack of disk space or some other fatal error thst is not under the control of the program.

If you forget to handle this, the error will cascade.

Also, exceptions do not make the signature of a function change (at least not in C++, Java checked exceptions is different). This means that the plasticity for adding errors at any depth of the call stack augments without bypassing any error silently.

All in all, I would say exceptions should be the main mechanism in normal circumstances and for expected errors you csn use error/result types.

The worst thing about exceptions is that you can't tell from the type signature of a function whether it might throw one. So you have to hope it's documented, guess at whether try-catch is necessary, or reading through the entire call stack.

I personally much prefer Rust style Result which also can't be ignored, and puts fallibili5y in the function signature.

> The worst thing about exceptions is that you can't tell from the type signature of a function whether it might throw one.

In Java exceptions can be part of a method's declared type information, so handling is checked at compile time and IDEs can display the info.

Unfortunately certain Java missteps made this design unpopular these days. For example, for many years new String(bytes, "UTF-8"); made it mandatory to catch a UnsupportedEncodingException despite the language spec guaranteeing UTF-8 would be available. A later version of Java addressed it, but still...

Or you can assume exceptions can be thrown and capture and log coming from a base class in a centralized place (and probably refine incrementally what to do with each group without dirtying all the logic inside the functions themselves).

For Rust result in C++ you have optional and expected. But they have their own problems, like rewriting function signatures all the way up the stack if you notice an error was possible when adding code.

But with that design, you’re quite limited in what you can usefully do once you catch that exception.
Well, I am not sure what you mean exactly.

But it enables incremental addition of errors and you can decide later what to do exactly, so I still tend to see it as an advantage.

It is like defer the error handling and centralize later.

If you want to handle an error in place immediately, prpbably it is not an exception what you want.

But if you need it is a fatal error, I do not see any harm, since the most you will do with that exception is to unwind and log or telemetry or whatever to tracke the errors.

Any function can throw an exception unless it is marked noexcept.

Your code shouldn't need to make assumption about whether exception are being thrown or not.

Your mistake is thinking that a function potentially throwing means you need to catch it.

> I would classify D's scope exit/failure/success as RAII actually, even if D uses a GC.

D's scope exit/failure/success is built on top of RAII and has nothing to do with the GC.

> I would say exceptions should be the main mechanism in normal circumstances and for expected errors you csn use error/result types.

Come to DConf (or watch the live stream) and I hope I can change your mind, or at least challenge your conclusions!

> D's scope exit/failure/success is built on top of RAII and has nothing to do with the GC.

What I meant here (I do not know the mechanism) is that I am aware that D has a GC. This means, correct me if I am wrong, that D does not use destructors (like C++) for scope exit/failure/success, though they are scoped mechanisms (RAII-like).

> Come to DConf (or watch the live stream)

I always follow D (even if I did not use it intensively). I have extremely high respect for you as a language designer and as a technician. Your knowledge is very refined, so I never take your opinions lightly.

Probably one of the most knowledgeable persons in the industry for native language design. However, I am far and cannot attend, so I will definitely watch it.

> or at least challenge your conclusions!

No problem in challenging them. I am always open to do things better. This is just my practical experience as I implement stuff so far.

I currently think exceptions seems like a good default mechanism (apparently at least) compared to alternatives. But I also think other mechanisms have their place and can/should be used even in the same program, just for different things.

All mechanisms have trade-offs. I just talked about defaults from my POV for the kind of software I develop (mostly server-side, some client-side, and not embedded in the extremely constrained sense).

-----------------------------

One question: I tried to use D several times in my career (20 years of C++ experience here, roughly). I found it a lot of fun. I like it better than more "modern languages". I like the plasticity D provides. But I am not sure how it would work if I want to do:

   1. backend (server-side mainly, Linux is main platform, x86-64)
   2. desktop + android and iOS (being mobile more important than desktop)
   3. web assembly (even more important than mobile at this moment, could change).
   4. backwards compatibility (heard complaints of versions breaking stuff frequently around).
   5. interaction with C++.
I do like D a lot, but the problems I had before were more related to tooling (autocomplete, I use mainly Emacs but did not try for a few years) and toolchain maturity.

It would be ready for all of the above? Also, very very handy would be to wrap C++ code and C code. I saw that D had an ongoing effort for C++ compatibility better than other languages, but I am afraid it could be half-broken. Even if it is, it is documented what works and what does not work? That would help a lot.

Thanks.

> D does not use destructors (like C++) for scope exit/failure/success

It does, but the user doesn't see them.

There are people working on 1, 2 and 3.

> 4. backwards compatibility (heard complaints of versions breaking stuff frequently around).

We listened, and cut way back on breaking changes. Now we have editions.

> 5. interaction with C++.

C++ has gotten to the point where it would take another decade of my time to wire in ImportC++. However, if you stick with C++'s C interface when interfacing with D, you should be fine. D will also work with C++ name mangling, but all the complexity of C++ templates and such is just too much.

And thank you for the kind words! I definitely appreciate it.

> I am far and cannot attend, so I will definitely watch it.

Wonderful!

Regarding web assembly, support is limited. There's been several efforts to support WebAssembly in D but they were one man efforts that never got finished and merged to the mainline D. The only support there is in the official D compiler is for the BetterC mode, which is a minimal mode with no garbage collection, no runtime and effectively no standard library. There's been some people doing some interesting projects in it but you have to reimplement a lot of basic stuff such as data structures just to get going and at that point you might wonder why not just use C/C++ instead.

There is a fork of D compiler called OpenD which has some WebAssembly support with garbage collection and standard library. I've used it, it works for the most part, but there's bugs and if you encounter any issues then you're on your own.

> effectively no standard library

The C standard library works well with it.

> I would classify D's scope exit/failure/success as RAII actually, even if D uses a GC.

It has better than that, but it is a bit clunky compared with C++(just use struct instead of class). You can have proper destructors, but if you have a container you need to be more careful than in C++.

In D exceptions are the default like in C++ and you have to opt out with nothrow or extern(C) or betterC.

Really try some D code in a bigger situation it is 90% good and almost worth using over C++, but if you can't have GC it is a huge pain, but better than C.

I've never understood why GC would be a huge pain. It makes some things a lot easier, like Compile Time Function Execution.

Also, recently the GC implementation received a big modernization upgrade.