The MoltResult Type

July 9, 2019

Rust provides the standard Result<T,E> type for returning values from functions or methods that might need to include error info or the like. C has no such thing; and so the C implementation of Tcl has to jump through some hoops. I’m going to describe how Standard Tcl does it, and then how I’ve implemented the same pattern in Rust.

How Tcl’s C API Handles Results

In standard Tcl’s C API, functions that might affect the flow of control return a result code: an integer that indicates what happened. The set of C functions cooperate to make the flow of control indicated by the user’s code actually happen.

There are five standard return codes:

  • TCL_OK
  • TCL_ERROR
  • TCL_BREAK
  • TCL_CONTINUE
  • TCL_RETURN

The code TCL_OK indicates that the called function executed normally, and the caller should continue executing normally.

The code TCL_ERROR indicates that the function detected an error. The caller should (unless it wishes to catch the error) just return TCL_ERROR itself, propagating the error upward.

The codes TCL_BREAK and TCL_CONTINUE are used by Tcl’s loop control structures. They are produced by the Tcl break and continue commands, which as you might expect are used to break loop execution or jump to the beginning of the next loop iteration. The caller should propagate them upward—unless it’s the function implementing the loop, in which case it should catch and handle them.

The code TCL_RETURN is similar; it’s produced by Tcl’s return command, which is used to return from Tcl procedures. As with TCL_BREAK and TCL_CONTINUE, it propagates upward until it is handled by the C function that executes Tcl procedures.

Thus, you see a lot of C code that looks like this;

int result_code = Tcl_SomeFunction(interp, /* some args*/);

if (result_code != TCL_OK) {
    return result_code;
}

or

char* value = Tcl_FindSomethingOrOther(interp, /* some args */);

if (value == null) {
    return TCL_ERROR;
}

But what about the Tcl procedure’s computed return value? In the TCL_ERROR case, just shown, what about the error information?

The codes TCL_OK and TCL_RETURN always include a computed value (which may be the empty string); and TCL_ERROR always includes an error message and some other data. These things are given to the Tcl_Interp object, represented above by the interp argument, as the interpreter’s result. The Tcl_Interp class has a detailed API for setting, manipulating, and retrieving the result.

Rustifying Tcl Results

The above pattern should look pretty familiar. It’s basically the Result<T,E> pattern. The integer result codes take the place of Ok and Err, and the additional data passed via the Tcl interp take the place of the Ok and Err tuple data. Oh, and a whole lot of if statements stand in for Rust’s ? operator.

Molt does it like this:

pub type MoltResult = Result<Value, ResultCode>;

#[derive(Eq, PartialEq, Clone, Debug)]
pub enum ResultCode {
    Error(Value),
    Return(Value),
    Break,
    Continue,
}

The Value type is Molt’s standard representation for a data value, i.e., something that can be put in a Molt variable. I plan to write a post about Value, but for now if you think of it as an immutable string you won’t go far wrong.

A method or function in Molt’s Rust API that computes a Molt Value returns MoltResult. Ok(Value) handles the normal case, and corresponds to TCL_OK. The four ResultCode case correspond to the other Standard Tcl return codes, but don’t require the interpreter’s help in passing back values. Instead of TCL_ERROR we have Err(ResultCode::Error(Value)); the Value is the error message. Instead of TCL_RETURN we have Err(ResultCode::Return(Value)), where the Value is the returned result.

In cases where we are computing something other than a Value, say an i64, we use that directly, and return Result<i64,ResultCode>.

Consequently, we end up writing code like this:

let value: Value = interp.eval("some Molt code")?;

let index:i64 = molt_find_something(/* some args*)?;
...

Note that the Molt interpreter need not be involved!

Tcl Liens

There are still a few things that the Standard Tcl implementation can do that Molt’s cannot.

At present, on error Molt only returns an error message. Tcl also saves an error code and accumulates a stack trace as the stack unwinds. The ResultCode::Error case needs to be updated to handle that.

Also, Tcl extensions written in C do not need to confine themselves to the five canonical result codes. An extension that implements application-specific control structures, for example, could use additional integer codes for own its own use. That’s more difficult with Molt’s architecture; I’d need to add an additional case to the ResultCode enum to support it.