New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error handling (tryEval?) #356

Open
jameshfisher opened this Issue Sep 27, 2014 · 9 comments

Comments

Projects
None yet
5 participants
@jameshfisher

jameshfisher commented Sep 27, 2014

What documentation is there for error handling in the Nix expression language? I think that evaluation can abort with an error message for many different reasons. I also see a concept of throwing an error message, and this appears to be a separate concept to abort. I also see brief mentions of a builtin called tryEval, but its behavior is not documented anywhere.

Is the error/exception/throw/abort/tryEval semantics defined more precisely anywhere?

@jameshfisher

This comment has been minimized.

jameshfisher commented Sep 27, 2014

The distinction between uncatchable errors and catchable errors ("exceptions"?) is not very clear. I would like to know roughly what kinds of thing evaluation can yield; at the moment I have the idea that evaluation of an expression either yields:

  • a value, e.g. 2 + 3 evaluates to 5, or
  • an uncatchable error, like abort "foo" evaluates to an uncatchable error with message foo
  • an exception, like throw "foo" evaluates to an exception with message foo

The distinction is important for tryEval, which appears to only catch some forms of error:

> nix-instantiate --eval --expr 'builtins.tryEval (abort "foo")'
error: evaluation aborted with the following error message: `foo'
> nix-instantiate --eval --expr 'builtins.tryEval (throw "foo")'
{ success = false; value = false; }
@jameshfisher

This comment has been minimized.

jameshfisher commented Sep 27, 2014

Is it the case that, in the set that builtins.tryEval evaluates to, if the success key is false, then the value key is always false too?

@jameshfisher

This comment has been minimized.

jameshfisher commented Sep 27, 2014

At the moment, I want to give builtins.tryEval the following rough semantics:

  • if Expr evaluates to error Error(Msg), the expression builtins.tryEval Expr evaluates to error Error(Msg)
  • if Expr evaluates to exception Exception(Msg), the expression builtins.tryEval Expr evaluates to value { success = false; value = false; }
  • if Expr evaluates to value Val, the expression builtins.tryEval Expr evaluates to value { success = true; value = Val; }

Does this sound accurate?

@jameshfisher

This comment has been minimized.

jameshfisher commented Sep 27, 2014

The documentation for assert says:

assert e1; e2

where e1 is an expression that should evaluate to a Boolean value. If it evaluates to true, e2 is returned; otherwise expression evaluation is aborted and a backtrace is printed.

IMO this implies that failed assertions generate an uncatchable error in my above terminology, as with the abort expression. But experimentation shows that assertions actually generate a catchable exception:

> nix-instantiate --eval --expr 'builtins.tryEval (assert false; "foo")'
{ success = false; value = false; }
@jameshfisher

This comment has been minimized.

jameshfisher commented Sep 27, 2014

So I looked through the source; my C++ is very rusty but based on the Error class hierarchy and this definition for tryEval:

/* Try evaluating the argument. Success => {success=true; value=something;},
 * else => {success=false; value=false;} */
static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
    state.mkAttrs(v, 2);
    try {
        state.forceValue(*args[0]);
        v.attrs->push_back(Attr(state.sValue, args[0]));
        mkBool(*state.allocAttr(v, state.symbols.create("success")), true);
    } catch (AssertionError & e) {
        mkBool(*state.allocAttr(v, state.sValue), false);
        mkBool(*state.allocAttr(v, state.symbols.create("success")), false);
    }
    v.attrs->sort();
}

I generated this class hierarchy diagram, where the pink error kinds are the catchable ones:

errors

In other words, the only errors that can be caught with tryEval are those that are generated by assert and throw.

I would suggest that the documentation be changed to call these exceptions: we can throw exceptions and catch exceptions, and exceptions are also thrown by assertion failures. Everything else is a fatal error.

@shlevy

This comment has been minimized.

Member

shlevy commented Sep 27, 2014

Your interpretation of the tryEval code is correct. These are the errors that can be generated by end-user code (Abort can also, but having the result of abort be catchable seems dubious). In particular, I believe they are used in evaluating the nixpkgs jobset per-system: various packages have assert/throws in order to limit the package to only being built on the systems they support, and the evaluation simply skips those using in-language constructs rather than having to handle errors in C++.

👍 for documentation.

@jameshfisher

This comment has been minimized.

jameshfisher commented Sep 28, 2014

Great, thanks Shea. I mainly ask because I'm writing what's turned out to be a rather epic blog post on the Nix expression language. Hopefully I can get someone to fact-check it when I'm done soon. :-)

On an unrelated note, it would be nice if the thrown message wasn't "thrown away" when caught; i.e. if the semantics were instead:

  • ...
  • if Expr evaluates to exception Exception(Msg), the expression builtins.tryEval Expr evaluates to value { success = false; message = Msg; }
  • ...
@sjmackenzie

This comment has been minimized.

sjmackenzie commented Feb 15, 2016

@jameshfisher great work on this thread, very considerate of you!

@jb55

This comment has been minimized.

jb55 commented Apr 18, 2017

(triage) some good stuff in here, what's the status of this issue? can it be closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment