Sound Types
MetaScript has a sound type system that catches errors at compile-time, not runtime. Unlike TypeScript's "gradual" types, MetaScript types are guaranteed.
Type Soundness
In TypeScript, types can lie:
// TypeScript - this compiles but crashes at runtime
const user: User = JSON.parse(response) // No guarantee it's a User!In MetaScript, types are verified:
// MetaScript type-safe parsing
const { ok, value: user } = JSON.parse<User>(response); // return Result<User, ParseError>
// Or shorthand for TypeScript-compatible, yet still safe parsing with try
const user = try JSON.parse<User>(response);
// Or flexible/un-typed parse that behaves very much like JavaScript
const data = JSON.parse(response); // return flexible JsonValue
console.log(data.name);
console.log(data.nested.level.name); // return null if nested fields don't exist, no exception
const name: string = data.nest.level.name;
const inferredStringType = data.nest.level.name.asString("default value"); // or .asString() mean default ""Primitive Types
MetaScript has familiar primitives with precise semantics:
| Type | Description | C Mapping |
|---|---|---|
number | IEEE 754 64-bit float | double |
int8|16|32|64 | Sized signed integers | int8_t, etc. |
uint8|16|32|64 | Sized unsigned integers | uint8_t, etc. |
float32|64 | Sized floats | float, double |
string | Mutable UTF-8 with COW | msString |
uint8[] | Byte array, zero-copy bridge with string | msUint8Array |
char | 8-bit character | char |
boolean | True or false | bool |
cstring | C-compatible string pointer | const char* |
void | No value | void |
never | Unreachable (bottom type) | - |
unknown | Type-safe any | void* |
Algebraic Data Types
Sum Types (Unions)
Tagged unions that the compiler tracks:
type Result<T, E> =
| { ok: true, value: T }
| { ok: false, error: E }
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return { ok: false, error: "Division by zero" }
}
return { ok: true, value: a / b }
}
// Compiler enforces handling both cases
const result = divide(10, 2)
if (result.ok) {
console.log(result.value) // Compiler knows this is number
} else {
console.error(result.error) // Compiler knows this is string
}Discriminated Unions (TypeScript superset)
Use match in type position for precise variant matching with enum discriminants:
enum NodeKind { NumLit, StrLit, BinExpr }
type NodeData = match (kind: NodeKind) {
NodeKind.NumLit => { value: number },
NodeKind.StrLit => { value: string },
NodeKind.BinExpr => { op: string, left: Node, right: Node },
};
// Construction is validated — wrong fields for a variant = compile error
const node: NodeData = { kind: NodeKind.NumLit, value: 42 };
// Field access after narrowing
match (node.kind) {
NodeKind.NumLit => console.log(node.value), // number
NodeKind.StrLit => console.log(node.value), // string
NodeKind.BinExpr => console.log(node.op), // string
}Structs (Value Types)
Stack-allocated value types — no heap, no refcounting:
struct Vec2 { x: float64; y: float64; }
const p: Vec2 = { x: 1.0, y: 2.0 }; // stack-allocated
// Compiler auto-selects optimal C ABI per parameter
function length(v: Vec2): float64 {
return Math.sqrt(v.x * v.x + v.y * v.y);
}Generics
Full parametric polymorphism:
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
}
const numbers = new Stack<number>()
numbers.push(42)
numbers.push("oops") // Compile error!Type Inference
MetaScript infers types where possible:
// Type inferred as number[]
const numbers = [1, 2, 3]
// Type inferred as (x: number) => number
const double = (x) => x * 2
// Return type inferred as string
function greet(name: string) {
return `Hello, ${name}!`
}Type Guards
Runtime checks that refine types:
function process(value: string | number) {
if (typeof value === "string") {
// Compiler knows value is string here
console.log(value.toUpperCase())
} else {
// Compiler knows value is number here
console.log(value.toFixed(2))
}
}Strict Null Safety
No more undefined is not a function:
function findUser(id: string): User | null {
return db.users.find(u => u.id === id)
}
const user = findUser("123")
// user.name // Compile error! Might be null
if (user) {
user.name // OK - compiler knows it's User
}
// Or use optional chaining
user?.name // Returns string | undefinedNext Steps
- Learn about the Memory Model for ownership and borrowing
- Explore Compile-Time Power for metaprogramming
- Check the Language Reference for full syntax