Catch a trap, Motoko

I know we’ve discussed this before, but I wanted to rais it again now that I’m using some different motoko structures.

Why can’t I catch a trap: https://m7sm4-2iaaa-aaaab-qabra-cai.raw.ic0.app/

actor Echo {

  // Say the given phase.
  public query func say() : async Text {

    type test = {
      #nat: Nat;
      #text : Text;
    };

    let the_value1 = #nat(6);
    let the_value2 = #text("7");

    let #text(phrase_1) = try{
      the_value1;
    } catch(e) {
      #text("not a text");
    };

    let #text(phrase_2) = try{
      the_value2;
    } catch(e) {
      #text("not a text");
    };

    return phrase_1 # "|" # phrase_2;
  };
};
       
Call failed:
Canister: wxani-naaaa-aaaab-qadgq-cai
Method: say (query)
"Status": "rejected"
"Code": "CanisterError"
"Message": "IC0503: Canister wxani-naaaa-aaaab-qadgq-cai trapped explicitly: pattern failed"

I know try and catch is supposed to be for async calls, but the above is an example that would be nice to do. Why can’t we try/catch regular functions? Is there something inherent to motoko that just can’t recover from these errors? Or is it a design choice? Is it a limitation of wasm?

2 Likes

Traps can indeed not be handled in Wasm. That’s a very intentional limitation of Wasm. Traps are not exceptions, they indicate program states that should never be reached, i.e. plain bugs in the program. When a program reaches such an inconsistent state, an attempt to recover from within the same program would then be dangerous, because it cannot guarantee consistent behaviour anymore – all bets are off. The only safe cause of action is to abort immediately and recover at a higher level.

Unfortunately, Wasm also does not have a real exception mechanism (yet). So for Motoko, there isn’t much choice to use anything else.

That all said, in your program, nothing would ever get caught anyway – the try-catch handlers do not enclose the faulty operation, which is the let-declaration with a non-exhaustive pattern match. You would have to write it as something like this:

let phrase_1 = try {
   let #text(phrase_1) = the_value;
   phrase_1
} catch (e) {
   "not a text"
}

Either way, this code is no better than simply writing a switch. Using try-catch to locally recover from a non-exhaustive pattern match is always gonna be more convoluted than using an exhaustive switch right away. So even if it worked, try-catch wouldn’t make sense for this use case.

6 Likes

Amazing explanation, thanks!