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 user = User.parse(response) // Returns Result<User, ParseError>Primitive Types
MetaScript has familiar primitives with precise semantics:
| Type | Description | Example |
|---|---|---|
number | 64-bit float (IEEE 754) | 42, 3.14 |
int | Platform-native integer | 42 |
i32, i64 | Sized integers | 42i32 |
u8, u32, u64 | Unsigned integers | 255u8 |
string | UTF-8 string | "hello" |
boolean | True or false | true |
void | No value | - |
never | Impossible type | - |
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
}Product Types (Records)
Classes with automatic structural equality:
@derive(Eq, Hash)
class Point {
x: number
y: number
}
const p1 = new Point(1, 2)
const p2 = new Point(1, 2)
console.log(p1.equals(p2)) // trueGenerics
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}!`
}Traits (Interfaces)
Define behavior contracts:
trait Serializable {
toJSON(): string
static fromJSON(json: string): Self
}
@derive(Serializable)
class User {
name: string
email: string
}
// Works with any Serializable
function save<T: Serializable>(item: T): void {
localStorage.setItem("data", item.toJSON())
}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