Skip to content

Adding a try/catch syntax to Perl #18504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
leonerd opened this issue Jan 22, 2021 · 19 comments
Closed

Adding a try/catch syntax to Perl #18504

leonerd opened this issue Jan 22, 2021 · 19 comments

Comments

@leonerd
Copy link
Contributor

leonerd commented Jan 22, 2021

I intend to add try/catch syntax. This has been one of the most frequently-requested of core syntax features - from both community sources like Freenode #perl and other core developers - so I do not anticipate this will be a contentious or debated idea.

As many of the existing CPAN solutions all follow a very similar theme - including my own Syntax::Keyword::Try - I expect I shall follow that pattern, and likewise do not anticipate that this will be considered controversial. Syntax::Keyword::Try has been the preferred module by Freenode #perl for a while now, and folks seem happy with it.

I further intend it to be introduced as

use feature 'try';

Towards this goal, I have created a module Feature::Compat::Try to simplify migration. Existing authors can

use Feature::Compat::Try;

in their current code, today, and import a configured version of Syntax::Keyword::Try which works on all Perl versions back to 5.16, and conforms exactly to the specification of the thing I want to add to core. (S:K:T additionally contains other features, experimental ideas, and past mistakes, that I do not intend to bring in at this time, hence the configuration).

A brief summary, from its synopsis, is:

use Feature::Compat::Try;

sub foo
{
   try {
      attempt_a_thing();
      return "success";
   }
   catch ($e) {
      warn "It failed - $e";
      return "failure";
   }
}

If anyone wishes to review in more detail the syntax and semantics, please read the documentation and unit-tests of this module. That is what I intend to implement exactly. You do not have to wait until I have a PR to actually implement it to do this - the earlier you do this, the better :)

@leonerd
Copy link
Contributor Author

leonerd commented Jan 22, 2021

Cross-posted to perl5-porters@ at
https://www.nntp.perl.org/group/perl.perl5.porters/2021/01/msg258856.html

@leonerd leonerd changed the title [feature] Adding a try/catch syntax to Perl Jan 22, 2021
@leonerd
Copy link
Contributor Author

leonerd commented Jan 23, 2021

Work progresses at pace on this branch:

https://github.com/Perl/perl5/tree/leonerd/feature-try

In fact at time of writing, it now passes all the unit tests of Feature::Compat::Try except for the one about blessed object exceptions that overload boolification to constant false.

Once I have that working, I shall have a go at the remaining core tasks which didn't affect a CPAN module - such as B::Deparse support. And then it should be ready for review.

Hopefully either by end of weekend, or if not, middle of next week.

@leonerd
Copy link
Contributor Author

leonerd commented Jan 23, 2021

There are two points that the current design (docs and tests) leaves entirely unaddressed. Those are

  • How does try/catch interact with $@?
  • How does try/catch interact with $SIG{__DIE__}?

So far it happens to be the same as eval, but only due to a lazy minimalist implementation that does not take steps differently. I suspect a more complete design may have something to say about these facts.

In particular I am tempted by the ideas that

  • $@ is not set within the catch block, so you must use the lexical variable (thus avoiding volatility)
  • $SIG{__DIE__} is not invoked by exceptions thrown within try but caught by catch.

Comments welcome

@mplscorwin
Copy link

Those ideas which tempt you, appeal (seem natural) to me.

@oschwald
Copy link

Not invoking $SIG{__DIE__} may prevent the new syntax from getting used for some use cases. For instance, we use $SIG{__DIE__} to inflate string exceptions to objects, adding stack traces, etc. If this was not done for exceptions caught by a catch, stack frames from within the try block would be lost if the exception was rethrown from the catch.

Also, how would it interact with code called from the try block that sets a localized $SIG{__DIE__}?

@leonerd
Copy link
Contributor Author

leonerd commented Jan 23, 2021

I will admit the $SIG{__DIE__} one hasn't had much thought. There wasn't any ability for SKT to interact with it, so I haven't done anything there so far. Plus I don't tend to use it myself so I haven't really considered it thus far.

@hvds
Copy link
Contributor

hvds commented Jan 23, 2021

There are two points that the current design (docs and tests) leaves entirely unaddressed. Those are

* How does try/catch interact with `$@`?

* How does try/catch interact with `$SIG{__DIE__}`?

So far it happens to be the same as eval, but only due to a lazy minimalist implementation that does not take steps differently. I suspect a more complete design may have something to say about these facts.

In particular I am tempted by the ideas that

* `$@` is not set within the `catch` block, so you must use the lexical variable (thus avoiding volatility)

Will the lexical be set the same (dualvar and all) as $@ would have been? If so, that seems a sane choice.

* `$SIG{__DIE__}` is not invoked by exceptions thrown within `try` but caught by `catch`.

This troubles me: fine right up until you need it, but at that point it is difficult or impossible to work around. I think I'd rather see it invoked, but with $^S set to some new (true) value distinguishable from the current "in eval" state. An orthogonal improvement later might be to be able to flag a $SIG{__DIE__} hook as being uninterested in certain cases of $^S, allowing performance improvements.

@Grinnz
Copy link
Contributor

Grinnz commented Jan 23, 2021

Will the lexical be set the same (dualvar and all) as $@ would have been? If so, that seems a sane choice.

You might be thinking of $!. $@ may be any value that had been passed to die.

@hvds
Copy link
Contributor

hvds commented Jan 23, 2021

Will the lexical be set the same (dualvar and all) as $@ would have been? If so, that seems a sane choice.

You might be thinking of $!. $@ may be any value that had been passed to die.

