What is Motoko's ?() type exactly?

If I have an actor method in Motoko that returns async ?(), how would I interpret that type in JavaScript/TypeScript? (The method returns async ?() to represent that either a) the method completed the operation successfully, i.e. ?(), or b) the method failed to complete, i.e. null. Maybe I should use Motoko’s Result interface to make that clearer?)

It seems like these are equivalent:

?() in Motoko
opt null in Candid
[] | [null] in JS/TS

I don’t understand why ?() in Motoko becomes opt null in Candid, but () in Motoko becomes () in Candid. I’m guessing that [null] in JS/TS means success, whereas [] in JS/TS means failure?

1 Like

I don’t understand why ?() in Motoko becomes opt null in Candid, but () in Motoko becomes () in Candid

That seems inconsistent/non-compositional to me too, @rossberg @nomeata @chenyan do we have a bug here or is this intentional? I haven’t checked the translations with the compiler.

The type () becomes null, as specified here: https://github.com/dfinity/motoko/blob/master/design/IDL-Motoko.md

This is needed so that the idiomatic way to express enums as variants work out (if you omit the type from a variant tag you get () in Motoko, but null in Candid.)

I assume that your () is not the unit type but rather the empty argument sequence, and that indeed is mapped to the empty argument sequence in Candid. Note that Candid functions don’t take a single type, but always a sequence of types.

1 Like

Oh interesting… Just to confirm then, in JS land [] represents the optional null, whereas [null] represents the Motoko type ()? That’s quite confusing and should probably be documented here.

Actually, the () in ?() is indeed the Motoko unit type. I’m using it as the return type of an actor method, i.e. async ?().

1 Like

Is it not documented? Oh, indeed, the section on Candid’s null type should say that it corresponds to both Null and () in Motoko.


Hmm, I shouldn’t write technical stuff on the phone. What I wrote above wasn’t the full story (according to https://github.com/dfinity/motoko/blob/master/design/IDL-Motoko.md). Indeed, when exporting from Motoko to Candid, both () and Null maps to null. But when importing Candid into Motoko, null maps to Null unless it is the type of a variant field, in which case it maps to ().


Actually, these are equivalent apparently and generated by dfx build:


actor {
  public func foo(assetId: Text): async () {...};


service: {
  foo: (Text) -> ();


export default interface _SERVICE {
  'foo': (arg_0: string) => Promise<undefined>,

That seems to indicate that () in Motoko doesn’t always get mapped to null in Candid. It apparently also gets mapped to the empty return type (I have no idea what type that is here), which then gets mapped to undefined in JS.