# 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](https://metacpan.org/pod/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"](#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](https://metacpan.org/pod/Try%3A%3ATiny), [Syntax::Keyword::Try](https://metacpan.org/pod/Syntax%3A%3AKeyword%3A%3ATry), [Feature::Compat::Try](https://metacpan.org/pod/Feature%3A%3ACompat%3A%3ATry).

# 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/`
