[Grr, Forum giving me a 403 again when my reply was only half-done, and I can’t edit it anymore. Rest of reply follows.]
Having said all that, the type null
also is in fact useful. For example, it allows the use of variants as enums in the first place: in Candid, the type variant {a; b}
is in fact just a shorthand for variant {a : null; b : null}
. This way, Candid needs no special handling of such variant cases. (With that in mind, my above pseudo-definitions wouldn’t really work in practice, because they would be circular – you’d need another unit type.)
This role of unit types is a general scheme when using generic types. Candid does not have generics yet, but once it has you could imagine an interface to a map canister like (making up some Motoko-style syntax):
{
put : <K,V> (K, V) -> ();
get : <K,V> (K) -> (opt V);
has : <K> (K) -> (bool);
}
Now, assume you just need a set. Then you can use this canister with type V = null
, effectively ignoring the value part. Note that you could not use V = empty
, because then you’d never be able to call put
.
There are potential use cases without generics, e.g., when evolving some interfaces. For example, say a canister has a function
foo : (bar : func (nat, opt nat) -> (nat)) -> (nat)
During some upgrade, the canister evolves to not using or needing the optional parameter of the callback anymore. It cannot remove it, because that would break existing clients. But it can indicate it’s unused from now on by refining the type of the callback to:
foo : (bar : func (nat, null) -> (nat)) -> (nat)
This documents the intent while being backwards compatible with callers passing a callback that still allows an option. New clients o.t.o.h. can implement this callback with a null parameter as well, and thereby ignore it.
No, sorry, not at all. You can write a function with return type null
, because there is a value for it. You cannot write a function with return type empty
. Also see my map-as-set example above.