4 ms·
I still don't see why. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c65c3161570329fb22907620f4f09692 https://play.rust-lang.org/?ver
by kaoD 8y ago
I still don't see why.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c65c3161570329fb22907620f4f09692 https://play.rust-lang.org/?version=stable&mode=debug&editio...
Let's add a new error condition:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=841a3bb4ff75c10752f9cfd1ace3bdd5 https://play.rust-lang.org/?version=stable&mode=debug&editio...
Only the function raising the error, and the consumer, had to change (and the consumer only changed because it _cared_ about the error type!)
If you want an example with nested errors I can do it, you'll see it's mostly the same.
- wvenable 8y agoIf you had an external compiled library the consumed MyError or called caller, you just broke it, becuase you changed the type. This isn't a realistic example at all because your MyError is simple enum that you changed to make it work. If you add another error, or have more than one level, or call it multiple locations, and don't eat the error then now you're changing many more functions, just as I said.
- kaoD 8y ago> This isn't a realistic example at all because your MyError is simple enum that you changed to make it work. Well, that's what an error is. A simple enum of the error cases and maybe some metadata. EDIT: e.g. IO errors in Rust's std: https://doc.rust-lang.org/src/std/io/error.rs.html#91-174 https://doc.rust-lang.org/src/std/io/error.rs.html#91-174 If your first guess is this isn't realistic because it's "too simple" then maybe you've never been exposed to this kind of error handling? It's so simple it's unbelievable? > If you add another error That's what I did, I added a new error. > or have more than one level and don't eat the error now you're changing more methods, just as I said. As I said, if you want an example with nested error types I can do it: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c4a81faf48266658f569c2a00ae10d02 https://play.rust-lang.org/?version=stable&mode=debug&editio... As you can see, caller() (which would be our "middleman" here, the one that neither produces nor uses the error) didn't change at all. > If you had an external compiled library the consumed MyError or called caller, you just broke it, becuase you changed the type. You mean a dynamically-linked library? Yep, completely broken, but that's not the only problem with DLLs. Static linking is fine. Anyways, the contract changed, so it's a breaking change, so that's good in my book. The contract changed in the case of exceptions too, you just hand-waved over the issue and called it a day. Wouldn't it be the same with exceptions across library boundaries?
- wvenable 8y ago> If your first guess is this isn't realistic because it's "too simple" then maybe you've never been exposed to this kind of error handling? I explained why the example is too simple. If you coded this in C with an integer error code the code would also be roughly the same size. > Wouldn't it be the same with exceptions across library boundaries? Actually no. That's the significant difference. I can freely change the implementation and if that causes new and different exceptions to be possible, that doesn't affect the implementors. And most of the time such a change makes will make material affect on the implementors so they are broken for nothing.
- kaoD 8y ago> I explained why the example is too simple. And I offered a second example with a nested error, and nothing changed. Errors are just enums, there's nothing more complex than this. It's that simple. > If you coded this in C with an integer error code the code would also be roughly the same size. But it wouldn't check the error types. It's a zero-cost abstraction. > I can freely change the implementation and if that causes new and different exceptions to be possible, that doesn't affect the implementors. Neither does in my case... unless I care about the actual errors, which I did in this case in main() just for completeness.
- wvenable 8y agoHonestly, that second example is more complex. Now you're had to manually map one error onto another in order to propagate it. How big does MyError get when you have OtherError, AnotherError, FailureNum5, ChokedOnBanana. You're just doing busy work creating types and mapping them together. And this is just a simple toy example! You're quick to hand-wave that wasted effort. What value is the enum value Other(OtherError) actually provide other than to satisfy the compiler? How far do we take this when there are more levels? OtherFailure(OtherMistake(Other(OtherError)))? > Neither does in my case... In each example you changed the implementation of type so that it is no longer compatible. You altered the signature of the method. > But it wouldn't check the error types. It's a zero-cost abstraction. It's funny that you say that as error returns have a non-zero runtime cost. Yet exceptions can actually be zero-cost at runtime (if they aren't thrown) as they can be implemented out of band. Only the most naive implementation propagates exceptions with multi-value returns. Now I do understand why Rust is implemented this way but it's not because error returns are unequivocally better. Good code with exceptions have very little code actually related to error handling. Throw often, catch infrequently, and use normal resource cleanup for exceptional cleanup as well. You can get far with a 2-3 well placed catch statements and that's it. No extra typing. No error propagation code. If you think of error handling as happening where the error is raised as opposed to where it is handled it's hard to see the advantage of exceptions. Where the error is raised is not that important.