www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Try blocks are trying

reply Carl Sturtivant <sturtivant gmail.com> writes:
Why is it that a try block is defined to establish a scope?

```
     try {
         auto x = frgl();
     }
     // ...
     // x undefined here

```
What is the benefit of this? If try (like static if/foreach) did 
NOT establish a scope, then this annoyance would go away. And if 
a scope was desired for some reason, then an extra pair of braces 
could be written within.

Right now rewriting the above requires getting the return type of 
frgl and declaring x to be of that type before the try block.

Design error?
Oct 10 2021
next sibling parent jfondren <julian.fondren gmail.com> writes:
On Sunday, 10 October 2021 at 16:12:31 UTC, Carl Sturtivant wrote:
 Design error?
A block not introducing a scope is weird, and my first reaction to the {{ }} workaround was disgust. That's with version and static foreach blocks where not introducing a scope has very clear benefits. Try/catch meanwhile is a familiar construct and people are already used to working around issues like this. ```d typeof(frgl()) x; try { x = frgl; } catch (Exception e) { } ``` I'd consider also if you can use std.exception.ifThrown or a scope statement instead: ```d auto x = frgl().ifThrown(typeof(frgl()).init); ```
Oct 10 2021
prev sibling next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 10 October 2021 at 16:12:31 UTC, Carl Sturtivant wrote:
 Why is it that a try block is defined to establish a scope?

 ```
     try {
         auto x = frgl();
     }
     // ...
     // x undefined here

 ```
 What is the benefit of this? If try (like static if/foreach) 
 did NOT establish a scope, then this annoyance would go away. 
 And if a scope was desired for some reason, then an extra pair 
 of braces could be written within.

 Right now rewriting the above requires getting the return type 
 of frgl and declaring x to be of that type before the try block.

 Design error?
Sidenote, do you know about scope guard? https://tour.dlang.org/tour/en/gems/scope-guards
Oct 10 2021
prev sibling next sibling parent Greg Strong <mageofmaple protonmail.com> writes:
On Sunday, 10 October 2021 at 16:12:31 UTC, Carl Sturtivant wrote:
 Why is it that a try block is defined to establish a scope?

 ```
     try {
         auto x = frgl();
     }
     // ...
     // x undefined here

 ```
 What is the benefit of this? If try (like static if/foreach) 
 did NOT establish a scope, then this annoyance would go away. 
 And if a scope was desired for some reason, then an extra pair 
 of braces could be written within.

 Right now rewriting the above requires getting the return type 
 of frgl and declaring x to be of that type before the try block.

 Design error?
One of the main reasons to use a try block is to ensure a destructor is called at the end of the try block. If try didn't create a new scope, these things don't go out of scope, so they wouldn't be cleaned up.
Oct 10 2021
prev sibling next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 10/10/21 9:12 AM, Carl Sturtivant wrote:
 Why is it that a try block is defined to establish a scope?

 ```
      try {
          auto x = frgl();
      }
      // ...
      // x undefined here

 ```
