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

I saw this in constant.pm
$VERSION = '1.05'; foreach my $name ( keys %constants ) { unless (defined $name) { require Carp; Carp::croak("Can't use undef as constant name"); } ... }
and suddenly had an uncomfortable feeling. Are we supposed to watch out for nasty undefs slipping in there and taking up key seats in our hashes?

Replies are listed 'Best First'.
Re: undef as a key in a hash.
by Joost (Canon) on Sep 25, 2007 at 18:34 UTC
    undef will stringify as "" so they can be used as hash keys, equivalent to an empty string.

    warnings will complain though:

    perl -we'$b=undef;$a{$b}=1;print$a{""}' Use of uninitialized value in hash element at -e line 1. 1

    update: strictly speaking a hash key (for a normal hash) can't be undef, a reference, an object or anything but a plain string, and using anything as a hash key that isn't a string will use the stringified form. Which explains the behaviour in TedYoung's example.

      strictly speaking a hash key (for a normal hash) can't be undef, a reference, an object or anything but a plain string, and using anything as a hash key that isn't a string will use the stringified form.

      That's only true for plain hashes. Tied hashes are not subject to those restrictions, for example. (See code below.) Tied hashes can accept all of those as a key (including undef, although it generates a warning), but tied hashes can't have each/keys/values return undef because it's a sentinel value indicating all of the keys have been visited.

      So the question is whether there's another type of magical hash that can have keys return undef.

      use strict; use warnings; { package TestHash; sub keyinfo { my ($k) = @_; if (defined($k)) { if (ref($k)) { print("reference\n"); } else { print("string\n"); } } else { print("undefined\n"); } } sub TIEHASH { my ($c ) = @_; return bless([], $c); } sub FETCH { my ($s,$k ) = @_; keyinfo($k); return undef; } sub STORE { my ($s,$k,$v) = @_; keyinfo($k); push @$s, $k; } sub FIRSTKEY { my ($s ) = @_; return shift @$s; } sub NEXTKEY { my ($s ) = @_; return shift @$s; } } tie my %h, 'TestHash'; foreach my $k ('a', [], undef) { $h{$k} = 1; } print("\n"); foreach my $k ('a', [], undef) { my $dummy = $h{$k}; } print("\n"); foreach my $k (keys %h) { TestHash::keyinfo($k); }
      string reference Use of uninitialized value in hash element at line 29. undefined string reference Use of uninitialized value in hash element at line 31. undefined string reference
Re: undef as a key in a hash.
by liverpole (Monsignor) on Sep 25, 2007 at 18:47 UTC
    For fun, here's an example of using undef as a hash key.  It validates what Joost and Eimi Metamorphoumai have said, that as a key, an undef is equivalent to the empty string "":
    #!/usr/bin/perl -w use strict; use warnings; use Data::Dumper; my $x; # $x is undef my $y; # $y is undef too my %hash; $hash{$x} = 123; # This will throw warnings, but both $hash{$x} and $hash{$y} are 123 printf "\$hash{\$x} = %s; \$hash{\$y} = %s\n", $hash{$x}, $hash{$y}; print "Keys of \$hash => %s\n", Dumper([keys %hash]); __END__ Output: Use of uninitialized value in hash element at x line 11. Use of uninitialized value in hash element at x line 11. Use of uninitialized value in hash element at x line 14. Use of uninitialized value in hash element at x line 14. $hash{$x} = 123; $hash{$y} = 123 Keys of $hash => $VAR1 = [ '' ];

    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: undef as a key in a hash.
by TedYoung (Deacon) on Sep 25, 2007 at 18:34 UTC

    I had to try this myself, because I couldn't believe it:

    $a{undef} = 1; print defined for keys %a; # Output 1 # This doesn't work either: %a = (undef, 1);

    I wonder what he was thinking...

    Ted Young

      I wonder what he was thinking...

      The hash-style usage of constant wasn't added until perl 5.7.1, and the defined $name check was around before that. Basically all of 5.7.0's code was kept intact and put in the for loop. The defined $name check was just accidentally (I presume) left inside the for loop.

      Here's how it should look.

      my $multiple = ref $_[0]; if ( $multiple ) { ... } else { unless (defined $_[0]) { require Carp; Carp::croak("Can't use undef as constant name"); } $constants{+shift} = undef; } foreach my $name ( keys %constants ) { ... }
      Not that this matters much, because
      use constant undef, 'x';
      will fail anyway. You just get a slightly less clear error message.

      lodin

Re: undef as a key in a hash.
by Eimi Metamorphoumai (Deacon) on Sep 25, 2007 at 18:40 UTC
    Hash keys are always strings, so if you try to use undef it will be coerced to the empty string, with a warning ("Use of uninitialized value in hash element") if warnings are enabled. So it shouldn't actually be possible for that code to be called.
Re: undef as a key in a hash.
by kyle (Abbot) on Sep 25, 2007 at 18:48 UTC

    It looks to me as if undef used as a hash key gets turned into an empty string.

    use Test::More tests => 6; my %h = ( undef() => 1 ); foreach my $key ( keys %h ) { ok( defined $key, 'key is defined' ); is( $key, '', 'key is empty string' ); } is_deeply( [ '' ], [ keys %h ], 'undef key' ); is_deeply( [ 1 ], [ values %h ], 'values works' ); is( $h{undef()}, 1, 'undef key accesses value' ); is( $h{''}, 1, 'empty string key accesses value' );

    Even when I try to be rude and slip it in there, it won't go.

    package UndefKey; sub TIEHASH { bless [], __PACKAGE__ } sub FIRSTKEY { undef } sub NEXTKEY { undef } sub FETCH { 1 } package main; #my %h = ( undef() => 1 ); tie my %h, 'UndefKey';

    (Running the same tests as before, only the last two succeed, and the ones in the foreach do not run because keys returns an empty list.)

Re: undef as a key in a hash.
by bart (Canon) on Sep 26, 2007 at 12:16 UTC
    I couldn't believe it, but that really is in the source of constant 1.05, the version that comes with perl 5.8.8 (and also in the unauthorized release constant 1.11). What a major think-o by the author.

    No, it could never work. Hash keys are strings. undef is not a string.

Re: undef as a key in a hash.
by ursus (Acolyte) on Sep 25, 2007 at 22:08 UTC

    I think the error message might be better written Can't use '' as a constant name. Or maybe if you try to name a constant 'undef' its key mutates into '', which this code then croaks on.

    Or maybe this piece of code never gets hit.

      Nope. '' won't reach there. '' is defined. He'd have to use length instead of defined to check for ''.
        Or use it as a boolean. :-)