Types¶
Desi has a static type system that combines the simplicity of Python's syntax with the safety of compile-time type checking.
Primitive Types¶
Integers¶
int (typically 32-bit).
# Signed
let a: i8 = 127 # -128 to 127
let b: i16 = 32767 # -32,768 to 32,767
let c: i32 = 2147483647 # -2^31 to 2^31-1
let d: i64 = 9223372036854775807 # -2^63 to 2^63-1
# Unsigned
let e: u8 = 255 # 0 to 255
let f: u16 = 65535 # 0 to 65,535
let g: u32 = 4294967295 # 0 to 2^32-1
let h: u64 = 18446744073709551615 # 0 to 2^64-1
Integer Literals
- Decimal:
42,1000 - Hexadecimal:
0xDEAD,0xFF - Binary:
0b1011,0b11111111 - Octal:
0o755,0o644 - Scientific:
1e9(becomes float)
Floating Point¶
Boolean¶
let is_true: bool = true
let is_false: bool = false
# Boolean operations
let result: bool = true and false
let negated: bool = not true
String¶
let name: str = "Desi"
let greeting: str = "Hello, Desi"
let multiline: str = """
Multi-line
string literal
"""
# String concatenation
let full: str = "Hello" + " " + "World"
# F-strings (interpolation)
let msg: str = f"Hello, {name}!"
none¶
The none type represents the absence of a value. Functions that don't return anything have return type none.
Collection Types¶
List¶
# Type syntax
let numbers: list[int] = [1, 2, 3, 4, 5]
let names: list[str] = ["Alice", "Bob", "Charlie"]
# Shorthand syntax (sugar)
let nums: [int] = [1, 2, 3]
# Empty list (requires type annotation)
let empty: list[int] = []
# Operations
numbers.append(6)
let first: int = numbers[0]
let length: int = len(numbers)
Type Annotations Required
Empty collections require explicit type annotations since the compiler cannot infer the element type.
Dictionary¶
# Type syntax — dict[K, V] and dict<K, V> are both accepted
let ages: dict[str, int] = {"Alice": 30, "Bob": 25}
let scores: dict<str, int> = {"Alice": 100, "Bob": 95}
# Empty dict
let empty: dict[str, int] = {}
# Operations
ages["Charlie"] = 35
let age: int = ages["Alice"]
let has_key: bool = "Alice" in ages
Set¶
# Type syntax
let tags: set[str] = #{"python", "rust", "desi"}
# Empty set
let empty: set[int] = set()
# Operations
tags.add("go")
let has_python: bool = "python" in tags
let size: int = len(tags)
Set Literal Syntax
Use #{...} for set literals to distinguish from dict literals.
Empty sets must use set() constructor.
Tuple¶
# Type syntax
let pair: tuple[int, str] = (42, "answer")
let triple: tuple[str, int, bool] = ("test", 1, true)
# Destructuring
let (x, y) = pair
print(x) # 42
print(y) # "answer"
Tuples are fixed-size, heterogeneous collections with compile-time known types.
Type Aliases¶
Type aliases provide alternative names for existing types:
# Simple alias
type IntList = list[int]
type StringMap = dict[str, str]
# Usage
let numbers: IntList = [1, 2, 3]
let config: StringMap = {"key": "value"}
Generic Type Aliases¶
# Generic aliases with type parameters
type Box<T> = Option<T>
type Pair<A, B> = tuple[A, B]
type Triple<X, Y, Z> = tuple[X, Y, Z]
# Usage
let boxed: Box<int> = Option.Some(42)
let pair: Pair<str, int> = ("answer", 42)
let triple: Triple<int, str, bool> = (1, "two", true)
Type Annotations¶
Variable Declarations¶
# Explicit type annotation
let x: int = 42
let name: str = "Desi"
# Type inference (annotation optional)
let y = 42 # Inferred as int
let msg = "Hello" # Inferred as str
Function Signatures¶
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def no_return() -> none:
print("Side effect only")
When to Use Type Annotations
- Required: Function parameters and return types
- Required: Empty collections (
[],{},set()) - Optional: Variable declarations (type can be inferred)
- Recommended: Public APIs and complex expressions
Type Inference¶
Desi infers types when possible:
Limitations
Type inference cannot resolve:
- Empty collections: let x = [] ❌ (need let x: list[int] = [])
- Ambiguous expressions without context
- Generic type parameters without constraints
Type Conversion with as¶
Desi uses the as keyword for explicit type conversions between numeric types:
Basic Casts¶
# Integer size conversions
let x: int = 1000
let y: i32 = x as i32 # int → i32 (may truncate)
let z: i8 = 42 as i8 # literal → i8
# Float ↔ Integer
let pi: float = 3.14159
let pi_int: int = pi as int # 3 (truncates toward zero)
let n_float: float = 42 as float # int → float
# Float size conversions
let f: f32 = 3.14 as f32 # double → float
let d: f64 = 1.5 as f64 # float → double
Required for Mixed-Width Operations¶
let a: i32 = 100
let b: int = 50
# ❌ Error: mismatched numeric widths
# let sum = a + b
# ✅ Correct: cast to same type
let sum = (a as int) + b # Cast i32 → int
let sum2 = a + (b as i32) # Cast int → i32
When to Cast
- Always required when mixing sized types (
i8,i32,i64, etc.) - Always required for float ↔ int conversion
- Use
asinstead of constructor functions likeint(x)for numeric casts
What as Does Internally¶
| Cast Type | LLVM Operation | Notes |
|---|---|---|
| Large int → Small int | trunc |
May lose bits |
| Small int → Large int | sext |
Sign-extends |
| Float → Int | fptosi |
Truncates toward zero |
| Int → Float | sitofp |
May lose precision |
| Double → Float | fptrunc |
May lose precision |
| Float → Double | fpext |
No precision loss |
Decimal Type¶
The decimal type provides arbitrary-precision decimal arithmetic, ideal for financial calculations where floating-point errors are unacceptable:
# Create decimals from strings
let price: decimal = decimal("19.99")
let tax: decimal = decimal("0.0825")
# Precise arithmetic
let total: decimal = price + (price * tax) # Exact: 21.6391175
# No floating-point errors!
let a: decimal = decimal("0.1")
let b: decimal = decimal("0.2")
let sum: decimal = a + b # Exactly 0.3, not 0.30000000000000004
When to Use decimal¶
✅ Use for:
- Currency and financial calculations
- Scientific measurements requiring exact precision
- Any calculation where 0.1 + 0.2 == 0.3 must be true
❌ Don't use for:
- Performance-critical calculations (slower than float)
- Graphics or game development
- General-purpose math where approximation is acceptable
Decimal Operations¶
let a: decimal = decimal("100.50")
let b: decimal = decimal("20.25")
# Arithmetic
let sum: decimal = a + b # 120.75
let diff: decimal = a - b # 80.25
let prod: decimal = a * b # 2035.125
let quot: decimal = a / b # 4.962962962962962962962962963
# Comparison
if a > b:
print("a is larger")
Memory Managed
Decimal values are heap-allocated. The compiler automatically manages their memory through scope-based deallocation.
Type Compatibility¶
Assignability¶
# Same types are directly assignable
let x: int = 42
let y: int = x # OK
# Subtyping with Option/Result
let maybe: Option<int> = Option.Some(42)
let result: Result<int, str> = Result.Ok(42)
Generics¶
# Generic types must match exactly
let list_int: list[int] = [1, 2, 3]
let list_str: list[str] = ["a", "b", "c"]
# ❌ Cannot assign list[int] to list[str]
# let mixed: list[str] = list_int # Error!
Special Types¶
Option¶
Represents a value that may or may not exist:
let some: Option<int> = Option.Some(42)
let nothing: Option<int> = Option.Nothing
match some:
Option.Some(value): print(f"Got {value}")
Option.Nothing: print("No value")
See Error Handling for details.
Result¶
Represents either success or failure:
let success: Result<int, str> = Result.Ok(42)
let failure: Result<int, str> = Result.Err("failed")
match success:
Result.Ok(value): print(f"Success: {value}")
Result.Err(error): print(f"Error: {error}")
See Error Handling for details.
Common Patterns¶
Multiple Return Values¶
Use tuples for multiple return values:
def divide(a: int, b: int) -> tuple[int, int]:
return (a / b, a % b) # (quotient, remainder)
let (quot, rem) = divide(10, 3)
print(quot) # 3
print(rem) # 1
Type Guards with Match¶
match is an expression, so each arm is a single expression — return cannot
appear inside an arm. Return the match itself:
Collection Initialization¶
# Pre-sized list (future feature)
let numbers = [0] * 10 # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# Dict with defaults
let defaults: dict[str, int] = {"a": 0, "b": 0, "c": 0}
# Set from list
let unique: set[int] = set([1, 2, 2, 3, 3, 3]) # {1, 2, 3}
Type System Design¶
Strengths¶
- Static Checking - Catch type errors at compile time
- Type Inference - Less verbose than Java/C++, safer than Python
- No Implicit Conversions - Explicit is better than implicit
- Generic Types - Code reuse without sacrificing type safety
Philosophy¶
"Explicit where it matters, inferred where it's obvious"
Desi requires type annotations for: - Function boundaries (clear contracts) - Ambiguous situations (empty collections)
But allows inference for: - Local variables (obvious from RHS) - Simple expressions (clear types)
See Also¶
- Functions - Function type signatures
- Generics - Generic programming with type parameters
- Error Handling - Option and Result types
- Classes - Custom types and methods