One more point in favor of the current semantics, which your code seems to be in support of: x may be left in a state that does not provide its invariants. I am thinking out loud now: Luckily (and of course by good design), scope(failure) cannot be used in bad state either because in that case x must have already been defined when the scope(failure) appears in code: scope(failure) { writeln(x); // Compilation error (undefined x) } auto x = frgl(); And the following does not execute the scope(failure) block if frgl() throws because scope(failure) code is not "registered" yet: auto x = frgl(); scope(failure) { writeln(x); // Not executed if frgl() throws } Ok, I think we are in good shape with scope(failure) as well. :) Ali
Oct 10 2021
prev sibling next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Sunday, 10 October 2021 at 16:12:31 UTC, Carl Sturtivant wrote:
 Why is it that a try block is defined to establish a scope?

 ```
     try {
         auto x = frgl();
     }
     // ...
     // x undefined here

 ```
 What is the benefit of this? If try (like static if/foreach) 
 did NOT establish a scope, then this annoyance would go away. 
 And if a scope was desired for some reason, then an extra pair 
 of braces could be written within.

 Right now rewriting the above requires getting the return type 
 of frgl and declaring x to be of that type before the try block.

 Design error?
I do not deny that my heart has greatly desired this. Proposal: ``` export try { auto x = frgl(); } catch (Exception) { return false; } ``` shall be equivalent to ``` typeof(frgl()) x = void; try { frgl().moveEmplace(x); } catch (Exception) { __EXCEPTION_BODY__ assert(false, "`export` specified: all catch blocks must exit scope"); } POST ``` Here, `auto frgl` dominates `POST` and is thus correct. Scope guards directly in `try{}` should be lifted into the surrounding context, but this is confusing, so they are an error.
Oct 11 2021
prev sibling next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 10 October 2021 at 16:12:31 UTC, Carl Sturtivant wrote:
 Why is it that a try block is defined to establish a scope?

 ```
     try {
         auto x = frgl();
     }
     // ...
     // x undefined here

 ```
 What is the benefit of this? If try (like static if/foreach) 
 did NOT establish a scope, then this annoyance would go away. 
 And if a scope was desired for some reason, then an extra pair 
 of braces could be written within.

 Right now rewriting the above requires getting the return type 
 of frgl and declaring x to be of that type before the try block.

 Design error?
In that case you would just declare x outside and do the call in the try
Oct 11 2021
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 11 October 2021 at 08:55:56 UTC, Imperatorn wrote:
 In that case you would just declare x outside and do the call 
 in the try
Thus once again demonstrating that for a language that had a major version change over it, D2 really doesn't care about immutable.
Oct 11 2021
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Monday, 11 October 2021 at 09:21:48 UTC, FeepingCreature wrote:
 On Monday, 11 October 2021 at 08:55:56 UTC, Imperatorn wrote:
 In that case you would just declare x outside and do the call 
 in the try
Thus once again demonstrating that for a language that had a major version change over it, D2 really doesn't care about immutable.
I don't know about that. But it's pretty standard for try to introduce a scope.
Oct 11 2021
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 11 October 2021 at 09:23:27 UTC, Imperatorn wrote:
 On Monday, 11 October 2021 at 09:21:48 UTC, FeepingCreature 
 wrote:
 On Monday, 11 October 2021 at 08:55:56 UTC, Imperatorn wrote:
 In that case you would just declare x outside and do the call 
 in the try
Thus once again demonstrating that for a language that had a major version change over it, D2 really doesn't care about immutable.
I don't know about that. But it's pretty standard for try to introduce a scope.
The problem is if you want to use immutable types at all, this forces you to either use awkward nested function idioms with tuple returns for more than one variable, put the entire remaining function body in the `try` block. It makes it impossible to keep `try` small. One possible solution would be a way to try/catch as an expression: ``` auto x = frgl().tryCatch(Exception exc: return exc;); ``` But D has no precedent for statements embedded in expressions like this, so it'd be a major language shift. `export try` is the only feasible approach I can see offhand.
Oct 11 2021
next sibling parent reply bauss <jj_1337 live.dk> writes:
On Monday, 11 October 2021 at 09:27:30 UTC, FeepingCreature wrote:
 On Monday, 11 October 2021 at 09:23:27 UTC, Imperatorn wrote:
 On Monday, 11 October 2021 at 09:21:48 UTC, FeepingCreature 
 wrote:
 On Monday, 11 October 2021 at 08:55:56 UTC, Imperatorn wrote:
 In that case you would just declare x outside and do the 
 call in the try
Thus once again demonstrating that for a language that had a major version change over it, D2 really doesn't care about immutable.
I don't know about that. But it's pretty standard for try to introduce a scope.
The problem is if you want to use immutable types at all, this forces you to either use awkward nested function idioms with tuple returns for more than one variable, put the entire remaining function body in the `try` block. It makes it impossible to keep `try` small. One possible solution would be a way to try/catch as an expression: ``` auto x = frgl().tryCatch(Exception exc: return exc;); ``` But D has no precedent for statements embedded in expressions like this, so it'd be a major language shift. `export try` is the only feasible approach I can see offhand.
Using alias this, opAssign and a boolean value you can actually do something like this: ```d public struct BindOnce(T) { private bool _isSet; private T _value; alias _value this; void opAssign(T rhs) { if (_isSet) { throw new Error("The value has already been set."); } _isSet = true; _value = rhs; } string toString() { return to!string(_value); } // Required for writeln } ``` Example Usage: ``` int getNumber(int input) { if (input == 0) throw new Exception("This function doesn't allow multiplications of zero."); return input * 2; } void main() { BindOnce!int n; try { n = getNumber(20); //n = getNumber(40); // Uncommenting this line will throw the error "The value has already been set." writeln(n); } catch (Exception e) { writeln(e); writeln(n); } } ``` Of course this assumes you're able to allow runtime errors and that you don't blindly catches them and ignores them either (which you shouldn't as Error shouldn't be recoverable!)
Oct 11 2021
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 11 October 2021 at 11:05:47 UTC, bauss wrote:
 On Monday, 11 October 2021 at 09:27:30 UTC, FeepingCreature
 `export try` is the only feasible approach I can see offhand.
Using alias this, opAssign and a boolean value you can actually do something like this: ```d public struct BindOnce(T) { private bool _isSet; private T _value; alias _value this; void opAssign(T rhs) { if (_isSet) { throw new Error("The value has already been set."); } _isSet = true; _value = rhs; } string toString() { return to!string(_value); } // Required for writeln } ``` Example Usage: ``` int getNumber(int input) { if (input == 0) throw new Exception("This function doesn't allow multiplications of zero."); return input * 2; } void main() { BindOnce!int n; try { n = getNumber(20); //n = getNumber(40); // Uncommenting this line will throw the error "The value has already been set." writeln(n); } catch (Exception e) { writeln(e); writeln(n); } } ``` Of course this assumes you're able to allow runtime errors and that you don't blindly catches them and ignores them either (which you shouldn't as Error shouldn't be recoverable!)
I almost don't want to say it. Cough immutable cough... That's why this really needs language support. (Also I'd just use `Nullable` or `Rebindable` irl.)
Oct 11 2021
parent reply bauss <jj_1337 live.dk> writes:
On Monday, 11 October 2021 at 11:42:52 UTC, FeepingCreature wrote:
 On Monday, 11 October 2021 at 11:05:47 UTC, bauss wrote:
 On Monday, 11 October 2021 at 09:27:30 UTC, FeepingCreature
 `export try` is the only feasible approach I can see offhand.
Using alias this, opAssign and a boolean value you can actually do something like this: ```d public struct BindOnce(T) { private bool _isSet; private T _value; alias _value this; void opAssign(T rhs) { if (_isSet) { throw new Error("The value has already been set."); } _isSet = true; _value = rhs; } string toString() { return to!string(_value); } // Required for writeln } ``` Example Usage: ``` int getNumber(int input) { if (input == 0) throw new Exception("This function doesn't allow multiplications of zero."); return input * 2; } void main() { BindOnce!int n; try { n = getNumber(20); //n = getNumber(40); // Uncommenting this line will throw the error "The value has already been set." writeln(n); } catch (Exception e) { writeln(e); writeln(n); } } ``` Of course this assumes you're able to allow runtime errors and that you don't blindly catches them and ignores them either (which you shouldn't as Error shouldn't be recoverable!)
I almost don't want to say it. Cough immutable cough... That's why this really needs language support. (Also I'd just use `Nullable` or `Rebindable` irl.)
Yeah, I'm aware it's not necessarily "immutable" in the standard sense, but it is in the technical sense. Of course the export try would be much better but this is a somewhat viable solution until, as you can easily replace it with immutable once/if ever supported.
Oct 11 2021
parent FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 11 October 2021 at 12:20:11 UTC, bauss wrote:
 Yeah, I'm aware it's not necessarily "immutable" in the 
 standard sense, but it is in the technical sense.

 Of course the export try would be much better but this is a 
 somewhat viable solution until, as you can easily replace it 
 with immutable once/if ever supported.
No I mean: ``` struct Problem { immutable int value; } Problem getNumber(int input) { if (input == 0) throw new Exception("This function doesn't allow multiplications of zero."); return Problem(input * 2); } ``` And it errors out. Types like these are a crapshoot in D2 even now.
Oct 11 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 11 October 2021 at 09:27:30 UTC, FeepingCreature wrote:
 One possible solution would be a way to try/catch as an 
 expression:

 ```
 auto x = frgl().tryCatch(Exception exc: return exc;);
 ```

 But D has no precedent for statements embedded in expressions 
 like this, so it'd be a major language shift.
``` import std.sumtype; SumType!(V, E) tryCatch(E, V)(lazy V expr) { try return typeof(return)(expr); catch (E e) return typeof(return)(e); } auto x = frgl().tryCatch!Exception; ```
Oct 11 2021
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 11 October 2021 at 13:45:02 UTC, Paul Backus wrote:
 On Monday, 11 October 2021 at 09:27:30 UTC, FeepingCreature 
 wrote:
 One possible solution would be a way to try/catch as an 
 expression:

 ```
 auto x = frgl().tryCatch(Exception exc: return exc;);
 ```

 But D has no precedent for statements embedded in expressions 
 like this, so it'd be a major language shift.
``` import std.sumtype; SumType!(V, E) tryCatch(E, V)(lazy V expr) { try return typeof(return)(expr); catch (E e) return typeof(return)(e); } auto x = frgl().tryCatch!Exception; ```
Sure, but then you can't get at the `V` without - going into a subscope again. :) Though I guess it wouldn't be covered by a `try`. Neat (my lang) doesn't have exceptions, but it does have built-in sumtypes with error marking, so you can have ``` (V | fail Exception) frgl() { ... } auto x <- frgl(); ``` And it will "pick" the non-fail `V` and, in the case of `Exception`, just propagate (return) it. The goal is the same as `export try` - avoid having to define a variable in a separate block just to handle errors.
Oct 11 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 11 October 2021 at 16:04:00 UTC, FeepingCreature wrote:
 Sure, but then you can't get at the `V` without - going into a 
 subscope again. :) Though I guess it wouldn't be covered by a 
 `try`.
At some point, you need to have separate code paths for the error case and the success case, yes. I know of no language construct in D that allows you to create separate code paths without introducing a scope somewhere. The best you can do is move the scopes around.
 Neat (my lang) doesn't have exceptions, but it does have 
 built-in sumtypes with error marking, so you can have

 ```
 (V | fail Exception) frgl() { ... }

 auto x <- frgl();
 ```

 And it will "pick" the non-fail `V` and, in the case of 
 `Exception`, just propagate (return) it.
In D, you can also write `auto x = frgl();`, and it will propagate the exception automatically. :) This is not a replacement for `try` blocks, because the reason you use a `try` block is to handle an exception locally; in other words, to avoid propagating it.
Oct 11 2021
parent FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 11 October 2021 at 16:23:30 UTC, Paul Backus wrote:
 On Monday, 11 October 2021 at 16:04:00 UTC, FeepingCreature 
 wrote:
 Sure, but then you can't get at the `V` without - going into a 
 subscope again. :) Though I guess it wouldn't be covered by a 
 `try`.
