Type Inference and Abstractions

You shouldn't have to spell out every type. The compiler can figure out most types from context. But you also shouldn't have to guess what types are flowing through your code. We want the best of both worlds: less boilerplate, but full transparency.

Type Inference

We infer types locally but require annotations at boundaries:

// Function signature requires types
fn process($input string): Result<i32, Error> {
    // Inside, types are inferred
    $trimmed = $input.trim();   // inferred as string
    $parsed = $trimmed.parse(); // inferred as Result<i32, ParseError>
    $value = $parsed?;          // inferred as i32

    $value * 2 // return type matches signature
}

Why This Works

  • Reading code: Function signatures tell you what goes in and out
  • Writing code: Less boilerplate inside functions
  • Clarity: Types at boundaries, inference for local variables
  • Transparency: You always know what types cross abstraction boundaries

Inference Rules

The compiler infers types from:

  1. Literal values:
   $x = 42;      // inferred as int (default integer type)
   $y = 3.14;    // inferred as float (default float type)
   $s = "hello"; // inferred as string
   
  1. Function calls:
   fn add($a i32, $b i32): i32 { $a + $b }

   $result = add(10, 20); // inferred as i32 from return type
   
  1. Method calls:
   $s = "hello";
   $len = $s.len(); // inferred as usize from method signature
   
  1. Operators:
   $x = 10;     // inferred as i32
   $y = $x + 5; // inferred as i32 because $x is i32
   

Explicit Annotations When Needed

Sometimes the compiler needs help:

// Ambiguous without annotation
$numbers = vec![1, 2, 3]; // ERROR: could be Vec<i32>, Vec<i64>, etc.

// Explicit type resolves ambiguity
$numbers Vec<i32> = vec![1, 2, 3]; // Clear!

// Or turbofish syntax
$numbers = vec::<i32>[1, 2, 3];

Zero-Cost Abstractions

High-level code should compile to efficient machine code. No runtime overhead for abstractions.

Generics

Generics compile to monomorphized code — a separate copy for each concrete type:

fn swap<T>($a &T, $b &T) {
    $temp = *$a;
    $a = $b;
    *$b = $temp;
}

// Compiling swap::<i32> generates code as efficient as:
fn swap_i32($a &i32, $b &i32) {
    $temp = *$a;
    $a = $b;
    *$b = $temp;
}

No runtime dispatch, no indirection, no overhead. The abstraction costs nothing.

Iterators

Iterator chains compile to tight loops:

$sum = $numbers
    .iter()
    .filter(($x) { *$x > 0 })
    .map(($x) { $x * 2 })
    .sum();

// Compiles to code equivalent to:
$sum = 0;
for $x in $numbers {
    if $x > 0 {
        $sum += $x * 2;
    }
}

The high-level iterator chain is just as fast as a hand-written loop.

Inline Functions

Small functions inline automatically:

fn square($x i32): i32 {
    $x * $x
}

$result = square(5);

// Compiles to:
$result = 5 * 5;

No function call overhead. The abstraction disappears at compile time.

Traits: Shared Behavior

Traits define shared behavior across types. Like interfaces, but more powerful.

Trait Implementation

Types implement traits using the @TraitName annotation:

trait Printable {
    fn print;
}

type Point {
    x i32
    y i32

    fn new($x i32, $y i32): Point {
        return Point { x: $x, y: $y };
    }

    // Trait method defined with @TraitName annotation
    @Printable
    fn print {
        echo "({}, {})", $this.x, $this.y;
    }
}

Another Example

type Vector {
    x f64
    y f64

    @Printable
    fn print {
        echo "({}, {})", $this.x, $this.y;
    }
}

Generic Constraints

Use traits to constrain generic types:

fn print_all<T: Printable>($items &[T]) {
    for $item in $items {
        $item.print();
    }
}

// Works with both classes and structs
$points = [Point::new(0, 0), Point::new(1, 1)];
print_all(&$points);

$vectors = [Vector { x: 1.0, y: 2.0 }, Vector { x: 3.0, y: 4.0 }];
print_all(&$vectors);

This compiles to efficient code for each concrete type implementing Printable through monomorphization — a separate, optimized version for each type.

The Result

You write high-level, expressive code. The compiler generates efficient machine code. No runtime overhead, no sacrifices.

  • Generics: Zero-cost polymorphism
  • Iterators: High-level composition with loop performance
  • Inlining: Abstraction layers disappear
  • Monomorphization: Generic code becomes concrete code

High-level and fast. That's the power of zero-cost abstractions.

Copyright (c) 2025 Ocean Softworks, Sharkk