I don't see how the Rust approach would avoid this fate but I doubt it will ever be used in these contexts to begin with.
First, you _have_ to do it, even if that means a try! and passing the buck. The syntax for this isn't as monstrous as it is for checked exceptions as well.
Second, it feels more like a natural code-flow, not the break that exceptions provide.
Third, it's useful for more than just "Exceptions". Coupled with Optional types, it provides a more expressive way of not-hapy-path-code where exceptions just feel heavy handed. For instance https://docs.python.org/3/library/stdtypes.html
d[key]
Return the item of d with key key. Raises a KeyError if key is not in the map.
The key not existing isn't really exceptional. A proper optional, union, or result type handles this case much more easily.In sum, I think the expressibility of Result and Option, along with a more natural flow for handling them will make working around them less tempting/viable/easy to pass over in a code review.
I think twenty-five years ago there were similar hopes and dreams for Java checked exceptions.
This is not a problem in Rust.
Yeaaaaaaah. I remember being a very strong supporter of checked exceptions before I got into a giant codebase and saw how not having speced a sane exception type can cause lots of pain and some really long declarations or dropped exceptions.
catch (Exception e) { throw new RuntimeError(e); }
:(You're right, I guess time will tell.
That said, dict also has get(), which lets you specify the default value if key wasn't found (and the default default is None).
No. They are usually logged. The difference is that with exceptions, the logging will occur at a higher level and be done uniformly while in Rust you'll have to explicitly thread the error through the call stack manually to log it at a higher level.
What about non-fatal errors such as retries in a GUI library that loads an image from the web, how are they logged or otherwise propogated to the developer?
Is there currently a convention for dealing with such "encapsulated but interesting" errors occuring in Rust libraries?
It seems obvious to me that a web framework would have a logging hook but non-obvious how that API would function; would it call a logging callback with a severity and a string? Just a string? Or an error message and some kind of "related data" (such as a stack trace or relevant structs) container?
It doesn't seem obvious to me to log only text in the context of a GUI library. I'm thinking of building a native cross-platform GUI library in Rust (borrowing concepts from IUP[0] but adding more typing) and I feel like there would be value in having structured data as part of the nonfatal error interface, so I'm curious if there are existing patterns to learn from.
For logging, the `log` crate[1] provides the interface that libraries can use, which defines macros for each log level.
For partial success/failure, I actually don't think there is much convention. On the one hand, you might consider logging as sufficient enough, depending on your use case. In some cases, I have adopted a form of partial errors. That is, instead of:
fn foobar() -> Result<Value, Error> { ... }
I use fn foobar() -> (Value, Option<Error>) { ... }
You can see an example here: https://docs.rs/ignore/0.1.9/ignore/gitignore/struct.Gitigno... And in particular, the error type is a recursive structure, which permits it to store an aggregation of errors: https://docs.rs/ignore/0.1.9/ignore/enum.Error.htmlSince this isn't something people need too often, the syntactic overhead of this approach is considerably clunkier, so I definitely wouldn't want this to be a pervasive part of a library. Nevertheless, if you can get both a success value and an error value, then your return type is inherently a product, not a sum, which is at odds with the `Result` sum.
Anyway for me exceptions are way easier to work with, than return codes.
Rust has essentially specialized do-notation for error handling called the `try!` macro, or more recently, `?`. (`try!` and `?` are exactly equivalent in today's Rust.) Actually, it does just a bit more than standard do-notation would: it also tries to convert your error value at the call site to the error type expected by the return type of the current function.
The problems posed in the OP are pedagogical ones IMO that I hope can be solved. I think the current resource on error handling in the book is good for folks who really want to dive in and figure out the complete story, but it's bad for folks who just want to write code that works without spending a couple hours doing a deep dive. So I think there's room for more targeted pedagogy here.
No, they're not, not by a long shot. As soon as you start having multiple monads returned by functions and you need to combine them, you need to introduce monad transformers and your code turns into a giant untractactable spaghetti mess.
Exceptions have issues but they are the sanest way to handle errors today.
They are still superior for error handling, exceptions would have a more complex interaction with them.
"Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do."[1]
Do you mean, "this feature is required for me to write code in that language"? Or do you mean, "this feature is required for any project in the language to flourish"?
If the former, why do you think your preferences generalize? If the latter, how do you explain the large number of successful Go projects? Are we all stuck in the 1980s? And if so, what does that even mean?
Frankly, yeah, I think Go programmers are kind of stuck in the 1980s in some respects. This isn't something I'm completely clear about, but I feel like Go's developers are biased against anything that smells at all academic. So for instance they didn't want to implement a super fancy, cutting-edge type system. Which is somewhat understandable... but as a result they ignored 30 years of programming langauge research and implemented a primitive type system that basically provides nothing over C. I do not understand this mentality.
Writing that sort of clever, super-concise code scratches an itch that a lot of people have (myself included) but it's not something I want to encounter when I'm trying to debug something. When you're working with other people's code, you want it to be simple and consistent. That's what Go's primary strength is.
btw, I'll take this opportunity to plug my own "Go generics" solution: https://github.com/lukechampine/ply. It's like a Coffeescript for Go that lets you use stream HOFs like map/filter/reduce without any runtime cost.
> but as a result they ignored 30 years of programming langauge research and implemented a primitive type system that basically provides nothing over C
As someone who also has a decent amount of C experience, I don't see how this is a reasonable conclusion to make. The first point in favor of Go is that it's mostly memory safe (sans data races), which is achieved not just with bounds checks, but with a stronger type system. The second point in favor of Go is that Go does have a limited form of polymorphism that is checked by the compiler. In C, the only way you get polymorphism is by subverting the type system entirely. Both of those points are huge, and there's undoubtedly a much longer list that I could craft of smaller benefits if I were so inclined.
As we march toward complex type systems that move more runtime errors to compile time, we must also be vigilant of the trade offs we're making. Moving things from runtime to compile time isn't necessarily free; there's almost always some new cognitive load that is implied. It's important because if that cognitive load is "too high," then people aren't going to switch to it, and no amount of navel gazing is going to fix that. This entire thread is a perfect demonstration of that trade off in action. On the one hand, we have the "clearly superior" Rust approach to error handling that checks a lot more at compile time than Go does, but on the other hand, Go programmers don't ever need to "learn error handling" at all. They have a simple convention with a reasonably low bug rate (IME, anyway).
We can't just judge programming languages by their theoretical strengths. We must also judge them by their practical strengths. And then we must do the impossible: balance them. Snubbing our collective noses isn't going to do any good. Our only hope is to understand why people are attracted to languages like Go (such as myself) and figure out how to reduce the aforementioned cognitive load without giving up those sweet sweet compile time checks. Rust is clearly pushing that boundary, and I'm happier for it.
I don't think you misjudges the differences in complexity here at all. But wouldn't that have more to do with the different approaches to memory management?
>aforementioned cognitive load without giving up those sweet sweet compile time checks
I would argue the cognitive load comes mostly from nominal typing and lack of type inference. A counter example would be the Crystal language which has a very strong type system, but is exposing barely any of it as 'added cognitive load'.
This correlation between 'cognitive load' and 'expressiveness of a typesystem' seems unfair. Having to keep track of all the patterns that are valid but not supported by the type system is also a type of added (hidden) complexity.
Even the poster-feature of Rust has a (IMO) much easier cousin with the same (and more) advantages called 'the Clean programming language' in the shape of 'uniqueness types'. And most of the complexity in Rust is the result of the type system not being expressive enough for how they are attempting to use it, rather than the opposite.
What makes you say that? Rust's approach guarantees more type invariants.
I don't buy for a second that type inference has much to do with this. Firstly, Rust has type inference, it just isn't global. Secondly, I've found that type inference everywhere makes the cognitive load much worse, not better.
And I wasn't actually drawing a correlation between cognitive load and expressiveness. Namely, expressiveness isn't the final word. Much of PL theory is devoted not just to improving expressiveness, but making that expressiveness more accessible to the masses. Compare Haskell with System F, for example.
The lack of generics in Go simply means that you will for common algorithms and functions end up with either of these scenario's:
1. You were forced to duplicate code under a different name with a different type signature (even though the body would be identical)
2. You are forced to use the 'empty interface' which negates the advantages of static type-checking (and seriously: why isn't it the default when you open an accolade?)
Not having generics in a statically typed language means you combine the disadvantage of static typing ('having to specify types and doubling the cognitive complexity of the language with a special type syntax') with ('not having the compiler be able to check the validity and risk run-time type errors, ie. things like cast exceptions').
>If the former, why do you think your preferences generalize?
Generics is not a platonic ideal such as 'object oriented programming'. Type systems are as solid as math itself. And just like Math there are multiple systems with different trade-offs in how expressive they are. This expressivity is not subjective! One type system can be strictly more expressive than another type system. Consider this pseudo-code without any type annotations:
def example(a,n)
return a.something(n)
In a dynamically typed language such as Ruby, Erlang, Python or JS there are programs that would call this method that would make it throw an exception during run-time. For example because it doesn't know how to do '.something' on the provided argument.The challenge: It barfs during run-time and we want it to barf during compile-time.
Now imagine all the possible usages of this method in a dynamically typed language that would be provable correct. All the potential combinations of a's and n's that would not make it barf. How many of them are legal Java, Haskell, C# or Go programs? (some, most, some, few)? The goal of a static system is to barf during compile-time. Not to exclude valid, legal programs just because the language author thinks type systems are hard to implement.
Whenever you can write provable correct code, that a type-system is complaining about: how is that not a bug?
>If the latter, how do you explain the large number of successful Go projects?
1. The projects are successful because of the people who make them
Well for starters by not assuming that people who program in Go are completely incompetent drooling idiots who are only able to deliver working code because Go is so great. As if the same developers wouldn't have successful projects right now, if Go didn't exist.
2. Any language released by a famous language author at Google will get a large minimum cult following by default
Much like with Angular who releases something has a strong impact on the adoption, regardless of the actual quality of the product, language design or implementation. I'm convinced the same set of programmers would be more productive in other languages. I'm also not surprised they themselves aren't aware of it.
3. Go has nice competitive features, the type system just isn't one of them.
Finally, Go isn't all shitty, for example, it has a very nice IO and concurrency model and ever since it finally got a precise garbage collector, one could finally use it long-running processes.
4. Your logic has clear type errors that a strong type system would help you catch
The existence of successful Go projects isn't an indication of _anything_. With the same logic you could argue VHS is superior to Betamax or that that Kanye is a talented musician. It's like the Silicon Valley variant of religious logic of 'moral people don't get sick'. Sometimes people, projects or products are not successful because they did everything the right way! Sometimes they just get lucky. Sometimes they just plain conspire against civilisation and cheat. Sometimes the things they did correctly ended up being more important the the things they did wrong. Sometimes they just get to announce on a bigger stage than somebody else.
> Are we all stuck in the 1980s? And if so, what does that even mean?
It means that there has been a large research initiative since the 1980's in software verification but because science is hard and thinking is hard and we are all getting paid anyway, we rather prefer to cargo-cult, bike-shed and gloat about our own ignorance. Because what you haven't learned yet is hard and what you already know is easy, even if it's all wrong.
It doesn't help that most of the academic researchers don't care enough about engineering to turn their research into actual production tools (with a few exceptions).
But i'm starting to warm up to the common notion that all progress in programming language design comes not from people learning, but from generations of developers eventually dying of old age. There is just too much money to go around for developers to not act like spoiled ignorant little children, and as a result most technology is just fundamentally broken and for no good reason.
Personally, I think a lot of the language you chose to use in that giant wall of text was quite unfortunate. I'm not interested in being a party to your ax grinding.
That said, personally I'd love to have generics on top of that. Consequently, I have been following some of the discussions on the topic, and so far I haven't seen anything suggesting that the language developers have an aversion to it. What they do have, however, is a fear that an improperly designed generics concept could badly screw up the language in a way that can't be reversed by any practical means once it is launched.
They are basically very careful about adding stuff, without fully understanding the consequences at all levels. You (or I) can disagree with that approach, but it's not accurate to say that they have an aversion for generics, or even to say that they don't want it.
The fact is that Go is the only statically typed language, with any claim to being mainstream, that doesn't have generics. And it's not like generics are some kind of a new and radical concept. Java had them for 13 years now; C# had them for 12. There's literally millions of lines of code written in popular languages that utilize generics, which can be used as a guide to proper design, and understand its consequences.
The idea that there needs to be more "baking time" for generics simply doesn't hold water at this point. It amounts to insisting that structured programming (loops etc) should not be adopted "without fully understanding the consequences at all levels", and meanwhile we'll just use if+goto - in 1980.
I don't think their crux is so much that generics "as such" needs more baking, but the specifics of how to implement them with the Go language. Mind you that some of the core goals of Go is to be simple, easy to parse, fast to compile, support good tooling, etc. so the question they're battling with is how to add generics to that mix without sacrificing any of those goals, and without making some mistake you can never go back from once every code base out there starts depending on it.
Now, by all means, we can argue that those priorities are wrong, or that yours would have been different. But I think it is disingenuous to suggest that they are effectively idiots who don't understand how to apply basic concepts, or are unaware of other programming languages.
That's false. Go provides a smattering of blessed polymorphic types (slices, maps, chans, pointers) and functions (len, append, delete, chan send, chan recv) that go a long way. They are horrifying to civilized PL enthusiasts, but they cover a lot ground.
As I said in one of my sibling comments, navel gazing isn't going to get you anywhere. And Go isn't the only statically typed language without generics. C has that designation as well.