8. Type System¶
8.1 Types vs Traits¶
The language distinguishes between types (what things are) and traits (how things behave):
Types define the concrete structure and memory layout:
Point- A struct type with two fieldsRectangle- A struct type with width and heighti32- A primitive integer typeOption<T>- A union type that may contain a value
Traits define behavioral contracts:
::Addable- Types that support addition and subtraction::Drawable- Types that can be drawn::Stackable- Types that support stack operations
Key Distinction:
- A value has a type (what it is structurally)
- A value implements a trait (how it behaves)
- Types are concrete; traits are interfaces
- Functions can be generic over traits
- Functions can specify types or traits in signatures
- Every operator must be backed by a trait
Examples:
// Point is a type
(T T --) { x: y: } ::Point<T> struct
// Addable is a trait
{
(Self Self -- Self) +:
(Self Self -- Self) -:
} ::Addable trait
// Point implements Addable (Point now behaves additively)
::Addable {
(Self Self -- Self) {
over ::x get over ::x get +
swap ::y get swap ::y get +
Point
} +:
// ... subtract implementation ...
} ::Point impl
Related: See Section 9 for the complete trait system and Appendix B for all standard trait definitions. See Appendix H for detailed information about all types.
8.2 Type Inference¶
The compiler infers types in most situations, minimizing the need for explicit type annotations.
When Type Inference Works:
42 // Inferred as i64 (default integer)
3.14 // Inferred as f64 (default float)
[1 2 3] // Inferred as array of i64
// Function return type inferred from body
(Number -- Number) { dup * } ::square fn
5 square // Compiler knows result is Number
When Annotations Are Needed:
42:i32 // Explicit annotation for 32-bit integer
3.14:f32 // Explicit annotation for 32-bit float
// Ambiguous generic contexts may need hints
parse // May need type context to know what to parse to
Type Inference with Generics:
// Type parameter T is inferred from usage
(T -- T) { } ::identity fn
5 identity // T inferred as i64
"hello" identity // T inferred as String
Related: See Section 10 for generic programming and type parameter inference.
8.3 Type Tuples¶
Type tuples represent stack effects and are used in function signatures to specify what a function consumes from and produces to the stack.
Syntax: (inputs -- outputs)
Examples:
(T T -- T) // Two inputs of type T, one output of type T
(i32 f64 -- String) // Takes i32 and f64, returns String
(-- bool) // No inputs, returns bool
(Point --) // Takes Point, no outputs
(---) // No inputs, no outputs (side effects only)
Variable Arguments with Iterable:
For operations that need variable numbers of arguments, use the Iterable trait:
(-- Size) depth: // Zero arguments, returns stack depth
(String Iterable<Stringifiable> --) format: // String plus iterable of printable values
(Iterable<T> -- T) sum: // Iterable of values, returns sum
Examples:
"x=%d, y=%d" [x y] format // Format string with array of values
[1 2 3 4 5] sum // Sum array of numbers
depth print // No arguments needed
This approach keeps the type system simple without special variadic syntax.
8.4 Type Composition¶
Types can contain other types, creating composite data structures:
Nested Structures:
// Point contains two T values
(T T --) { x: y: } ::Point<T> struct
// Line contains two Points
(Point<T> Point<T> --) { start: end: } ::Line<T> struct
Arrays of Types:
[1 2 3] // Array of i64
[[1 2] [3 4]] // Array of arrays
[Point Point Point] // Array of Points
Generic Composition:
// Box contains a value of any type
(T --) { value: } ::Box<T> struct
// Option can contain any type (or nothing)
(T --) { Some(T) None } ::Option<T> union
// Nested generics
Option<Point<f64>> // Option containing a Point of f64s