Oops, yes I was.

@haarg
Copy link
Contributor

haarg commented Jan 24, 2021

For $@, I would argue that try should not touch it at all, or act as if it doesn't. This means that inside the try and catch blocks, it has the same value it had outside them. And any modifications to $@ should be maintained after them. This would match the ideal future where $@ isn't involved at all. The only odd part of this is that a bare die uses $@.

I think $SIG{__DIE__} should be invoked just like with eval. Until core exceptions exist, $SIG{__DIE__} becomes a necessary part of debugging to be able to get a stack trace. You'd need some alternative to this before moving away from __DIE__. Possibly something like running the catch from the context of the die, so that you could inspect caller at that point.

@leonerd
Copy link
Contributor Author

leonerd commented Jan 24, 2021

Righty. In that case I'm happy to accept the wisdom of the crowds on the subject of $SIG{__DIE__} - as I said I hadn't given that one much thought before, but clearly others have, so I will go with your recommendations and add a test to ensure it behaves the same with try as it does with eval.

On the subject of $@, I can easily have it be cleared to undef during the catch block, but it would be difficult in the current implementation to follow haarg's suggestion of

This means that inside the try and catch blocks, it has the same value it had outside them.

The use of $@ is currently deeply entangled with perl innards, and more related to the die operator than the surrounding eval. It would be hard to extract that out and have it operate purely on the lexical $e created by the catch block. I shall have it clear the variable to undef to ensure that people don't accidentally rely on it during the catch, but preserving it unmodified will be somewhat harder.

@haarg
Copy link
Contributor

haarg commented Jan 24, 2021

I shall have it clear the variable to undef to ensure that people don't accidentally rely on it during the catch, but preserving it unmodified will be somewhat harder.

This seems reasonable for an initial implementation. I do think it's important to prevent people from relying on $@ in catch blocks. People already have to deal with $@ being volatile and only being reliable immediately after an eval. Leaving it entirely untouched is more about matching the behavior of a theoretical future where die is changed to not touch it, and be more intimately connected catch.

@nuclight
Copy link

Where is 'finally' ?

@Grinnz
Copy link
Contributor

Grinnz commented Jan 28, 2021

Current plan is to implement it as a phaser: #17949

@nuclight
Copy link

nuclight commented Feb 2, 2021

Well, my question was mostly about running existing Try::Tiny code as unmodified as possible (it allows try/catch/finally as one construction). Would be good if there will be something like finally alias for this LEAVE.

Edit: it seems from there that LEAVE has a "double die" problem, but for exceptions it is common to re-throw them, so finally looks more natural.

leonerd added a commit that referenced this issue Feb 4, 2021
 * Add feature, experimental warning, keyword
 * Basic parsing
 * Basic implementation as optree fragment

See also
  #18504
@leonerd
Copy link
Contributor Author

leonerd commented Feb 4, 2021

With #18505 now merged, this is partway finished. I don't want to close this issue yet, because much work still needs to be done.

  • B::Deparse does not yet support the new syntax - this really must be done before a final release
  • Some of the internals could do with tidying and neatening up - until this is done I don't want to work on B::Deparse though as it will affect the opcode shape

However, the Perl-user visible syntax should now be stable, so as of the next point release (5.33.7) folks should be able to start playing with this under the feature guard use feature 'try';.

@leonerd
Copy link
Contributor Author

leonerd commented Feb 9, 2021

I've now opened a second PR #18552 to address the concerns listed above. It's purely internals-neatening and should not affect the user-visible syntax or semantics.

@leonerd
Copy link
Contributor Author

leonerd commented Feb 10, 2021

One other thing that turns out already works by coïncidence, though I haven't yet documented or tested, is value semantics (including list valued return) while inside a do { ... } block:

leo@shy:~/src/bleadperl/perl [git]
$ ./perl -Ilib -Mexperimental=try -E 'my $x = do { try { 123 } catch($e) { 456 } }; say $x'
123

leo@shy:~/src/bleadperl/perl [git]
$ ./perl -Ilib -Mexperimental=try -E 'my $x = do { try { 123; die } catch($e) { 456 } }; say $x'
456

leo@shy:~/src/bleadperl/perl [git]
$ ./perl -Ilib -Mexperimental=try -E 'my @x = do { try { 1, 2, 3 } catch($e) { 4, 5, 6 } }; $, = ","; say @x'
1,2,3

leo@shy:~/src/bleadperl/perl [git]
$ ./perl -Ilib -Mexperimental=try -E 'my @x = do { try { 1, 2, 3; die } catch($e) { 4, 5, 6 } }; $, = ","; say @x'
4,5,6

I may consider documenting that and adding a unit test, if it's something we feel we should give a guarantee about.

@leonerd
Copy link
Contributor Author

leonerd commented Feb 14, 2021

With #18552 now merged, and additionally the-above semantics about final expression values being documented and tested by 397e6c1 I think I am happy to mark this Issue as complete.

The overall try/catch experiment is still ongoing, and will probably gain some typed dispatch syntax (catch(VAR isa CLASS), catch(VAR =~ PATTERN)) and maybe finally blocks, but that will wait for 5.35.

For now I think this is good to test in 5.33 and 5.34. :)

@leonerd leonerd closed this as completed Feb 14, 2021
Corion pushed a commit to Corion/perl5 that referenced this issue Jun 20, 2021
 * Add feature, experimental warning, keyword
 * Basic parsing
 * Basic implementation as optree fragment

See also
  Perl#18504

# Conflicts:
#	ext/Opcode/Opcode.pm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants