Avoiding the dreaded use of uninitialized value in concatenation (.) Or string error when passing hashes as an argument
I have something like this:
do_something({ key1 => 'value1', key2 => 'value2'});
sub do_something {
$args = $_[0];
print $$args{key1} . $$args{key2} . $$args{key3};
}
This produces an annoying error Use of uninitialized value in concatenation (.) or string
because I haven't added the third key.
I'm lazy. So I don't want to add the third key to the hash. I'm fine, not printing anything for the third key in the function.
But I'm anal. I want to get rid of the error like this:
sub do_something {
$args = $_[0];
$third_key = $$args{key3} ? $$args{key3} : '';
print $$args{key1} . $$args{key2} . $$args{key3};
}
However, I'm super ultra lazy. I don't want to write a line like this for every parameter that can be empty.
Is there an easy solution for this?
source to share
print join '', map $args->{$_}, keys %$args;
or
sub do_something {
my %defaults = (
key3 => ''
);
my $args = $_[0];
# overlay defaults, this will modify the data structure in the caller
$args->{$_} ||= $defaults{$_}
foreach keys %defaults;
print $args->{key1} . $args->{key2} . $args->{key3};
}
source to share
You asked Perl to warn you about this when you wrote
use warnings;
So you have a choice (smallest to safest):
-
Delete
use warnings
so that no warnings are issued. The disadvantage is that no warnings will be issued, which will make it difficult to debug. -
Remove warning for uninitialized variables. This can be achieved with:
no warnings qw(uninitialized);
Put this after you tell it to enable alerts and you only disable one alert. -
Remove warning for uninitialized variables in the smallest possible scope. By placing the no warnions command in a smaller lexical scope, you can localize warning suppression only in places where you expect uninitialized variables to print, to help you catch errors elsewhere.
This last option can be accomplished with the following code.
do_something({ key1 => 'value1', key2 => 'value2'});
sub do_something {
no warnings qw(uninitialized);
$args = $_[0];
print $$args{key1} . $$args{key2} . $$args{key3};
}
source to share
Instead of passing a hash reference, just pass the hash values and use Perl's canonical method for default values for named arguments:
do_something(key1 => 'value1', key2 => 'value2');
sub do_something {
my %args = (key1 => '',
key2 => '',
key3 => '',
@_
);
print $args{key1} . $args{key2} . $args{key3};
}
source to share
OK, here's what I came up with, borrowing heavily from mwp's suggestion to use the default hash.
my $args = arg_init($_[0], {default1 => value1, defaults2 => value2}, 'value4', 'value5', 'value6');
sub arg_init {
my $args = shift; # $args is a hashref of arguments
set_default_args($args, shift) if ref($_[0]);
while (my $arg = shift) {
$$args{$arg} = exists $$args{$arg} ? $$args{$arg} : '';
}
return $args;
}
sub set_default_args {
my ($args, $defaults) = @_;
$args->{$_} ||= $$defaults{$_}
foreach keys %$defaults;
}
The arg_init function is called to initialize the arguments passed to the hash. First, it sees if the default hash is a hash link and, if so, sets the default values given in the hash link. It then looks at the scalar values passed to the function and sets those values to the empty string if they don't exist in the original hash argument.
source to share