Basic Concepts
This chapter introduces the core language model you will use across all Shape code.
1. Everything Is an Expression
Section titled “1. Everything Is an Expression”In Shape, control flow constructs return values.
let score = 84let grade = if score >= 90 { "A" } else if score >= 80 { "B" } else { "C" }print(grade)if, match, blocks, and function bodies are expression-oriented.
2. Bindings: let, let mut, var, and const
Section titled “2. Bindings: let, let mut, var, and const”Shape has three variable binding forms, each with different ownership behavior:
let name = "Alice" // immutable, uniquely owned — the defaultlet mut count = 0 // mutable, uniquely owned — for single-owner mutationcount = count + 1var shared = [1, 2, 3] // mutable, shared — copy-on-write when aliasedletis the default. Values are immutable and uniquely owned. Assignment moves the value.let mutis for mutation with a single owner. Mutations happen directly with zero overhead.varis for shared mutable state. Multiple references can exist, and mutations clone the data if needed (copy-on-write).
Use const for compile-time constants:
const MAX_RETRIES = 3// MAX_RETRIES = 4 // compile errorFor full details, see Variables and Types.
3. Records and Field Access
Section titled “3. Records and Field Access”Records are typed. Declare a record with type, then construct it with the type name. Fields are checked at compile time.
type Point { x: int, y: int,}
let mut point = Point { x: 10, y: 20 }point.y = 25
let total = point.x + point.yprint(total)Anonymous object literals ({ x: 10, y: 20 }) are read-only — they cannot grow new fields at runtime. Use a type declaration when you need field assignment or structural access.
4. Functions Use fn
Section titled “4. Functions Use fn”Use fn for named functions.
fn distance2(x: int, y: int) -> int { x * x + y * y}
let d2 = distance2(3, 4)print(d2)Closures (lambdas) are written with |params| body. Closures pick up their parameter types from the call site they are passed into:
let nums = [1, 2, 3, 4]
let evens = nums.filter(|x| x % 2 == 0) // x: int (inferred from nums)print(evens)A bare let f = |a, b| a + b without a call-site context cannot infer a and b — Shape requires types at compile time. Either pass the closure directly into a typed call, or wrap the logic in a named fn.
5. Pattern Matching
Section titled “5. Pattern Matching”match is an expression and supports type-based matching.
fn describe(value: int | string) -> string { match value { n: int => f"int({n})" s: string => f"string({s})" }}
print(describe(7))print(describe("hi"))6. Option/Result and Error Propagation
Section titled “6. Option/Result and Error Propagation”Shape uses Option<T> for absence and Result<T, E> for failure. Some / None / Ok / Err are available without an explicit import.
fn require_file(name: Option<string>) -> Result<string, string> { match name { Some(path) => Ok(path) None => Err("missing input filename") }}
let file = Some("events.csv")match require_file(file) { Ok(path) => print(f"path: {path}") Err(msg) => print(f"error: {msg}")}?propagatesOption/Resultinside functions whose return type matches!!adds error context to propagated failures
See Error Handling for the full operator semantics.
7. Imports and Modules
Section titled “7. Imports and Modules”Use named imports with from ... use { ... }:
from std::core::math use { sqrt }
fn radius(area: number) -> number { sqrt(area / 3.14159)}
print(radius(100.0))Bare names come from explicit named imports or the implicit prelude. See Names and Scope for the full target model.
8. Data Tables
Section titled “8. Data Tables”For structured row-oriented data, Shape uses Table<T> with a declared row schema:
type Event { id: int, value: number,}
// Tables are constructed via stdlib loaders (CSV, Arrow, JSON, ...).// See std::core::csv and std::core::table_methods for the full surface.Table operations (filter, map, groupBy, orderBy, head/tail) are covered in detail in Data Tables.
9. Semicolons
Section titled “9. Semicolons”Semicolons are usually optional.
- Use
;when placing multiple expressions on one line. - In a block, a trailing
;on the last expression turns it into()(unit) instead of returning that value.
fn a() { 1 } // returns 1fn b() { 1; } // returns ()Next Steps
Section titled “Next Steps”Continue with Variables and Types to learn binding rules, type annotations, and shape-aware object typing in detail.