What does "0 but true" mean in Perl?

Can someone explain what exactly the string "0 but true" means in Perl? As far as I understand, it equals zero in an integer comparison, but evaluates to true when used as a boolean. Is this correct? Is this a normal behavior of the language or is this a special string treated as a special case in the interpreter?


Solution 1:

It's normal behaviour of the language. Quoting the perlsyn manpage:

The number 0, the strings '0' and "", the empty list (), and undef are all false in a boolean context. All other values are true. Negation of a true value by ! or not returns a special false value. When evaluated as a string it is treated as "", but as a number, it is treated as 0.

Because of this, there needs to be a way to return 0 from a system call that expects to return 0 as a (successful) return value, and leave a way to signal a failure case by actually returning a false value. "0 but true" serves that purpose.

Solution 2:

Because it's hardcoded in the Perl core to treat it as a number. This is a hack to make Perl's conventions and ioctl's conventions play together; from perldoc -f ioctl:

The return value of ioctl (and fcntl) is as follows:

if OS returns:      then Perl returns:

    -1              undefined value
     0              string "0 but true"
anything else       that number

Thus Perl returns true on success and false on failure, yet you can still easily determine the actual value returned by the operating system:

$retval = ioctl(...) || -1;
printf "System returned %d\n", $retval;

The special string "0 but true" is exempt from -w complaints about improper numeric conversions.

Solution 3:

Additionally to what others said, "0 but true" is special-cased in that it doesn't warn in numeric context:

$ perl -wle 'print "0 but true" + 3'
3
$ perl -wle 'print "0 but crazy" + 3'
Argument "0 but crazy" isn't numeric in addition (+) at -e line 1.
3

Solution 4:

The value 0 but true is a special case in Perl. Although to your mere mortal eyes, it doesn't look like a number, wise and all knowing Perl understands it really is a number.

It has to do with the fact that when a Perl subroutine returns a 0 value, it is assumed that the routine failed or returned a false value.

Imagine I have a subroutine that returns the sum of two numbers:

die "You can only add two numbers\n" if (not add(3, -2));
die "You can only add two numbers\n" if (not add("cow", "dog"));
die "You can only add two numbers\n" if (not add(3, -3));

The first statement won't die because the subroutine will return a 1. That's good. The second statement will die because the subroutine won't be able to add cow to dog.

And, the third statement?

Hmmm, I can add 3 to -3. I just get 0, but then my program will die even though the add subroutine worked!

To get around this, Perl considers 0 but true to be a number. If my add subroutine returns not merely 0, but 0 but true, my third statement will work.

But is 0 but true a numeric zero? Try these:

my $value = "0 but true";
print qq(Add 1,000,000 to it: ) . (1_000_000 + $value) . "\n";
print "Multiply it by 1,000,000: " . 1_000_000 * $value . "\n";

Yup, it's zero!

The index subroutine is a very old piece of Perl and existed before the concept of 0 but true was around. It is suppose to return the position of the substring located in the string:

index("barfoo", "foo");   #This returns 3
index("barfoo", "bar");   #This returns 0
index("barfoo", "fu");    #This returns ...uh...

The last statment returns a -1. Which means if I did this:

if ($position = index($string, $substring)) {
   print "It worked!\n";
}
else {
   print "If failed!\n";
}

As I normally do with standard functions, it wouldn't work. If I used "barfoo" and "bar" like I did in the second statement, The else clause would execute, but if I used "barfoo" and "fu" as in the third, the if clause would execute. Not what I want.

However, if the index subroutine returned 0 but true for the second statement and undef for the third statement, my if/else clause would have worked.