Type aliases
A type alias gives an existing type another name. Use one to shorten a long
type that you write often, such as HashMap<String, Array<int>>, or to say
what a value means, such as Score instead of int.
Declaring an alias
Write type, the new name, =, and the type it stands for:
# An alias is another name for an existing type.
type Score = int;
type Names = Array<String>;
type Handler = (int) => int;
struct Player {
var name: String;
var best: Score = 0; # aliases work in fields...
}
type Team = Player;
double_it(x: int): int {
return x * 2;
}
# ...and in parameters and return types.
apply(h: Handler, x: Score): Score {
return h(x);
}
main(): int {
var s: Score = 21;
var plain: int = s; # Score and int are the same type
assert(apply(double_it, plain) == 42, "function type alias");
var names = Names(); # construct through the alias
names.append("Ada");
assert(names.count == 1, "Names is Array<String>");
var p = Team();
p.best = s;
var q: Player = p; # Team and Player are the same type
assert(q.best == 21, "struct alias");
return 0;
}
An alias is only a spelling, not a new type. The compiler replaces every use
of the alias with its target before it checks anything else, so Score and
int are interchangeable: you can assign one to the other, pass one where the
other is expected, and error messages name the target type.
You can use an alias anywhere a type can appear: local and module-level
variables, parameters, return types, struct fields, generic arguments, and
casts. An alias of a struct also works where the struct’s name would: to
construct it (Team()) and to call its static methods.
Generic aliases
An alias can have type parameters of its own, including defaults, just like a generic struct:
# Generic aliases take their own type parameters.
type Predicate<T> = (T) => bool;
type Lookup<V> = HashMap<String, V>;
type Outcome<T, E=Error> = Result<T, E>;
is_even(x: int): bool {
return x % 2 == 0;
}
count_matching(values: Array<int>, test: Predicate<int>): int {
var n = 0;
for (var v in values) {
if (test(v)) {
n += 1;
}
}
return n;
}
parse_digit(text: String): Outcome<int> {
if (text == "7") {
return Outcome<int>.ok(7);
}
return Outcome<int>.err(Error("not a digit I know"));
}
main(): int {
var values = Array<int>();
values.append(1);
values.append(2);
values.append(4);
assert(count_matching(values, is_even) == 2, "Predicate<int> is (int) => bool");
var ages = Lookup<int>(); # HashMap<String, int>
ages.insert("Ada", 36);
assert(ages.contains("Ada"), "Lookup<int> is HashMap<String, int>");
# Outcome<int> fills E with its default: Result<int, Error>.
assert(parse_digit("7").value() == 7, "ok");
assert(parse_digit("x").is_err(), "err");
return 0;
}
The arguments at the use site fill the alias’s parameters in order, and
omitted ones take their defaults. Outcome<int> becomes
Result<int, Error>, and Lookup<int> becomes HashMap<String, int>.
Aliases are especially handy for function types, which
are long to write out: Predicate<int> reads better than (int) => bool.
Aliases across files
Like other top-level declarations, an alias is private to the file (or
module) that declares it. Mark it export to let files that import it use it; see
Visibility and Modules and imports.
Recursive aliases
An alias must not refer to itself, directly or through other aliases. The compiler reports the cycle at the alias, with a note at each alias it passes through:
type Celsius = Degrees;
type Degrees = Celsius;
main.pep:1:6: error: type alias 'Celsius' refers to itself
1 | type Celsius = Degrees;
| ^~~~~~~
main.pep:2:6: note: through type alias 'Degrees', declared here
2 | type Degrees = Celsius;
| ^~~~~~~