Skip to content

Data Types

Language: Español | English


Crespi is dynamically typed at runtime, with an optional static type checker that uses type inference. Types are only enforced when you opt into the checker (for example with --check).

Type Annotations

Crespi supports type annotations with Rust-style syntax. When the optional checker runs, types are inferred from context.

Variable Type Annotations

// With explicit type
var name: String = "Alice"
var age: Int = 25
var active: Bool = true

// Type inference (when running the checker)
var count = 42           // Inferred: Int
var pi = 3.14            // Inferred: Float
var list = [1, 2, 3]     // Inferred: List[Int]

Mixed-type list literals are inferred as `List[Any]`.

With the checker enabled, a variable's inferred or annotated type must remain consistent. Without it, reassignment can use any type at runtime.

Function Type Annotations

// Fully typed function
fn add(a: Int, b: Int) -> Int {
    return a + b
}

// With default values
fn greet(name: String = "World") -> String {
    return "Hello, " + name
}

// Generic function with constraints
fn [T: Numeric] max(a: T, b: T) -> T {
    if a > b { return a }
    return b
}

Nullable Types

// Nullable type annotation with ?
var result: String? = null

// Null coalescing operator
var value = result ?? "default"

Union Types

// Union type annotation with |
fn parse(input: String) -> Int | Error {
    // Can return either Int or Error
}

Class Type Annotations

class Point(let x: Int, let y: Int) {
    fn distance() -> Float {
        return sqrt(float(this.x * this.x + this.y * this.y))
    }
}

Generic Types

class Container[T](let value: T) {
    fn get() -> T {
        return this.value
    }
}

Type Table

Static Types (for annotations)

Type Description Example
Int 64-bit signed integer (default) 42
Int32, Int16, Int8 Specific width signed integers 100: Int32
UInt, UInt32, UInt16, UInt8 Unsigned integers 50: UInt
Double 64-bit floating-point (default) 3.14
Float 32-bit floating-point 3.14: Float
String UTF-8 string "Hello"
Bool True or false value true
Null Absence of value null
Unit No value (like void) fn f() -> Unit
Any Top type (accepts anything) var x: Any
Never Bottom type (no values) fn fail() -> Never
List[T] Dynamic array of type T [1, 2]: List[Int]
(T1, T2) Fixed-size tuple (1, "a"): (Int, String)
Dict[K, V] Text-keyed map {"a": 1}: Dict[String, Int]

Runtime Type Names (from typeof())

Name Values
"int" All signed/unsigned integers
"float" Double and Float
"string" String
"bool" Bool
"null" Null
"list" List[T]
"tuple" (T1, T2)
"dict" Dict[K, V]
"function" Functions and lambdas
"instance" Class instances

Numeric Types

Crespi uses explicit-width numeric types and follows Swift's naming convention for floating-point numbers.

Integers

Type Width Range
Int 64-bit -9.22e18 to 9.22e18
Int32 32-bit -2.14e9 to 2.14e9
Int16 16-bit -32,768 to 32,767
Int8 8-bit -128 to 127
UInt 64-bit 0 to 1.84e19
UInt32 32-bit 0 to 4.29e9
UInt16 16-bit 0 to 65,535
UInt8 8-bit 0 to 255

Floating-Point

Type Width Description
Double 64-bit Default float type (IEEE 754)
Float 32-bit Single precision float

Strict Typing

Crespi does not perform implicit numeric coercion. You must use explicit conversion functions or methods if you want to assign an Int32 to an Int, or an Int to a Double.

var i: Int = 42
var d: Double = i.toDouble() // OK
// var d2: Double = i        // Error: Type mismatch

Primitive Types

int

Signed 64-bit integers. Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

var age = 25
var negative = -100
var zero = 0
var large = 9223372036854775807

print(typeof(age))  // int

Operations: - Arithmetic: +, -, *, /, % - Comparison: <, >, <=, >=, ==, !=

float

64-bit floating-point numbers (IEEE 754 double precision).

var pi = 3.14159
var temperature = -5.5
var scientific = 1.5e10  // Scientific notation: 1.5 × 10¹⁰

print(typeof(pi))  // float

Note on division:

print(10 / 3)     // 3 (integer division)
print(10.0 / 3)   // 3.333... (float division)
print(10 / 3.0)   // 3.333...

string

Immutable character strings encoded in UTF-8.

var greeting = "Hello, World"
var multiline = "Line 1
Line 2"
var with_escape = "Says: \"Hello\""
var with_tabs = "Col1\tCol2"

print(typeof(greeting))   // string
print(greeting.length())   // 12

Escape sequences: | Sequence | Meaning | |----------|---------| | \\ | Backslash | | \" | Double quote | | \n | Newline | | \t | Tab | | \r | Carriage return | | \$ | Literal dollar sign |

String interpolation:

var name = "Ana"
var total = 3
print("Hello, $name")            // Hello, Ana
print("Total: ${total + 1}")     // Total: 4

Raw (triple-quoted) strings:

var multiline = """Line 1
Line 2"""

