NAME

Clean::Eval - Run code under eval without leaking $@ and get a rich error object back on failure.

DESCRIPTION

Perl's built-in eval is the standard way to trap exceptions, but it has two long-standing ergonomic problems:

  • It modifies the global $@, which can be clobbered by destructors or other code running during stack unwind, leading to lost or corrupted error messages.

  • The return value of eval can be ambiguous: a successful eval that legitimately returns a false value is indistinguishable from a failure unless you check $@.

Clean::Eval wraps eval in a way that avoids both problems. It localizes $@ so the caller's copy is never touched, returns a true value on success, and returns an overloaded error object on failure. The error object stringifies to the trapped error and is always false in boolean context, so a single if check is enough to distinguish success from failure regardless of what the wrapped code returned.

Both a block form (clean_eval { ... }) and a string form (clean_string_eval $code) are provided. The string form rewrites #line information so that any error reports the file and line of the caller, not an anonymous (eval N).

SYNOPSIS

use Clean::Eval qw/clean_eval clean_string_eval last_error/;

# Block form
if (my $ret = clean_eval { do_something_risky() }) {
    # success path - $ret is a true value (1)
}
else {
    # failure path - $ret is the error object
    warn "Failed: $ret\n";          # stringifies to error message
    warn "  at $ret->{file} line $ret->{line}\n";
}

# String form (compiles at runtime). No need to add a trailing
# "; 1" - it is appended for you so the success path is unambiguous.
my $ret = clean_string_eval 'use SomeOptionalModule';
unless ($ret) {
    warn "Optional dep missing: $ret\n";
}

# Retrieve the most recent error from anywhere
my $last = last_error();

EXPORTS

Nothing is exported by default. The three functions below may be imported individually using Importer-style syntax:

use Clean::Eval qw/clean_eval clean_string_eval last_error/;
$ret = clean_eval { BLOCK }

Run BLOCK under eval. On success returns 1. On failure returns a Clean::Eval error object (see "ERROR OBJECT"). $@ in the caller's scope is not touched.

The prototype is (&), so the block form works without a leading sub.

$ret = clean_string_eval $STRING

Run $STRING as Perl code under eval. On success returns 1. On failure returns a Clean::Eval error object.

A #line directive is prepended to $STRING using the caller's filename and line number, so any error or warning produced by the eval'd code refers to the source location of the clean_string_eval call rather than to an anonymous eval string.

A trailing ; 1 is also appended to $STRING, so you do not need to remember the usual eval "...; 1" success guard - the return value is unambiguously 1 on success regardless of what the final statement in $STRING evaluates to. Including the ; 1 yourself is harmless.

The prototype is ($), so a single scalar argument is taken.

$err = last_error()

Return the most recent error object produced by clean_eval or clean_string_eval anywhere in the program, or undef if no error has been recorded yet. Useful for code paths that discarded the return value or want to inspect a previous failure after the fact.

Caveat: last_error is a global slot and is subject to the same class of bug that makes raw $@ fragile. If a DESTROY method (or anything else running during stack unwind) calls clean_eval or clean_string_eval, it will overwrite the global and the error you actually cared about will be lost. last_error is a convenience, not a guarantee - the only robust way to inspect a particular failure is to capture the return value of clean_eval/clean_string_eval directly at the call site and keep it in a lexical of your own.

ERROR OBJECT

On failure both clean_eval and clean_string_eval return a blessed hashref of class Clean::Eval. It overloads stringification and boolean context:

  • Boolean context: always false. This lets if (my $ret = clean_eval { ... }) work without ambiguity even when the block legitimately returns a false value.

  • String context: the trapped error (the value $@ had inside the eval).

The object is a plain hashref with the following keys:

error

The trapped error message (string or object).

package

The package the call was made from.

file

The file the call was made from.

line

The line the call was made from.

An additional to_string method is provided that returns the error message; it is equivalent to stringifying the object.

WHY NOT JUST USE eval?

You can, but you have to be careful. The idiomatic safe pattern looks like:

my $ok = eval { ...; 1 };
if (!$ok) {
    my $err = $@;
    ...
}

This is correct but verbose, and the ; 1 trailer is easy to forget. The $@ variable is also famously fragile: destructors that run during stack unwind can call eval themselves and reset it before you read it. Localizing $@ the way Clean::Eval does avoids that class of bug entirely.

SEE ALSO

Try::Tiny, Syntax::Keyword::Try, Feature::Compat::Try.

SOURCE

The source code repository for Clean-Eval can be found at https://github.com/exodist/Clean-Eval/.

MAINTAINERS

Chad Granum <exodist@cpan.org>

AUTHORS

Chad Granum <exodist@cpan.org>

COPYRIGHT

Copyright 2026 Chad Granum <exodist7@gmail.com>.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See http://dev.perl.org/licenses/