Skip to main content

Type System

Dryft features a static type system that verifies type correctness at compile time. The type system is stack-based, tracking the types of values on the stack as your program executes.

Built-in Types

Dryft has three fundamental value types:

Number

Integer numbers (signed):
42
-17
0

Text

String literals:
"Hello, World!"
"Line one\nLine two"

Binary

Boolean values for logic and conditionals:
true
false

Type Inference

The compiler automatically tracks types on the stack. When you push a value, its type is known:
5           # Stack types: [Number]
3           # Stack types: [Number, Number]
+           # Stack types: [Number]

Function Type Signatures

Functions consume input types from the stack and produce output types:

Notation

Type signatures use the format: (inputs -> outputs)
(Int Int -> Bool)
fun: divby
    mod 0 =? ;
This function:
  • Takes two integers from the stack
  • Returns one boolean

Multiple Outputs

(Int -> Int Int)
fun: duplicate
    copy ;  # Produces two copies

No Inputs or Outputs

(-> Int)
fun: get_constant
    42 ;

(Int ->)
act: consume
    drop ;

Type Checking

The compiler enforces type safety throughout your program:

Arithmetic Operations

Arithmetic operators require Number types:
5 3 +      # OK: both are Number
5 3 *      # OK
5 3 -      # OK
10 2 /     # OK
7 3 mod    # OK: modulo operation

Comparison Operations

Comparisons produce Binary (boolean) results:
5 3 >?         # Number Number -> Binary
10 10 =?       # Number Number -> Binary
4 7 !=?        # Number Number -> Binary
5 3 >=?        # Number Number -> Binary
2 8 <?         # Number Number -> Binary

Logical Operations

Logical operations work on Binary types:
true false both?    # Binary Binary -> Binary (AND)
true false either?  # Binary Binary -> Binary (OR)
true not            # Binary -> Binary (NOT)

Type Footprint

Blocks must have a balanced type footprint - they should return the same types they consume:
# This block consumes nothing and returns nothing
true then:
    "Hello" prints  # prints consumes the string
;

# Loop must have no net type change
cycle:
    # loop body must maintain stack balance
;

Type Errors

The compiler catches type mismatches:
# ERROR: Type mismatch
"hello" 5 +  # Can't add Text and Number

# ERROR: then expects Binary
5 then:  # 5 is Number, not Binary
    "yes" prints
;

Variables and Types

Variables are typed when declared:
act: example
    10 var: x        # x has type Number
    "hi" var: msg    # msg has type Text
    true var: flag   # flag has type Binary
    
    $x 5 +          # OK: both Number
    # $msg 5 +      # ERROR: can't add Text and Number
:act

Method Types

Functions and actions can have complex type signatures:
(Int Int -> Int)
fun: max
    var: a var: b
    when:
        $a $b >? then: $a ;
        $b ;
:fun

Type System and Linear Types

Dryft’s type system works in conjunction with its linear type system. Each value must be used exactly once, ensuring:
  • No memory leaks
  • No use-after-free
  • Explicit resource management
See the Linear Types page for details.

Benefits of Static Typing

  1. Early Error Detection: Catch mistakes at compile time
  2. Documentation: Type signatures document function behavior
  3. Optimization: Compiler can optimize based on type information
  4. Safety: Prevents entire classes of runtime errors

Type System Features

Dryft’s type system can be disabled with a compilation flag (typesystem feature), but this is not recommended for production code.

Examples

Factorial with Types

(Int -> Int)
fun: factorial var: n
    1 var: result
    cycle: $n 0 =? then break ;
        $result $n * result!
        $n 1 - n!
    :cycle
    $result
:fun

Type-Safe Division

(Int Int -> Int)
act: safediv 
    var: x var: y
    when:
        $x 0 equals? then: "Division by zero" prints ; 
        $y 0 equals? then: 0 ;
        $x 1 equals? then: $y ;
        $y $x / ;
:act

Build docs developers (and LLMs) love