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 -- 31Accessor Functions
You can create small helper functions to keep structure access clean and reusable.
1structure User where2 name : String3 score : Nat4 deriving Repr56def isTopUser (u : User) : Bool := u.score >= 1007def displayName (u : User) : String := s!"@{u.name}"89#eval isTopUser { name := "alice", score := 120 } -- true10#eval displayName { name := "bob", score := 10 }Functions 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.
Create a function that increments a person's age and returns the updated record.
1structure Person where2 name : String3 age : Nat45def birthday (p : Person) : Person :=6 { p with age := p.age + 1 }78#eval birthday { name := "Eve", age := 29 }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