var price = 5
print("""Cost: $$${price}""")   // Cost: $5

Operations:

// Concatenation
var full = "Hello" + " " + "World"

// Character access (iteration)
for letter in "ABC" {
    print(letter)  // A, B, C
}

// Search
print("Hello World".contains("World"))  // true

bool

Logical values: true or false.

var active = true
var finished = false

print(typeof(active))  // bool

Truthiness (conversion to boolean):

Type Falsy Value Truthy Value
bool false true
int 0 Any other
float 0.0 Any other
string "" (empty) Non-empty
list [] (empty) Non-empty
tuple (no empty literal) Always truthy
null Always -
// In conditions
if 1 { print("true") }      // Executes
if 0 { print("not seen") }  // Does not execute
if "hello" { print("yes") } // Executes
if "" { print("no") }       // Does not execute

null

Represents the absence of a value. Similar to null or nil in other languages.

var result = null

fn find(list, target) {
    for item in list {
        if item == target {
            return item
        }
    }
    return null
}

var found = find([1, 2, 3], 5)
if found == null {
    print("Not found")
}

Collection Types

list

Dynamic array that can contain elements of any type.

// Creation
var empty = []
var numbers = [1, 2, 3, 4, 5]
var mixed = [1, "two", true, null, [1, 2]]

print(typeof(numbers))  // list

Element access:

var list = [10, 20, 30, 40, 50]

// Positive indices (from start)
print(list[0])   // 10 (first element)
print(list[2])   // 30

// Negative indices (from end)
print(list[-1])  // 50 (last element)
print(list[-2])  // 40

tuple

Fixed-size ordered collection. Tuples use parentheses with commas, and a single-element tuple requires a trailing comma.

var point = (3, 4)
var single = (1,)

print(typeof(point))    // tuple
print(point.length())    // 2
print(point[0])         // 3
print(point[-1])        // 4

Modification:

var list = [1, 2, 3]

// Modify element
list[0] = 100
print(list)  // [100, 2, 3]

// Append to end
list.push(4)
print(list)  // [100, 2, 3, 4]

// Remove from end
var last = list.pop()
print(last)  // 4
print(list)  // [100, 2, 3]

Iteration:

var colors = ["red", "green", "blue"]

for color in colors {
    print(color)
}

dict

Text-keyed map to values of any type.

// Creation
var empty = {}
var person = {
    "name": "Ana",
    "age": 25,
    "active": true
}

print(typeof(person))  // dict

Value access:

var dict = {"a": 1, "b": 2, "c": 3}

print(dict["a"])    // 1
print(dict["b"])    // 2

Modification:

var config = {"theme": "dark"}

// Modify existing value
config["theme"] = "light"

// Add new key
config["language"] = "en"

print(config)  // {theme: light, language: en}

Iteration:

var grades = {"math": 90, "physics": 85, "chemistry": 78}

// Iterate over keys
for subject in grades.keys() {
    print(subject + ": " + str(grades[subject]))
}

// Get values
var vals = grades.values()
print(vals)  // [90, 85, 78]


Callable Types

function

User-defined functions.

// Standard declaration
fn add(a, b) {
    return a + b
}

// Short syntax (single expression)
fn double(x) = x * 2

// With default values
fn greet(name = "World") {
    print("Hello, " + name)
}

print(typeof(add))  // function

Functions as values:

// Assign to variable
var operation = add
print(operation(3, 4))  // 7

// Pass as argument
fn apply(fn, value) {
    return fn(value)
}
print(apply(double, 5))  // 10

// Return function
fn create_multiplier(factor) {
    fn multiply(x) {
        return x * factor
    }
    return multiply
}
var triple = create_multiplier(3)
print(triple(4))  // 12

class

Definition of a type with constructor and methods.

class Counter(var value = 0) {
    fn increment() {
        this.value += 1
    }

    fn get() {
        return this.value
    }
}

print(typeof(Counter))  // class

instance

Object created from a class.

class Point(let x, let y) {
}

var p = Point(3, 4)
print(typeof(p))  // instance

// Property access
print(p.x)  // 3
print(p.y)  // 4

Type Checking

Use typeof() to check a value's type:

fn is_number(value) {
    var t = typeof(value)
    return t == "int" || t == "float"
}

fn is_collection(value) {
    var t = typeof(value)
    return t == "list" || t == "dict"
}

print(is_number(42))         // true
print(is_number(3.14))       // true
print(is_number("42"))       // false

print(is_collection([1,2]))   // true
print(is_collection({"a":1})) // true

Type Conversion

Function Accepted Types Result
str() Any string
int() string, float, int, bool int
float() string, float, int float
// To string
print(str(42))           // "42"
print(str(3.14))         // "3.14"
print(str(true))         // "true"
print(str([1, 2]))       // "[1, 2]"

// To integer
print(int("42"))         // 42
print(int(3.7))          // 3 (truncates)
print(int(true))         // 1
print(int(false))        // 0

// To float
print(float("3.14"))     // 3.14
print(float(42))         // 42.0

See Also