Products (Structures)
Structures bundle multiple values together into a single type. They're similar to structs in Rust/C or records in other functional languages.
Defining Structures
Use the structure keyword to define a new data type with named fields:
1structure Point where2 x : Float3 y : Float45-- Create instances6def origin : Point := { x := 0.0, y := 0.0 }7def p1 : Point := { x := 3.0, y := 4.0 }89-- Access fields with dot notation10#eval p1.x -- 3.011#eval p1.y -- 4.0The where keyword introduces the field list. Each field has a name and a type.
Anonymous Constructor Syntax
For concise construction, use angle brackets ⟨...⟩ (type\langle and \rangle, or just use ASCII<...>):
1structure Point where2 x : Float3 y : Float45-- Anonymous constructor (order matters!)6def p1 : Point := ⟨3.0, 4.0⟩78-- ASCII alternative9def p2 : Point := Point.mk 3.0 4.01011-- Both are equivalent to:12def p3 : Point := { x := 3.0, y := 4.0 }{ x := ..., y := ... } is clearer for structures with many fields.Default Field Values
Fields can have default values, making them optional during construction:
1structure Config where2 host : String := "localhost"3 port : Nat := 80804 debug : Bool := false56-- Use all defaults7def defaultConfig : Config := {}89-- Override some fields10def prodConfig : Config := { port := 443, debug := false }1112-- Override all13def customConfig : Config := { host := "api.example.com", port := 9000, debug := true }1415#eval defaultConfig.host -- "localhost"16#eval prodConfig.port -- 443Deriving Common Functionality
Use deriving to automatically generate useful instances:
1structure Person where2 name : String3 age : Nat4 deriving Repr, DecidableEq, Inhabited56def alice : Person := { name := "Alice", age := 30 }7def bob : Person := { name := "Bob", age := 25 }89-- Repr allows #eval to print the structure10#eval alice -- { name := "Alice", age := 30 }1112-- DecidableEq allows equality comparison13#eval alice == bob -- false14#eval alice == alice -- true1516-- Inhabited provides a default value17#eval (default : Person) -- { name := "", age := 0 }deriving Repr is essential for debugging—it makes your structures printable with #eval.Updating Structures
Structures are immutable, but you can create modified copies with "update" syntax:
1structure Person where2 name : String3 age : Nat4 deriving Repr56def alice : Person := { name := "Alice", age := 30 }78-- Create a new Person with updated age9def olderAlice : Person := { alice with age := 31 }1011-- Original is unchanged12#eval alice.age -- 3013#eval olderAlice.age -- 31Functions on Structures
Define functions that work with your structures:
1structure Point where2 x : Float3 y : Float4 deriving Repr56def Point.distance (p1 p2 : Point) : Float :=7 let dx := p2.x - p1.x8 let dy := p2.y - p1.y9 Float.sqrt (dx * dx + dy * dy)1011def origin : Point := ⟨0.0, 0.0⟩12def p : Point := ⟨3.0, 4.0⟩1314#eval Point.distance origin p -- 5.015#eval origin.distance p -- 5.0 (method syntax!)Notice the method syntax: origin.distance p. When a function is in the Point namespace and takes a Point as its first argument, you can call it with dot notation.
Nested Structures
Structures can contain other structures:
1structure Address where2 street : String3 city : String4 deriving Repr56structure Person where7 name : String8 address : Address9 deriving Repr1011def alice : Person := {12 name := "Alice"13 address := { street := "123 Main St", city := "Boston" }14}1516#eval alice.address.city -- "Boston"1718-- Nested update19def movedAlice : Person := {20 alice with21 address := { alice.address with city := "Cambridge" }22}Deep Dive: Structures vs Classes
Lean also has a class keyword, but it's for type classes (covered later), not OOP-style classes. Structures are the primary way to bundle data.
There is no inheritance in the OOP sense. Composition (nesting structures) is the preferred pattern.
Pattern Matching on Structures
You can destructure structures in function parameters:
1structure Point where2 x : Float3 y : Float45-- Destructure in the parameter list6def magnitude : Point → Float7 | ⟨x, y⟩ => Float.sqrt (x * x + y * y)89-- Or use a match expression10def magnitude' (p : Point) : Float :=11 match p with12 | ⟨x, y⟩ => Float.sqrt (x * x + y * y)1314-- Or just use field access15def magnitude'' (p : Point) : Float :=16 Float.sqrt (p.x * p.x + p.y * p.y)1718#eval magnitude ⟨3.0, 4.0⟩ -- 5.0