-
Notifications
You must be signed in to change notification settings - Fork 293
/
exceptions.pod6
400 lines (294 loc) · 11.4 KB
/
exceptions.pod6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
=begin pod :tag<perl6>
=TITLE Exceptions
=SUBTITLE Using exceptions in Perl 6
Exceptions in Perl 6 are objects that hold information about errors. An
error can be, for example, the unexpected receiving of data or a network
connection no longer available, or a missing file. The information that
an exception objects store is, for instance, a human-readable message
about the error condition, the backtrace of the raising of the error,
and so on.
All built-in exceptions inherit from L<Exception|/type/Exception>, which provides some basic
behavior, including the storage of a backtrace and an interface for the
backtrace printer.
=head1 I<Ad hoc> exceptions
Ad hoc exceptions can be used by calling L<die|/routine/die> with
a description of the error:
die "oops, something went wrong";
# RESULT: «oops, something went wrong in block <unit> at my-script.p6:1»
It is worth noting that C<die> prints the error message to the standard error
C<$*ERR>.
=head1 Typed exceptions
Typed exceptions provide more information about the error stored
within an exception object.
For example, if while executing C<.zombie copy> on an object, a needed path
C<foo/bar> becomes unavailable, then an L<X::IO::DoesNotExist> exception can be
raised:
die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"))
# RESULT: «Failed to find 'foo/bar' while trying to do '.zombie copy'
# in block <unit> at my-script.p6:1»
Note how the object has provided the backtrace with information about what
went wrong. A user of the code can now more easily find and correct the
problem.
=head1 Catching exceptions
It's possible to handle exceptional circumstances by supplying a C<CATCH> block:
die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy"));
CATCH {
when X::IO { $*ERR.say: "some kind of IO exception was caught!" }
}
# OUTPUT: «some kind of IO exception was caught!»
Here, we are saying that if any exception of type C<X::IO> occurs, then the
message C<some kind of IO exception was caught!> will be sent to I<stderr>,
which is what C<$*ERR.say> does, getting displayed on whatever constitutes the
standard error device in that moment, which will probably be the console by
default.
A X<C<CATCH>|CATCH> block uses smartmatching similar to how C<given/when>
smartmatches on options, thus it's possible to catch and handle various
categories of exceptions inside a C<when> block.
To handle all exceptions, use a C<default> statement. This example prints out
almost the same information as the normal backtrace printer.
CATCH {
default {
$*ERR.say: .payload;
for .backtrace.reverse {
next if .file.starts-with('SETTING::');
next unless .subname;
$*ERR.say: " in block {.subname} at {.file} line {.line}";
}
}
}
Note that the match target is a role. To allow user defined exceptions to match
in the same manner, they must implement the given role. Just existing in the
same namespace will look alike but won't match in a C<CATCH> block.
=head2 Exception handlers and enclosing blocks
After a CATCH has handled the exception, the block enclosing the C<CATCH> block
is exited.
In other words, even when the exception is handled successfully, the I<rest of
the code> in the enclosing block will never be executed.
die "something went wrong ...";
CATCH {
# will definitely catch all the exception
default { .Str.say; }
}
say "This won't be said."; # but this line will be never reached since
# the enclosing block will be exited immediately
# OUTPUT: «something went wrong ...»
Compare with this:
CATCH {
CATCH {
default { .Str.say; }
}
die "something went wrong ...";
}
say "Hi! I am at the outer block!"; # OUTPUT: «Hi! I am at the outer block!»
See L<Resuming of exceptions|/language/exceptions#Resuming_of_exceptions>, for
how to return control back to where the exception originated.
X<|try blocks>
=head1 C<try> blocks
A C<try> block is a normal block which implicitly turns on the
L<C<use fatal> pragma|/language/pragmas#index-entry-fatal-fatal> and
includes an implicit C<CATCH> block that drops the exception, which
means you can use it to contain them. Caught exceptions are stored
inside the C<$!> variable, which holds a value of type C<Exception>.
A normal block like this one will simply fail:
=for code
{
my $x = +"a";
say $x.^name;
} # OUTPUT: «Failure»
However, a C<try> block will contain the exception and put it into the
C<$!> variable:
=begin code
try {
my $x = +"a";
say $x.^name;
}
if $! { say "Something failed!" } # OUTPUT: «Something failed!»
say $!.^name; # OUTPUT: «X::Str::Numeric»
=end code
Any exception that is thrown in such a block will be caught by a
C<CATCH> block, either implicit or provided by the user. In the latter
case, any unhandled exception will be rethrown. If you choose not to
handle the exception, they will be contained by the block.
=begin code
try {
die "Tough luck";
say "Not gonna happen";
}
try {
fail "FUBAR";
}
=end code
X<|resume (Exceptions)>
In both C<try> blocks above, exceptions will be contained within the
block, but the C<say> statement will not be run. We can handle them,
though:
class E is Exception { method message() { "Just stop already!" } }
try {
E.new.throw; # this will be local
say "This won't be said.";
}
say "I'm alive!";
try {
CATCH {
when X::AdHoc { .Str.say; .resume }
}
die "No, I expect you to DIE Mr. Bond!";
say "I'm immortal.";
E.new.throw;
say "No, you don't!";
}
Which would output:
=begin code :lang<text>
I'm alive!
No, I expect you to DIE Mr. Bond!
I'm immortal.
Just stop already!
in block <unit> at exception.p6 line 21
=end code
Since the C<CATCH> block is handling just the C<X::AdHoc> exception
thrown by the C<die> statement, but not the C<E> exception. In the
absence of a C<CATCH> block, all exceptions will be contained and
dropped, as indicated above. C<resume> will resume execution right after
the exception has been thrown; in this case, in the C<die> statement.
Please consult the section on
L<resuming of exceptions|/language/exceptions#Resuming_of_exceptions>
for more
information on this.
A C<try>-block is a normal block and as such treats its last statement
as the return value of itself. We can therefore use it as a right-hand
side.
=begin code
say try { +"99999" } // "oh no"; # OUTPUT: «99999»
say try { +"hello" } // "oh no"; # OUTPUT: «oh no»
=end code
Try blocks support C<else> blocks indirectly by returning the return
value of the expression or L<Nil|/type/Nil> if an exception was thrown.
with try +"♥" {
say "this is my number: $_"
} else {
say "not my number!"
}
# OUTPUT: «not my number!»
C<try> can also be used with a statement instead of a block:
=begin code
say try "some-filename.txt".IO.slurp // "sane default";
# OUTPUT: «sane default»
=end code
What C<try> actually causes is, via the C<use fatal> pragma, an immediate throw
of the exceptions that happen within its scope, but by doing so the C<CATCH>
block is invoked from the point where the exception is thrown, which defines its
scope.
=begin code
my $error-code = "333";
sub bad-sub {
die "Something bad happened";
}
try {
my $error-code = "111";
bad-sub;
CATCH {
default {
say "Error $error-code ", .^name, ': ',.Str
}
}
}
# OUTPUT: «Error 111 X::AdHoc: Something bad happened»
=end code
=head1 Throwing exceptions
Exceptions can be thrown explicitly with the C<.throw> method of an
C<Exception> object.
This example throws an C<AdHoc> exception, catches it and allows the code
to continue from the point of the exception by calling the C<.resume> method.
{
X::AdHoc.new(:payload<foo>).throw;
"OHAI".say;
CATCH {
when X::AdHoc { .resume }
}
}
"OBAI".say;
# OUTPUT: «OHAIOBAI»
If the C<CATCH> block doesn't match the exception thrown, then the
exception's payload is passed on to the backtrace printing mechanism.
{
X::AdHoc.new(:payload<foo>).throw;
"OHAI".say;
CATCH { }
}
"OBAI".say;
# RESULT: «foo
# in block <unit> at my-script.p6:1»
This next example doesn't resume from the point of the exception. Instead,
it continues after the enclosing block, since the exception is caught, and then
control continues after the C<CATCH> block.
{
X::AdHoc.new(:payload<foo>).throw;
"OHAI".say;
CATCH {
when X::AdHoc { }
}
}
"OBAI".say;
# OUTPUT: «OBAI»
C<throw> can be viewed as the method form of C<die>, just that in this
particular case, the sub and method forms of the routine have different
names.
=head1 Resuming of exceptions
Exceptions interrupt control flow and divert it away from the statement
following the statement that threw it. Any exception handled by the
user can be resumed and control flow will continue with the statement
following the statement that threw the exception. To do so, call the
method C<.resume> on the exception object.
CATCH { when X::AdHoc { .resume } } # this is step 2
die "We leave control after this."; # this is step 1
say "We have continued with control flow."; # this is step 3
Resuming will occur right after the statement that has caused the exception, and
in the innermost call frame:
=begin code
sub bad-sub {
die "Something bad happened";
return "not returning";
}
{
my $return = bad-sub;
say "Returned $return";
CATCH {
default {
say "Error ", .^name, ': ',.Str;
$return = '0';
.resume;
}
}
}
# OUTPUT:
# Error X::AdHoc: Something bad happened
# Returned not returning
=end code
In this case, C<.resume> is getting to the C<return> statement that happens
right after the C<die> statement. Please note that the assignment to C<$return>
is taking no effect, since the C<CATCH> statement is happening I<inside> the
call to C<bad-sub>, which, via the C<return> statement, assigns the C<not
returning> value to it.
=head1 Uncaught exceptions
If an exception is thrown and not caught, it causes the program to exit with a
non-zero status code, and typically prints a message to the standard error
stream of the program. This message is obtained by calling the C<gist> method
on the exception object. You can use this to suppress the default behavior of
printing a backtrace along with the message:
class X::WithoutLineNumber is X::AdHoc {
multi method gist(X::WithoutLineNumber:D:) {
$.payload
}
}
die X::WithoutLineNumber.new(payload => "message")
# prints "message\n" to $*ERR and exits, no backtrace
=head1 Control exceptions
Control exceptions are thrown by certain L<keywords|/language/phasers#CONTROL>
and are handled either automatically or by the appropriate
L<phaser|/language/phasers#Loop_phasers>. Any unhandled control exception is
converted to a normal exception.
{ return; CATCH { default { $*ERR.say: .^name, ': ',.Str } } }
# OUTPUT: «X::ControlFlow::Return: Attempt to return outside of any Routine»
# was CX::Return
=end pod
# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6