7 ms·
Odin language creator here. Thank you to the author of the article for taking the time to try out both Odin and Zig. I want to clarify my position as the comm
by gingerBill 5y ago
Odin language creator here.
Thank you to the author of the article for taking the time to try out both Odin and Zig.
I want to clarify my position as the comments you are commenting on are lacking a little nuance.
For the first one regarding OOM, Odin's allocators all support error values signalling things such as `Out_Of_Memory`, `Invalid_Pointer`, `Invalid_Argument`, and `Mode_Not_Implemented`. So if you you want to handle those error states, there is nothing preventing you from doing so in Odin. However, there are two aspects to my original argument: you should know the constraints of the platform you are on and plan accordingly as it is part of your problem; what happens in the case when you have completely ran out of memory (globally/system-wide)?
> My reaction to that is “640KB is enough for everything”, right?
I've worked on systems with 16KiB and that's just how much you had to deal with. Meaning we had to plan accordingly for what we needed so that we never ran out of memory. The program was heavily tested (both analyzed and empirically) so that we made sure that it ran within the constraint of 16 KiB. On such platforms, `malloc` was never called, and in fact banned.
> I have never had a program cause a system to run out of memory in real software (other than artificial stress tests).
In this comment, I say "program" and not _allocator_ here. I have many allocators that run out of memory _on purpose_. On desktop applications that I have worked on (I know this does not apply to all), if we did run out of memory, the better option is to just crash rather than try to recover. But of course some applications cannot do this, especially when dealing with third-party programs, such as databases (as you state). In those cases, the best you can do is empirically test how much is required and gracefully handle those cases when you do OOM (if possible). That's part of your problem, it's not an external thing. If you can control things, try to.
Minor note: Zig's allocators only return one possible error value `error.OutOfMemory`, whilst Odin has 4 possible error values meaning there is a finer granularity in Odin with its error value system for its allocators.
Onto the second comment you bring up regarding error value handling.
> ...My code, which is calling the service, need to be able to handle any / all of those
Yes. And you should handle them accordingly, and not just in a general "catch all", especially since these are wildly different kinds of failure states.
> ...Odin relies on the multiple return values system. We have seen how good that is with Go. In fact, one of the most common issues with Go is the issue with how much manual work it takes to do proper error handling.
Regarding error value propagation, I needed to clarify my position a little more in that article I was referring to an aggregate set of comments from a GitHub post and not a truly nuanced argument. Odin has the `or_return` operator which allows the user to easily propagate values in code. It is similar to Rust's `?` or Zig's `try` but complements Odin's multiple return values AND does not rely on having a concept of an "error value type".
Demo of `or_return` <https://github.com/odin-lang/Odin/blob/fd256002b3190076bb91ec3e02ae17c858222eb5/examples/demo/demo.odin#L2030 https://github.com/odin-lang/Odin/blob/fd256002b3190076bb91e...> And check out Odin's [`core:math/big`](https://github.com/odin-lang/Odin/tree/master/core/math/big https://github.com/odin-lang/Odin/tree/master/core/math/big) which has big extensive use of the concept.
Even though I added this concept to Odin, I do believe that my general hypotheses are still correct regarding exception-like error value handling. The main points being:
* Error value propagation _ACROSS_ library bounds
* _Degenerate states_ due to type erasure or automatic inference
* Cultural lack of partial success states
The most important one is the degenerate state issue, where all values can generate to a single type. It appears that you and many others pretty much only want to know if there was an error value or not and then pass that up the stack, writing your code as if it was purely the happy path and then handling any error value. Contrasting with Zig, Zig has a built-in concept of an error value type, and all error values are either inferred `!`, have specific error set, or degenerate to `anyerror`. In practice from what I have seen of many Zig programmers, most people just don't handle error values and just pass them up the stack to a very high place and then pretty much handle any error state as they are all the same degenerate value: "error or not". This problem occurs in Go code too because everyone degenerates to the error interface in practice, therefore it's now equivalent to a fancy boolean.
For error values in Odin, you can use any data type you require for your problem, and even compose many together. It's very common to just use an `enum` or even a boolean, but sometimes you need something more complex or aggregate too. A good example of this is when you need to aggregate different possible error values from multiple different data types. The two common approaches are to make a mapping procedure to convert them to the specific `enum` OR to have a (discriminated) `union` of the `enum`s. This means Odin does not suffer from the degenerate state issue that Zig, Go, and many other languages do suffer from. This is because Odin doesn't have a built-in degenerate type which would be typically used for error values.
One issue with Zig's approach is due to its error value inference system, it's very difficult to know what possible errors values a procedure returns just from __reading__ the code (assuming `!T` or `anyerror!T` is used), you have to compile the code in order to determine that. This may be okay for a single developer, but if you are on a larger team you are now either relying on external documentation (which Zig can provide) or the compiler telling you this. With a specific (error) set this is not a problem because it's explicit to the reader.
Most of the time you are reading code, not writing code.
Odin's type system and feature set is rich enough that you can achieve everything you want _extremely cleanly_. You can many parallels between Odin and Zig: `errdefer` would be akin to `defer if err != nil {` in Odin, `try` would be akin to `or_return`, etc.
n.b. Someone recently wrote an article which is closer to my own position on error value handling (with some of the author's own flair and preferences) here: <http://tuukkapensala.com/files/software_does_not_contain_errors.txt http://tuukkapensala.com/files/software_does_not_contain_err...>
Thank you again for writing this article. And as I always say, I highly recommend people try out as many tools (which includes programming languages) as they can to find what are the best tools for your problems!
Regards,
Bill
- abainbridge 5y agoI read the article and found myself saying "No, no, no!". Then I read your response here and completely agree with it. How does this difference come about? There's no way to prove who is right. Maybe people's experience of different domains is part of the cause. But mainly I believe the difference comes about due to the different way people's brains work. I think the most harmful thing you can do to a team is mix people together who naturally have different approaches. This aspect never seems to be tackled directly by the interview process.
- gingerBill 5y agoI'm not even saying my approach is "the best" but that there is no silver bullet. Different people and teams will benefit from different styles and approaches. Pluralism in thought and design is better for finding good approaches to things. If people prefer Odin's approach of Zig's approach or vice versa, that's fine and more power to them.
- jstimpfle 5y agoIt's less subjective than that. I find exceptions to sort-of work in "boring" software (short running programs like scripts, or systems comprised of short running processes), or in software with low quality standards (sorry to be blunt). It's nice that I can bang out a 100 line Python batch script, and catch and transform that one exception, and otherwise just be happy when the script quits automatically because a file could not be created or some input data was malformed. Whatever, I'll "rm -rf" my temp directory and re-try. Exceptions are also popular in enterprise software - all issues with real-world business logic aside, my idea is that enterprise software doesn't have a lot of complex state after all. There is a domain model that is pretty much single-state as far as most operations on it are concerned. Normally errors are not expected to happen, and for some exceptional situations programmers simply don't care about writing error handling code - the GC will probably clean up to a reasonable state to serve the next request, it will do so in reasonable time, and all will probably just work, and nobody needs to understand what went wrong. (Maybe someone will later have a manual look at the stack trace from when the exception was thrown). If you're not a "happy path" programmer and need control over what situations are handled and when, I don't see that there is a good way with exceptions - because exceptions take away control (control flow) and take away detailed error information as gingerBill described. In my perception (while I don't read a lot of code and certainly not a lot of GC'ed code) most code never cares to catch exceptions, because it's inconvenient, and because that was the idea behind exceptions. Hope I made a reasonable argument. Can't see anything subjective about it.