4 ms·
I haven't written very much Java, but here are some differences between Java checked exceptions and Rust errors as I understand them: In Java, a function may t
by oconnor663 7y ago
I haven't written very much Java, but here are some differences between Java checked exceptions and Rust errors as I understand them:
In Java, a function may throw a long list of checked exceptions, and these lists tend to grow to inconvenient sizes in larger programs. For example, if foo() calls bar() and baz(), which each throw 3 different exception types, then now foo() might throw 6 different exception types. In contrast in Rust, each function can only return 1 error type. If a function needs to represent several different types of internal errors, then the crate that it's in needs to define an error enum type with a variant for each of those. (Either that, or the function can use a generic wrapper error that can contain anything. This is less common in library code but pretty common in application code.) This shifts a lot of work from library callers to library authors, which is a good thing.
Someone with more Java experience will need to correct me here: I think it's fairly common to bulldoze all that complexity by declaring a function that just "throws Exception". That saves you from writing out N different types (and more importantly, from changing every transitive caller when a low level library introduces a new exception type). But it kind of defeats the purpose of checked exceptions, by throwing away all the info they provide. It's a shame that you have this "all or nothing" choice when it comes to exceptions and how much complexity you want to deal with. In contrast in Rust, wrapper errors can define automatic "From" conversions from the lower level error types they wrap, and the standard `?` operator automatically applies those conversions. That means that in many cases, a low level library adding a new error variant might not require any changes in its callers at all. The new information is there for callers who want to look for it, but existing abstractions around the error type generally just keep working.
- abraxas 7y agoThis is kind of true but not quite. If you don't want to deal with any exceptions in your function you can declare that it just throws java.lang.Exception or put a verbose list of exception types in your method signature. On the other hand, you're also free to catch and process some and only propagate a subset outside of the function or even wrap the ones you want to process and throw a different exception type from within your function that wraps the existing ones. It's not mandated but pretty idiomatic in Java that any Exception type you define should be able to accept a different exception as its 'cause' in your exception's constructor signature.
- fauigerzigerk 7y agoI don't think this is a real difference. You can make the exact same API design mistakes regardless of which error delivery mechanism you use. The important thing is for the API designer to think about the abstractions, i.e which types of errors should be part of the API and which errors are just implementation details that may change. If the API designer is too lazy to put enough thought into that then the result will always be what you are ascribing to Java.
- oehpr 7y agoThere is always an onus on an API designer creating an API to express it well. However I think the difference between good vs bad language (and a good vs bad API come to think of it) is that it makes doing the right thing easy (lazy), and the wrong thing hard. I think about this when I think about GraphQL. I like GraphQL, I really do, but GraphQL's N+1 problem is why I don't recommend it. The easy thing is to hammer the heck out of your database, the hard things is to parse the GraphQL request and correctly transform it into a SQL statement. I just don't trust everyone on the dev team to not be lazy.
- fauigerzigerk 7y agoI agree with the principle, but in this case it seems very easy to do the right thing in either language once you have done the hard work of designing a good API. The same goes for doing the wrong thing.
- afiori 7y agoI do not know much about java, but I think that the part about automatic conversion on passing errors is not available in java.
- noisem4ker 7y agoYou can also choose to stop playing the game and wrap any checked Exception in an unchecked RuntimeException. Or, if you use Lombok, you can apply the @SneakyThrows annotation to quiet the compiler. As the idea that checked exceptions were a design mistake gains mindshare, you see these tricks more and more in newer codebases.
- jdmichal 7y ago> For example, if foo() calls bar() and baz(), which each throw 3 different exception types, then now foo() might throw 6 different exception types. > I think it's fairly common to bulldoze all that complexity by declaring a function that just "throws Exception". These are both solved by the same approach: foo should be wrapping those underlying exceptions under its own domain, not just passing them along. This has the added bonus that the further up the call stack you go, generally the more context you have about the operation. So you also get to set a new message that explains that context. So foo should only throw FooException, which might be wrapping an underlying BarException, which might be wrapping an underlying BazException. You get a full accounting of what happened, and how each layer interpreted it. For example: I invoke the user preferences subsystem to give me the user's foo setting. User preferences can be in a flat file or an embedded database. If I don't wrap exceptions, now my user preference system has to expose that implementation detail by exposing both file and database exception types. Instead I should wrap both of them in a UserPreferenceLoadingException type or whatever makes sense.