Variables and Types
Declare a type. Echo checks it at runtime when the value is bound or reassigned.
name: str = "Echo";
count: int = 1;
price: float = 2.5;
ok: bool = true;
value: dynamic = null;
const title: str = "Echo";
[a: int, b: int] = [1, 2];
{ id: int, name: str } = { id: 1, name: title };
{ id as userId: int } = { id: 1 };
{ id: int, rest: dynamic... } = { id: 1, extra: true };title: str = "Echo";
users: int = 42;
ratio: float = 1.5;
enabled: bool = true;
missing: dynamic = null;
say(title, users, ratio, enabled, missing);Echo 42 1.5 true nullNotes
Declaration
Use this form for a new variable:
name: Type = expression;Reassignment
Use this form after declaration:
name = expression;const name: T = expression; cannot be reassigned. The bound list or hash is frozen.
Destructuring unpacks a list or hash into names:
[a: int, b: int] = pair;
[a, b] = pair;
{ id: int, name: str } = user;
[head: int, rest: int...] = xs;Built-in types
int: whole numbersfloat: decimal numbersstr: textbool:trueorfalsedynamic: any runtime valuelist: mutable ordered collectionhash: mutable key-value mapvoid: onlynull(used when a binding may be absent)null: literal value for missing data (typed asdynamicunless the binding isvoidordynamic)
Union types
type Id = int | str;
id: Id = 1;
id = "x";
fn show(x: int | str) -> str {
return asString(x);
}A value matches a union if it matches any member. Unions are not Option — Echo has no null type, so write str | void when null is allowed. Use switch type arms to dispatch on members.
Classes (nominal)
class Point {
new {
x: int;
y: int;
}
}
p: Point = Point { x: 3, y: 4 };
say(p.x);
p.x = 10;
say(type(p)); // "Point"Point is a nominal type — not the same as exact { x: int, y: int }. Fields are declared under new { ... } (optional defaults: x: int = 0). Construction requires every field without a default and rejects extras. Methods use an explicit this receiver: fn length(this) -> int { ... }, call p.length() or unbound Point.length(p); type methods omit this (fn origin() -> Point, call Point.origin()). Interfaces declare method signatures only (interface Named { fn name(this) -> str; }); a class implements them by providing compatible methods; an optional implements Named clause documents intent and is checked (E3214).
Runtime type checking
count: int = 1;
count = 2; // valid
count = "two"; // runtime type errorCommon Mistakes
Assigning before declaration
count = 1;Treating null as a type
x: null = null;Use dynamic instead.
Using void as a variable type
This is not allowed.
Current Limitation
- No generics such as
list<int>. - Lists are not element-typed.
- Hashes are only structurally typed when used through object type aliases.