At some point, you need to have separate code paths for the error case and the success case, yes. I know of no language construct in D that allows you to create separate code paths without introducing a scope somewhere. The best you can do is move the scopes around.
Right, hence `export try`.
 In D, you can also write `auto x = frgl();`, and it will 
 propagate the exception automatically. :)

 This is not a replacement for `try` blocks, because the reason 
 you use a `try` block is to handle an exception locally; in 
 other words, to avoid propagating it.
Right, sorry. I've been meaning to add a feature for that: `frgl().handle(Exception ex: return false;)`, but it isn't in yet.
Oct 12 2021
prev sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 10 October 2021 at 16:12:31 UTC, Carl Sturtivant wrote:
 Why is it that a try block is defined to establish a scope?

 ```
     try {
         auto x = frgl();
     }
     // ...
     // x undefined here

 ```
 What is the benefit of this? If try (like static if/foreach) 
 did NOT establish a scope, then this annoyance would go away. 
 And if a scope was desired for some reason, then an extra pair 
 of braces could be written within.

 Right now rewriting the above requires getting the return type 
 of frgl and declaring x to be of that type before the try block.

 Design error?
My gut instinct tells me problems would arise if try didn't introduce a scope. It's not a compile time concept like static if. If we had a static try I would probably agree
Oct 11 2021