Type annotations
Types are optional. A program with no annotations behaves exactly as it always has, and an unannotated parameter accepts anything. What an annotation buys is a guarantee: it is checked where the value enters, so from then on the type is known rather than assumed.
Annotate a parameter, a variable, or a return type with ::
fn add(a: num, b: num): num {
return a + b
}
fn greet(name: str, times: num = 2): str {
let out: str = ""
for (let i = 0; i < times; i = i + 1) { out = out + name + "!" }
return out
}
Earlier releases wrote the return type as
fn add(a: num, b: num) -> num. That form still parses, so existing code keeps running, but:is the documented spelling: one way to say "this name has this type", everywhere.
The type names are num, str, bool, list, dict, buffer, nil, fn,
and any - plus any class name:
class Animal { fn init(n: str) { this.n = n } fn name(): str { return this.n } }
class Dog extends Animal { }
fn describe(a: Animal): str { return a.name() }
print(describe(new Dog("rex"))) # rex -- a Dog is an Animal
A class annotation accepts instances of that class and of anything deriving
from it, so describe above takes a Dog without complaint. any is the
same as writing nothing.
A violated annotation is a runtime error, reported where the bad value came from, with the usual stack trace:
fn f(a: num): num { return a }
f("hi")
# Runtime error: parameter 'a' of 'f' is declared num but got str
# at f() demo.bee:2
# at <main> demo.bee:2
Return types are checked on every path out, including falling off the end -
a function declared : num that never returns anything yields nil, and that
is an error:
Annotations may be mixed freely with unannotated code. A typed function can call an untyped one and vice versa; only what is declared is enforced.
See sized numeric types for fixed-width integer and
float annotations (i8…u64, f16/f32/f64).