cavac has asked for the wisdom of the Perl Monks concerning the following question:

I run into a strange thing in how croak() (from the Carp module) works:

#!/usr/bin/env perl use v5.36; use strict; use warnings; use Carp; open(my $ifh, '<', 'doesnotexist.dat') or croak($!);

This outputs:

 at carptest2.pl line 9.

But when i change croak($!) to croak("$!"), everything works as expected:

No such file or directory at carptest2.pl line 9.

Shouldn't that result in the exact same output with both variants? It certainly does so with the builtin die(), where both die($!) and die("$!") print:

No such file or directory at carptest2.pl line 9.

Do i have a basic misunderstanding on how $! works/should work or is this a bug (or workaround) with Carp?

PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP

Replies are listed 'Best First'.
Re: Carp: $! vs "$!" in croaking
by Corion (Patriarch) on Jun 26, 2023 at 09:26 UTC

    $! is a magic variable that reflects the error of the latest error that occurred while doing a syscall (errno()).

    Most likely, croak() does some syscall that succeeds, while die does not.

    So, if you want to keep the value of $! at some point in time, you need to make a copy of it. One way to do that is to use double quotes, like you showed.

Re: Carp: $! vs "$!" in croaking (updated)
by haukex (Archbishop) on Jun 26, 2023 at 09:27 UTC

    One of the first things Carp does is local($!, $^E), I assume because of what its documentation says: "Carp takes care not to clobber the status variables $! and $^E in the course of assembling its error messages." I think what's going on is that since @_ are aliases, this actually clears out the argument as well. Another thing to note is that $! is like a dualvar, though I'm not sure if this has to do with the issue. <update> Actually, in the following, $? behaves the same way as $!, while $@, an our variable, and an explicitly created dualvar do not - so it probably does have to do with the variable's magic in combination with local instead. </update> I suspect you'll have to work with "$!". This reproduces the issue:

    use warnings; use strict; use Data::Dump; sub foo { dd @_; local $!; dd @_; } $! = 2; foo($!); $! = 2; foo("$!"); __END__ "No such file or directory" "" "No such file or directory" "No such file or directory"

      I think what's going on is that since @_ are aliases, this actually clears out the argument as well.

      That would be the behaviour you'd get if local simply backed up the current value of a variable and restored it on scope exit.

      As you discovered, that's not what local does. Instead, it creates a new variable, aliases the name to the new variable, and aliases the name back to the original variable on scope exit.

      $ perl -e' use feature qw( say ); say 0+\$x; { local $x; say 0+\$x; } say 0+\$x; ' 94031376424552 94031376229560 94031376424552

      That means the fact that $_[0] is an alias for the localized variable shouldn't matter. And it normally doesn't.

      $ perl -e' use feature qw( say ); $x = 2; say 0+\$x; say $x; sub { local $x = 3; say 0+\$x; say $x; say $_[0]; }->( $x ); say 0+\$x; say $x; ' 94300947464040 Address of pre-local $x 2 Value of pre-local $x 94300947268840 Address of post-local $x 3 Value of post-local $x 2 Value of pre-local $x 94300947464040 $x is restored 2 Value of pre-local $x

      But $! is magical. And more specifically, it's a proxy for a C variable. local copies the magic onto the new scalar it creates. So you end up with two variables (the $! passed as argument and the new $! created by local) which both use the same C variable for storage.

      $ perl -e' use feature qw( say ); $! = 2; say 0+\$!; say 0+$!; sub { local $! = 3; say 0+\$!; say 0+$!; say 0+$_[0]; }->( $! ); say 0+\$!; say 0+$!; ' 93839899443048 Address of pre-local $! 2 Value of pre-local $! (aka value of errno) 93839899247848 Address of post-local $! 3 Value of post-local $! (aka value of errno) 3 Value of pre-local $! (aka value of errno) 93839899443048 $! is restored 3 Value of pre-local $! (aka value of errno)

      The net effect is that local $! doesn't really localize $! at all!

      $? is similarly a proxy for a C variable. $@ isn't magical.

        Accidentally submitted parent very early in its composition. (Mid sentence, even!) It has been heavily edited.

      Grumble. I feared that it's something i couldn't blame Carp for. Oh well, i'll spend a couple of hours writing a Perl script that audits and fixes all my other Perl scripts and modules to make sure arguments to croak() are quoted properly.

      Hmm, i could either modify an existing script (which probably has all the crud from the last 12 years) or ask ChatGPT to do it (and fix all the variable names and regular expressions). I could also write from scratch, but it's Monday and i'm not feeling THAT enthusiastic about fixing some error message handling...

      PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
Re: Carp: $! vs "$!" in croaking
by haj (Vicar) on Jun 26, 2023 at 10:54 UTC

    I guess that's a consequence of $! being localized rather early: You pass $!... which is available as an alias in Carp::croak. By localizing $!, it also masks the value you passed to the subroutine.

    Demo:

    use 5.032; sub foo { local $!; my $err = shift; say "foo found: '$err'"; } sub bar { my $err = shift; local $!; say "bar found: '$err'"; } open (my $foo, '<', 'doesnotexist.dat') or foo($!); open (my $bar, '<', 'doesnotexist.dat') or bar($!);

    Output:

    foo found: '' bar found: 'No such file or directory'

    In foo, your $! gets clobbered by localizing. If you quote it, you've already copied the string value of $!, so it doesn't matter that it gets localized afterwards.