www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why is SwitchError an error and how is it unsafe to continue after

reply aliak <something something.com> writes:
Because from what I understand, an Error is something you should 
not be catching and represents something unrecoverable. And it 
the docs say that it's unsafe to continue execution. But the 
following code is very recoverable and I don't see how it's 
unsafe to continue executing:

import optional;
import core.exception: SwitchError;

enum Enum : string {
   one = "one", two = "two"
}

Optional!Enum makeEnum(string value) {
   try {
     final switch (value) {
     case Enum.one: return some(Enum.one);
     case Enum.two: return some(Enum.two);
     }
   } catch (SwitchError) {
     return no!Enum;
   }
}

unittest {
     assert(makeEnum("one") == some(Enum.one));
     assert(makeEnum("huh") == no!Enum);
}

Cheers,
- Ali
Feb 24
next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Sunday, 24 February 2019 at 10:53:09 UTC, aliak wrote:
 Because from what I understand, an Error is something you 
 should not be catching and represents something unrecoverable. 
 And it the docs say that it's unsafe to continue execution. But 
 the following code is very recoverable and I don't see how it's 
 unsafe to continue executing:

 import optional;
 import core.exception: SwitchError;

 enum Enum : string {
   one = "one", two = "two"
 }

 Optional!Enum makeEnum(string value) {
   try {
     final switch (value) {
     case Enum.one: return some(Enum.one);
     case Enum.two: return some(Enum.two);
     }
   } catch (SwitchError) {
     return no!Enum;
   }
 }

 unittest {
     assert(makeEnum("one") == some(Enum.one));
     assert(makeEnum("huh") == no!Enum);
 }

 Cheers,
 - Ali
There is a semantic difference between a switch and a final switch statement, defined here: https://dlang.org/spec/statement.html#final-switch-statement By this difference, the writer of the final switch declares, that it is unrecoverable to pass something unexpected to the switch statement. The catch of an error as you demonstrated is, therefore, a contradiction to the finality of the switch. I mean, if you know, that something beyond the enum can be passed, use a normal switch and handle the case in the default section. If you are able to ensure, this case is unreachable, you express this knowledge/ability by the final switch and don't need the try-catch clause at all.
Feb 24
parent aliak <something something.com> writes:
On Sunday, 24 February 2019 at 11:05:31 UTC, Alex wrote:
 On Sunday, 24 February 2019 at 10:53:09 UTC, aliak wrote:
 [...]
There is a semantic difference between a switch and a final switch statement, defined here: https://dlang.org/spec/statement.html#final-switch-statement By this difference, the writer of the final switch declares, that it is unrecoverable to pass something unexpected to the switch statement. The catch of an error as you demonstrated is, therefore, a contradiction to the finality of the switch.
Hmm ok. Yes that makes sense to see it that way. Thanks!
 I mean, if you know, that something beyond the enum can be 
 passed, use a normal switch and handle the case in the default 
 section. If you are able to ensure, this case is unreachable, 
 you express this knowledge/ability by the final switch and 
 don't need the try-catch clause at all.
Aye, agreed.
Feb 25
prev sibling next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Sunday, 24 February 2019 at 10:53:09 UTC, aliak wrote:
 But the following code is very recoverable and I don't see how 
 it's unsafe to continue executing:
There is no guarantee that a final switch throws an Error. From what I've heard of Walter [1] (though I can't find it in the spec), the default case in a final switch is assumed to be unreachable, so in optimized (non-safe release) code you might just go out of bounds of the jump table and execute garbage code instead of catching and Error, which is only a debug facility. [1] https://issues.dlang.org/show_bug.cgi?id=13169
Feb 24
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, February 24, 2019 5:42:32 AM MST Dennis via Digitalmars-d-learn 
wrote:
 On Sunday, 24 February 2019 at 10:53:09 UTC, aliak wrote:
 But the following code is very recoverable and I don't see how
 it's unsafe to continue executing:
There is no guarantee that a final switch throws an Error. From what I've heard of Walter [1] (though I can't find it in the spec), the default case in a final switch is assumed to be unreachable, so in optimized (non-safe release) code you might just go out of bounds of the jump table and execute garbage code instead of catching and Error, which is only a debug facility. [1] https://issues.dlang.org/show_bug.cgi?id=13169
IIRC, last time I tested this, with -release, there was no SwitchError when a final switch was given a bad value, whereas there was without -release. Regardless, Errors in general are considered fatal conditions and should not normally be caught. Cleanup code such as destructors, finally blocks, and scope statements are not necessarily run when an Error (or any kind of Throwable other than an Exception) is thrown. The program is always in a bad state after an Error is thrown (it's just a question of how bad), and by definition, your program is an undefined state if you catch an Error and attempt to continue. One example of this is that nothrow functions cannot throw Exceptions, but they can throw all other kinds of Throwables, and the compiler is not going to put proper exception handling code around a call to a nothrow function (if it did, that would defeat one of the main purposes of a function being nothrow). So, while the compiler doesn't guarantee that any cleanup code is run when an Error is thrown (but it still may happen in some cases), it definitely won't be run in the case of nothrow functions. So, the state of the program just becomes that much more invalid as an Error propagates its way up the stack. Errors are intended for fatal or unrecoverable conditions such as bad program logic or running out of memory and as such are really just there to print the error message and stack trace before killing the program. They are _not_ intended to be recovered from. So, it is almost never the case that code should be catching them. If you have a final switch statement, it's up to you to guarantee that the variable contains a valid enum value. If it doesn't, you have a bug in your program that needs to be fixed, regardless of whether a SwitchError is thrown or whether the program goes and does something wonky, because the debugging check that results in the SwitchError was not compiled in. Even if a SwitchError were always thrown on bad input (just like RangeErrors are always thrown for out-of-bounds array indexing in safe functions), it would still be a programming bug that needed to be fixed. Code should not be trying to catch an Error and continue. Rather, if an Error is thrown, that means that the code has a bug that needs to be fixed. - Jonathan M Davis
Feb 24
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On Sunday, 24 February 2019 at 10:53:09 UTC, aliak wrote:
 Because from what I understand, an Error is something you 
 should not be catching and represents something unrecoverable. 
 And it the docs say that it's unsafe to continue execution. But 
 the following code is very recoverable and I don't see how it's 
 unsafe to continue executing:

 import optional;
 import core.exception: SwitchError;

 enum Enum : string {
   one = "one", two = "two"
 }

 Optional!Enum makeEnum(string value) {
   try {
     final switch (value) {
     case Enum.one: return some(Enum.one);
     case Enum.two: return some(Enum.two);
     }
   } catch (SwitchError) {
     return no!Enum;
   }
 }

 unittest {
     assert(makeEnum("one") == some(Enum.one));
     assert(makeEnum("huh") == no!Enum);
 }

 Cheers,
 - Ali
These days you can disable the check on final switches: -check=switch=off However, in this case, I would say you should actually be using a normal switch because your input is not an enum and you aren't constraining its value. IMO, using any type other than an enum as the input for a final switch should be a compiler error.
Feb 24