Skip to content

Latest commit

 

History

History
303 lines (229 loc) · 6.21 KB

File metadata and controls

303 lines (229 loc) · 6.21 KB

Data Types and Pattern Matching

Kōdo lets you define custom data types using struct and enum. Pattern matching with match provides a safe way to destructure values and handle every case.

Structs

A struct groups related values under named fields:

struct Point {
    x: Int,
    y: Int
}

Creating Structs

Create a struct value by providing all fields:

let origin: Point = Point { x: 0, y: 0 }
let p: Point = Point { x: 3, y: 4 }

Accessing Fields

Use dot notation to access individual fields:

fn get_x(p: Point) -> Int {
    return p.x
}

fn main() {
    let p: Point = Point { x: 10, y: 20 }
    print_int(p.x)
    print_int(p.y)
}

Structs as Function Parameters

Structs can be passed to and returned from functions:

struct Point {
    x: Int,
    y: Int
}

fn translate(p: Point, dx: Int, dy: Int) -> Point {
    return Point { x: p.x + dx, y: p.y + dy }
}

fn main() {
    let p: Point = Point { x: 1, y: 2 }
    let q: Point = translate(p, 10, 20)
    print_int(q.x)
    print_int(q.y)
}

Output:

11
22

Internally, structs are passed by pointer — there is no copy overhead for large structs.

Enums

An enum defines a type that can be one of several named variants. Each variant can optionally carry data:

enum Color {
    Red,
    Green,
    Blue
}

enum Shape {
    Circle(Int),
    Rectangle(Int, Int)
}

Color has three variants with no data. Shape has two variants: Circle carries a radius (Int) and Rectangle carries width and height (Int, Int).

Creating Enum Values

Use the EnumName::Variant(args) syntax:

let s: Shape = Shape::Circle(5)
let r: Shape = Shape::Rectangle(10, 20)

Pattern Matching with match

match is the primary way to work with enums. It destructures the value and binds contained data to variables:

fn area(s: Shape) -> Int {
    match s {
        Shape::Circle(r) => {
            return r * r * 3
        }
        Shape::Rectangle(w, h) => {
            return w * h
        }
    }
}

Each arm of a match specifies a pattern (Shape::Circle(r)) and a body to execute when the value matches that pattern. The contained values are bound to the names you choose (r, w, h).

Match is Exhaustive

Every match must cover all variants. If you forget one, the compiler will tell you. This prevents a common category of bugs where a new variant is added but not handled everywhere.

Enums as Function Parameters

Like structs, enums can be passed to and returned from functions:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

fn move_point(p: Point, dir: Direction, amount: Int) -> Point {
    match dir {
        Direction::Up => {
            return Point { x: p.x, y: p.y + amount }
        }
        Direction::Down => {
            return Point { x: p.x, y: p.y - amount }
        }
        Direction::Left => {
            return Point { x: p.x - amount, y: p.y }
        }
        Direction::Right => {
            return Point { x: p.x + amount, y: p.y }
        }
    }
}

Complete Example

Here's a program that combines structs, enums, and pattern matching:

module geometry {
    meta {
        purpose: "Calculate areas of shapes"
        version: "0.1.0"
    }

    struct Point {
        x: Int,
        y: Int
    }

    enum Shape {
        Circle(Int),
        Square(Int),
        Rectangle(Int, Int)
    }

    fn area(s: Shape) -> Int {
        match s {
            Shape::Circle(r) => {
                return r * r * 3
            }
            Shape::Square(side) => {
                return side * side
            }
            Shape::Rectangle(w, h) => {
                return w * h
            }
        }
    }

    fn main() {
        let c: Shape = Shape::Circle(5)
        let sq: Shape = Shape::Square(4)
        let r: Shape = Shape::Rectangle(3, 7)

        print_int(area(c))
        print_int(area(sq))
        print_int(area(r))
    }
}

Output:

75
16
21

Tuples

A tuple groups a fixed number of values of possibly different types. Unlike structs, tuple fields are accessed by position rather than by name.

Tuple Literals

Create tuples with parentheses:

let pair: (Int, Int) = (42, 99)
let triple: (Int, String, Bool) = (1, "hello", true)

Accessing Elements

Use .0, .1, .2 etc. to access elements by index:

let pair: (Int, Int) = (10, 20)
let first: Int = pair.0
let second: Int = pair.1

Destructuring

Bind all elements at once with destructuring:

let triple: (Int, Int, Int) = (1, 2, 3)
let (a, b, c) = triple
// a = 1, b = 2, c = 3

Returning Tuples from Functions

Tuples are useful when a function needs to return multiple values:

fn min_max(a: Int, b: Int) -> (Int, Int) {
    if a < b {
        return (a, b)
    }
    return (b, a)
}

fn main() {
    let result: (Int, Int) = min_max(7, 3)
    print_int(result.0)  // 3
    print_int(result.1)  // 7
}

Float64

Kōdo supports 64-bit floating-point numbers with full arithmetic:

let pi: Float64 = 3.14159
let radius: Float64 = 5.0
let area: Float64 = pi * radius * radius

println_float(area)

All arithmetic (+, -, *, /, %), comparison (==, !=, <, >, <=, >=), and negation (-x) operators work with Float64 values.

String Operators

Strings support concatenation with the + operator:

let greeting: String = "Hello, " + "world!"
println(greeting)

let name: String = "Kōdo"
let msg: String = "Welcome to " + name
println(msg)

Strings also support equality comparison with == and !=, which compares by content (not by pointer):

let a: String = "hello"
let b: String = "hello"
if a == b {
    println("strings are equal")
}

See Modules and Imports for the full list of string methods (length, contains, split, trim, concat, index_of, replace, etc.).

Next Steps

  • Closures — closures, lambda lifting, and higher-order functions
  • Generics — parameterize your types with type variables
  • Error Handling — use Option<T> and Result<T, E> for safe error handling
  • Contracts — add runtime verification to your functions