perl.gg / hidden-gems

<!-- category: hidden-gems -->

The diagnostics Pragma

2026-04-29

Perl tells you something is wrong. Then it tells you absolutely nothing useful about what.
use warnings; my @x = (1, 2, 3); my $y = @x[1];
Scalar value @x[1] better written as $x[1] at script.pl line 3.
Okay. But why is it better? What is actually happening? What does @x[1] do differently than $x[1]? Perl knows the answer. It just does not feel like sharing.

Until you ask nicely.

use diagnostics; use warnings; my @x = (1, 2, 3); my $y = @x[1];
Scalar value @x[1] better written as $x[1] at script.pl line 4 (#1) (W syntax) You've used an array slice (indicated by @) to select a single element of an array. Generally it's better to ask for a scalar value (indicated by $). The difference is that $foo[&bar] always behaves like a scalar, both when assigning to it and when evaluating its argument, while @foo[&bar] behaves like a list when you assign to it, and provides a list context to its subscript, which can do weird things if you're expecting only one subscript.
Now we're talking. A full paragraph of explanation, pulled straight from Perl's internal documentation. The terse one-liner becomes a mini-tutorial. This is diagnostics.

Part 1: THE BASIC USAGE

One line. That is all it takes:
use diagnostics;
Drop it at the top of your script, right after use warnings. Every warning and error message that Perl emits gets expanded into a multi-paragraph explanation sourced from the perldiag man page.
#!/usr/bin/env perl use strict; use warnings; use diagnostics; my %h; my $val = $h{nonexistent}{deep}{key};
Without diagnostics, you get nothing (autovivification is silent here). But try something that does warn:
#!/usr/bin/env perl use strict; use warnings; use diagnostics; print "hello" . undef;
Use of uninitialized value in concatenation (.) or string at script.pl line 6 (#1) (W uninitialized) An undefined value was used as if it were already defined. It was interpreted as a "" or a 0, but maybe it was a mistake. To suppress this warning assign a defined value to your variables. ...
Instead of just "uninitialized value," you get a complete explanation of what Perl did with that undef and what you probably should have done instead.

Part 2: HOW IT WORKS UNDER THE HOOD

The diagnostics pragma is not magic. It is a module that ships with Perl core, and it does exactly two things:
  1. At compile time, it loads and parses the entire perldiag man page
into a lookup table.
  1. It installs a $SIG{__WARN__} handler and a $SIG{__DIE__} handler
that intercept every warning and error, look up the message pattern in the table, and append the verbose explanation.
Perl emits warning | v $SIG{__WARN__} fires | v diagnostics looks up the message pattern in perldiag | v Prints original message PLUS the multi-paragraph explanation
That is why it works on every warning and every fatal error. It is sitting between Perl's messaging system and your terminal, enriching every message that passes through.

The lookup is pattern-based. Perl's diagnostic messages contain placeholders like %s and %d. The module matches your actual error text against these patterns to find the right explanation.

Part 3: BEFORE AND AFTER

Here is a side-by-side gallery. On the left, what Perl normally says. On the right, the gist of what diagnostics adds.
TERSE: "my" variable $x masks earlier declaration VERBOSE: You declared a variable with the same name in the same scope. The inner declaration hides the outer one. This is almost always a mistake. TERSE: Useless use of a constant in void context VERBOSE: You used a value (like a string or number) as a statement but didn't do anything with the result. Perl computed it and threw it away. You probably meant to assign it. TERSE: Bareword "foo" not allowed while "strict subs" in use VERBOSE: You used an unquoted string where Perl expected an operator or expression. Under strict subs, barewords aren't allowed as a safety measure. Quote it or declare a subroutine. TERSE: Can't use string ("3") as an ARRAY ref VERBOSE: You tried to dereference a plain string as if it were a reference. Something upstream gave you a string instead of the reference you expected.
The verbose versions are not guesses. They are the official Perl documentation for that exact diagnostic. Written by the Perl porters. Reviewed over decades. Usually accurate and helpful.

Part 4: THE SPLAIN COMMAND

Do not want to add use diagnostics to your code? Use splain from the command line instead. It is a filter that reads Perl error output and adds the verbose explanations:
$ perl script.pl 2>&1 | splain
Same lookup table, same explanations. But it processes output after the fact instead of hooking into the runtime. Feed it any Perl error messages and it will expand them.

You can also use it on saved log files:

$ perl buggy_script.pl 2> errors.log $ splain < errors.log
The splain tool ships with Perl. No installation needed. It is literally the same perldiag lookup engine, packaged as a command-line filter.

One difference: splain only processes text you feed it. It does not hook into your running program. If your script swallows warnings internally, splain will never see them.

Part 5: RUNTIME OVERHEAD

Here is the part nobody talks about. The diagnostics pragma is not free.

At compile time, it reads and parses the entire perldiag man page. That is a big document. On Perl 5.40, it is over 5,000 lines of diagnostic descriptions. Parsing that adds noticeable startup time.

# benchmark: startup time with and without diagnostics # (rough numbers, vary by system) # Without: # $ time perl -e 'use strict; use warnings; 1' # real 0m0.011s # With: # $ time perl -e 'use strict; use warnings; use diagnostics; 1' # real 0m0.089s
About 80ms of extra startup on a modern machine. Trivial for a one-off debugging session. Not trivial if you are running the script 10,000 times in a loop, or if it is a CGI script that starts fresh on every request.

The runtime overhead per warning is also nonzero. Every warning triggers a hash lookup and string concatenation. If your code emits thousands of warnings (maybe you should fix those), the diagnostics handler will slow things down.

Part 6: DEVELOPMENT ONLY

The diagnostics pragma is a development tool. Not a production tool. Put it in when debugging, take it out before deploying.

A clean pattern:

#!/usr/bin/env perl use strict; use warnings; # Uncomment for verbose error explanations during development # use diagnostics; use feature 'say';
Or use an environment variable to toggle it:
#!/usr/bin/env perl use strict; use warnings; BEGIN { if ($ENV{PERL_DIAGNOSTICS}) { require diagnostics; diagnostics->import(); } }
Now you can flip verbose errors on and off without editing the script:
$ PERL_DIAGNOSTICS=1 perl my_script.pl # verbose $ perl my_script.pl # normal
This is especially useful for shared codebases. Everyone gets terse messages by default. When someone hits a confusing error, they set the env var and get the full story.

Part 7: COMBINING WITH WARNINGS

The diagnostics pragma plays nicely with warnings categories. You can enable specific warning categories and get verbose output for just those:
use warnings; use diagnostics; no warnings 'uninitialized'; # suppress these even with diagnostics
Warnings you have explicitly disabled stay disabled. Diagnostics only enhances warnings that are active. So you can silence the noisy ones you already understand and get verbose help on the ones that surprise you.

You can also use the -W flag for maximum coverage:

$ perl -W -Mdiagnostics script.pl
The -W flag enables all warnings regardless of what the script says. Combined with diagnostics, this is the nuclear option for debugging someone else's code. Every possible warning, fully explained.

Part 8: THE PERLDIAG DOCUMENTATION

Everything diagnostics knows comes from perldoc perldiag. You can read the whole thing yourself:
$ perldoc perldiag
It is organized alphabetically by message text. Each entry includes:
- The message pattern with placeholders - The severity category (W = warning, F = fatal, S = severe) - The specific warning category (syntax, numeric, etc.) - A paragraph or more of explanation - Sometimes example code showing the fix
If you are the kind of person who reads man pages for fun (and you are reading perl.gg, so you probably are), perldiag is one of the best. It is basically a catalog of every mistake you can make in Perl, with explanations written by the people who implemented the error checking.

The diagnostics module just makes perldiag available at runtime, automatically, at the exact moment you need it.

Part 9: PRACTICAL DEBUGGING WORKFLOW

Here is how to use diagnostics effectively in a real debugging session. You have a script. It is misbehaving. You are not sure why.

Step 1: Add diagnostics and run:

#!/usr/bin/env perl use strict; use warnings; use diagnostics; use feature 'say'; # ... your buggy code ...
Step 2: Read the verbose output. The explanation usually tells you not just what is wrong, but what to do about it.

Step 3: Fix the issue. Remove diagnostics. Run again.

#!/usr/bin/env perl use strict; use warnings; use feature 'say'; # ... your fixed code ...
For one-liners, skip the file editing entirely:
$ perl -Mdiagnostics -e 'my @x; print $x[0] + 1'
The -Mdiagnostics flag loads the module from the command line. Perfect for quick experiments where you want to understand what Perl is complaining about.

Part 10: WHY IT MATTERS

Perl has a reputation for cryptic error messages. The diagnostics pragma demolishes that reputation. Every cryptic message has a paragraph of explanation sitting right there in the distribution, waiting to be read.

The information was always there. In perldiag. In the man pages. In perldoc. The diagnostics pragma just brings it to you at the moment you need it, without making you go look it up.

.--. |o_o | "I know what the error means. |:_/ | I just wanted Perl to say it // \ \ out loud." (| | ) /'\_ _/`\ \___)=(___/
Most programmers learn about use strict and use warnings in their first week of Perl. Most never learn about use diagnostics. That is a shame, because diagnostics is the one that actually teaches you things. Strict and warnings tell you something is wrong. Diagnostics tells you why, and what to do about it.

It is a development tool, not a production tool. It has startup overhead. It makes your error output verbose. But when you are staring at a message you do not understand, those are features, not drawbacks.

Add it when confused. Remove it when enlightened. Repeat for 30 years of Perl programming.

perl.gg