June 07, 2022

On 6/7/22 12:28 PM, frame wrote:

>

On Friday, 3 June 2022 at 23:40:50 UTC, Steven Schveighoffer wrote:

>

During the last beerconf, I wrote a short blog post about how Error and Exception are different, and why you should never continue after catching Errors.

I know the thematics but I still wonder why we only have scope(failure) then? Anywhere where you will use this shiny thing with a return statement will also catch any error that have occurred. scope(exit) doesn't allow return statements, thus the only properly clean design would be an additional scope(exception) guard.

My very common use of scope(failure) for my DB code:

conn.exec("START TRANSACTION");
scope(success) conn.exec("COMMIT");
scope(failure) conn.exec("ROLLBACK");

This is hard to encapsulate into a type, as dtors only hook scope(exit) essentially.

-Steve

June 07, 2022

On Tuesday, 7 June 2022 at 18:37:13 UTC, Steven Schveighoffer wrote:

>

My very common use of scope(failure) for my DB code:

conn.exec("START TRANSACTION");
scope(success) conn.exec("COMMIT");
scope(failure) conn.exec("ROLLBACK");

This is hard to encapsulate into a type, as dtors only hook scope(exit) essentially.

-Steve

That's fine as the Throwable is still thrown but since scope(failure) acts as like catch-block, people may use it as replacement if there is something more to do.

I personally like the clean, short syntax when I don't care about the actual exception or if I know the called function has already handled the exception and just rethrows it:

bool fun() {
     scope(failure) {
         // do something
         return false;
     }

     badCode();
     return true;
}

I know this is bad as I'm capturing a possible error here - but this is what an unexperienced coder would do, because it works. The better approach would be to use scope(exception) (not typed, just as keyword) that allows to act only on Exceptions so one cannot break Exception vs Error paradigma and this also clarifies that usage of scope(failure) is potentially dangerous if you return from it.

Anyway, I just put that here - maybe you mention it in your blog.

June 07, 2022
On 6/7/22 12:58, frame wrote:

> ```d
> bool fun() {
>       scope(failure) {
>           // do something
>           return false;
>       }
>
>       badCode();
>       return true;
> }
> ```
>
> I know this is bad as I'm capturing a possible error here - but this is
> what an unexperienced coder would do, because it works.

WAT! I think that's a bug. Simply having a 'return' statement suppresses rethrowing the exception? Too subtle! The following bug is related:

  https://issues.dlang.org/show_bug.cgi?id=21443

And the spec does not mention 'return' in scope(failure) having such a huge effect. Uncommenting the 'return' line below causes wildly different program behavior:

import std.stdio;

void main() {
  writeln("main is calling zar");
  zar();
}

void zar() {
  scope (failure) {
    writeln("zar is failing");
  }

  writeln("zar is calling bar");
  bar();
}

void bar() {
  scope (failure) {
    writeln("bar is failing");
    // return;
  }

  writeln("bar is calling foo");
  foo();
}

void foo() {
  writeln("foo is throwing");
  throw new Exception("Oops!");
}

BUG! :)

Ali

June 07, 2022

On 6/7/22 3:58 PM, frame wrote:

>

On Tuesday, 7 June 2022 at 18:37:13 UTC, Steven Schveighoffer wrote:

>

My very common use of scope(failure) for my DB code:

conn.exec("START TRANSACTION");
scope(success) conn.exec("COMMIT");
scope(failure) conn.exec("ROLLBACK");

This is hard to encapsulate into a type, as dtors only hook scope(exit) essentially.

-Steve

That's fine as the Throwable is still thrown but since scope(failure) acts as like catch-block, people may use it as replacement if there is something more to do.

I personally like the clean, short syntax when I don't care about the actual exception or if I know the called function has already handled the exception and just rethrows it:

bool fun() {
      scope(failure) {
          // do something
          return false;
      }

      badCode();
      return true;
}

Wait, what?

I'm looking at the spec and it says:

A scope(exit) or scope(success) statement may not exit with a throw, goto, break, continue, or return; nor may it be entered with a goto.

Which specifically does not include scope(failure).

I would never have expected that to be a "feature"...

>

I know this is bad as I'm capturing a possible error here - but this is what an unexperienced coder would do, because it works. The better approach would be to use scope(exception)  (not typed, just as keyword) that allows to act only on Exceptions so one cannot break Exception vs Error paradigma and this also clarifies that usage of scope(failure) is potentially dangerous if you return from it.

Anyway, I just put that here - maybe you mention it in your blog.

Ali's linked bug report suggests that it's happening for Errors too, which is going to cause major problems if something didn't get cleaned up properly.

I will update the blog post. My recommendation is going to be, don't circumvent the propagation of the throwable for now. Instead use a try + catch(Exception). I think we need a compiler change to not allow return if it's catching an Error.

There may be other "weird" cases that are not covered by the spec. Is it legal to goto a label inside scope(failure)?

Thanks for the heads up!

-Steve

June 12, 2022

On Tuesday, 7 June 2022 at 18:37:13 UTC, Steven Schveighoffer wrote:
[...]

>

My very common use of scope(failure) for my DB code:

