3 ms·
This problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail bu
by cyco130 2y ago
This problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail but `close()` can fail. Therefore, for 100% correctness, `close()` calls should be handled explicitly and not left to finalizers.
Finalizers shouldn't fail because they might be executed while another exception is already in flight. Three languages have three different behaviors when that happens but in my opinion they all do the wrong thing:
In C++, if you throw from a destructor while another exception is in flight, the program will be terminated. In Java, throwing from a `finally` block will "forget" the original exception. In Go (according to this article, I'm not familiar with it), error from `defer` will be ignored. None of these are ideal.
- pansa2 2y ago> in my opinion they all do the wrong thing What would be the right thing? Combining the original exception and the error from `close` into some kind of `MultipleError`?
- saurik 2y agoPersonally I do not believe the math of these two monads allows for any better solutions (and I do not believe multierror is correct ;P)... I am thereby also very curious what they think the correct thing to do here is.
- cyco130 2y agoThat's probably the best option I think. I've heard Ada does that (but don't quote me on that). If you can access the original errors from the `MultipleError` object, at least you can tell the user what exactly went wrong. I don't thing there's one true right thing™ though. That's why explicit handling is necessary: The compiler doesn't have enough context to handle it for you. The programmer needs to decide what's the right way to handle it.
- Almondsetat 2y agoWhat about Java's try-with-resources?
- cyco130 2y agoMy Java knowledge is at least 10 years out of date so I'm not familiar with try-with-resources. But from what I can gather from Google, it looks like they now provide a way to access the suppressed exceptions, which is probably a step in the right direction.
- shawnz 2y agoThe problem is handled with exceptions plus Java's try-with-resources or C#'s using statements or Python's context managers though, right? Furthermore in Java since version 7 you can actually see both exceptions with the suppressed exceptions pattern.
- cyco130 2y agoSee my reply to the sibling comment about try-with-resources. I'm not familiar with any of these mechanisms but giving access to suppressed exceptions is probably a step in the right direction.
- layer8 2y agoJava with try-with-resources does the correct thing: It attaches the new exception as a secondary exception to the currently in-flight exception. Since function calls form a tree, exceptions must form a tree as well. Doing this automatically is also one of the killer arguments for exceptions over error codes, IMO.
- dwattttt 2y agoHow automatic it is depends on the language, but error "objects" (rather than codes) can do this too. It is pretty great.
- layer8 2y agoYes, with enough syntactic sugar it can become equivalent. Exceptions can be viewed as sum types with special syntactic sugar in conjunction with the regular return types, and can in principle be implemented as such. When I say "exceptions", I mean the source-level semantics, not how it's implemented behind the scenes.
- kokada 2y agoGo also has `errors.Join` since 1.20, and one of its uses is exactly this one: to be used in deferred close that can possible raise errors so the errors can stack each other: https://stackoverflow.com/a/78013962 https://stackoverflow.com/a/78013962. > Doing this automatically is also one of the killer arguments for exceptions over error codes, IMO. Definitely doing this automatically is better than relying on the programmer to do this manually, but I wouldn't say this is the "killer" argument for exceptions over errors codes, because this doesn't add anything new to the argument of exceptions vs explicit error handling.
- neonsunset 2y agoIt might fail, but usually doesn't. In C#, to handle an e.g. file open failure, you can just write try { using var file = File.OpenWrite("test.txt"); file.Write("Hello, World!"u8); } catch (IOException e) { Console.WriteLine(e.Message); } The exception handler is an enclosing scope for both file open and dispose (flush and close) operations. You can also hoist file variable to an outer scope to, for example, decide what to do if dispose throws for some reason. In practical terms, this is as much of an edge case as it gets, but can be expressed in a fairly straightforward way.
- lostmsu 2y agoI don't think this does the right thing. You are catching exception if Close fails and nothing else. The problem thought is what to do when after file is opened something fails first, and then Close also fails.
- neonsunset 2y agoI'm catching any exception that occurs within the scope of try block. This includes an exception when trying to open or write to the file. If I fail to write to a file, it will try to dispose the stream, which flushes it and closes the file handle. If disposing the file handle itself fails, which should never happen, the exception will occur in the finally block, which this exception handler catches too. If you need to disambiguate and handle each case differently, which is rarely needed, you can order try-catch-finally blocks differently with explicit dispose and different nesting. This, again, is not a practical scenario and most user code will just `using file = File.OpenWrite` it and let the exception bubble up.
- lostmsu 2y agoYes, and this ignoring of the original exception is the core of the problem discussed. If you are willing to lose supposedly written data, your approach is golden.
- 2y ago
- ekimekim 2y agoPython handles this case by raising the new error but including a reference to the original error. By default, the formatted error shows both: >>> mylist = [] >>> try: ... first = mylist[0] ... finally: ... inverse_length = 1.0 / len(mylist) # imagine this was something more complex ... Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: list index out of range During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> ZeroDivisionError: float division by zero