conn.exec("START TRANSACTION");
scope(success) conn.exec("COMMIT");
scope(failure) conn.exec("ROLLBACK");

Are there multiple (successful) returns in your code or why are you executing the COMMIT under a scope exit clause? If there is only one successful return wouldn't it enhance code readability when an explicit COMMIT precedes the return?

Is the ROLLBACK really necessary? Isn't the transaction not committed (i.e. rolled back) when the db handle is closed? Why not have a Transaction class (struct)?

June 12, 2022

On Sunday, 5 June 2022 at 23:57:19 UTC, Steven Schveighoffer wrote:

>

On 6/5/22 6:09 PM, kdevel wrote:

>

On Sunday, 5 June 2022 at 20:53:32 UTC, Steven Schveighoffer wrote:
[...]

> >

For this purpose nobody needs a separate subclass named Error. That works with Exceptions.

You can use Exceptions instead. But the difference is they are part of the program instead of considered a check on the program itself.

I have no clue what that means.

An assert (or thrown error) is not ever supposed to happen, so it's not part of the program. It's a check to ensure that the program is sound and valid. You can feel free to insert as many asserts as you want, and it should not be considered part of the program.

It basically says "If this condition is false, this entire program is invalid, and I don't know how to continue from here."

Who is the narrator here? It is the author of the function which contains the assert. But this is not necessarily the author of the code which calls this function. Why should the author of the function be allowed to terminate the whole the program (be it via assert or exit)?

> >

[...]
My code does not throw Errors. It always throws Exceptions.

Then I guess you should just ignore Errors and let the runtime handle them? I'm not sure why this discussion is happening.

Because phobos/runtime throw Errors?

> > >

changing all range functions to use the checkedPopFront and checkedFront, just to find the error. Instead of letting the asserts do their job.

It is not clear to me, what you now complain about. You first criticized that in

for(auto r = range; !r.empty; r.popFront) {
    auto elem = r.front;
}

there is triple checking that r is not empty, namely in !r.empty, in r.front and in r.popFront. One check, !r.emtpy, will suffice. Hence one can safely use

for(auto r = range; !r.empty; r.popFront_unchecked) {
    auto elem = r.front_unchecked;
}

You can do this, and then if it fails, you have to guess that maybe you did something wrong, and start using the checked versions.

What shall go wrong in this example? Of course the compiler might generate wrong code. But it is not the aim of putting enforce and asserts into program code to guard against the bugs in the compiler.

June 13, 2022

On 6/12/22 4:11 PM, kdevel wrote:

>

On Tuesday, 7 June 2022 at 18:37:13 UTC, Steven Schveighoffer wrote:
[...]

>

My very common use of scope(failure) for my DB code:

conn.exec("START TRANSACTION");
scope(success) conn.exec("COMMIT");
scope(failure) conn.exec("ROLLBACK");

Are there multiple (successful) returns in your code or why are you executing the COMMIT under a scope exit clause?

No, usually just one (the implicit final return).

>

If there is only one successful return wouldn't it enhance code readability when an explicit COMMIT precedes the return?

It just introduces another place where I can mess up the transaction. I can write these 3 lines, and be done, never having to worry about the transaction code again.

This is one of the major features of scope guards.

>

Is the ROLLBACK really necessary?

Yes. If you don't execute the rollback and start executing more DB calls, they all get included in the transaction (and might be expected to be).

>

Isn't the transaction not committed (i.e. rolled back) when the db handle is closed?

Possibly, but I don't close the handle. It goes back to a pool to get reused.

>

Why not have a Transaction class (struct)?

A transaction class/struct cannot hook normal return and throwing differently (the destructor has no way of knowing whether an exception is in flight).

-Steve

June 13, 2022

On 6/13/22 9:15 AM, Steven Schveighoffer wrote:

>

Yes. If you don't execute the rollback and start executing more DB calls, they all get included in the transaction (and might be expected to be).

Should have said "might not be expected to be"

-Steve

June 14, 2022

On Monday, 13 June 2022 at 13:15:42 UTC, Steven Schveighoffer wrote:

>

Possibly, but I don't close the handle. It goes back to a pool to get reused.

Um. I need a (fresh) connection per CGI process. I wonder if nowadays the TLS startup between the browser and the webserver isn't at least one or two magnitudes more expensive than the (unencrypted) connection to the domain socket of the DB engine. May pooling DB connections instead of closing them be optimizing on the wrong end?

> >

Why not have a Transaction class (struct)?

A transaction class/struct cannot hook normal return and throwing differently (the destructor has no way of knowing whether an exception is in flight).

Regarding C++: “[...] In a proper robust design, commits should always be explicit and destructors only rollback uncommitted transactions. [...]

There is now enough introspection in the language to actually implement implicit commit in a non exceptional path, but I think it would be a mistake."

[1] https://news.ycombinator.com/item?id=24648406

June 14, 2022

I don't see what you see wrong with the code I wrote. It's straightforward, obvious, and does the job I need it to do, in a way that's not prone to future mistakes.

I explained why, but you don't agree with the explanation. That's OK, we don't all have to write the same exact systems. D is a language that can satisfy many needs and philosophies! If you find it doesn't do it quite the way you want, and C++ does, C++ is also a fine language to use.

-Steve