ext Statement User Guide

Overview

The ext statement allows you to add methods to existing types after their initial definition. This is similar to Rust's impl blocks but adapted for Auto's syntax.

Syntax

ext TypeName {
    // Instance methods
    fn method_name() ReturnType {
        // self is available implicitly
        // Use .prop to access self.prop
    }

    // Static methods
    static fn static_method() ReturnType {
        // No self available
        // Called on the type, not instances
    }
}

Instance Methods

Instance methods have implicit access to self (the instance value):

ext int {
    fn double() int {
        self + self  // self is the int value
    }

    fn triple() int {
        self + self + self
    }
}

fn main() {
    let x = 5
    print(x.double())  // Output: 10
    print(x.triple())  // Output: 15
}

Accessing Fields

For user-defined types with fields, use .prop shorthand:

type Point {
    x int
    y int
}

ext Point {
    fn sum() int {
        .x + .y  // .x is shorthand for self.x
    }
}

fn main() {
    let p = Point { x: 3, y: 4 }
    print(p.sum())  // Output: 7
}

Static Methods

Static methods don't have access to self and are called on the type itself:

ext int {
    static fn default() int {
        42
    }

    static fn max() int {
        2147483647
    }
}

fn main() {
    let x = int.default()
    print(x)  // Output: 42

    let m = int.max()
    print(m)  // Output: 2147483647
}

Built-in Type Extension

You can extend any built-in type:

ext int {
    fn is_even() bool {
        self % 2 == 0
    }
}

ext str {
    fn is_empty() bool {
        .size == 0
    }
}

fn main() {
    let x = 4
    print(x.is_even())  // Output: true

    let s = "hello"
    print(s.is_empty())  // Output: false
}

Multiple ext Blocks

You can have multiple ext blocks for the same type:

ext int {
    fn double() int {
        self + self
    }
}

ext int {
    fn triple() int {
        self + self + self
    }
}

fn main() {
    let x = 5
    print(x.double())  // Output: 10
    print(x.triple())  // Output: 15
}

Note: If you define the same method twice, the later definition overwrites the earlier one with a warning.

Method Names

Methods are registered using the format TypeName::method_name:

  • int.double() → registered as int::double
  • Point.sum() → registered as Point::sum

This avoids conflicts with methods of the same name on different types.

C Transpilation

The Auto to C transpiler converts ext methods to regular C functions:

Auto code:

ext int {
    fn double() int {
        self + self
    }
}

fn main() {
    let x = 5
    print(x.double())
}

Generated C code:

int int_double(int self) {
    return self + self;
}

int main(void) {
    int x = 5;
    printf("%d\n", int_double(x));
    return 0;
}

Key Differences in C

  1. Function naming: TypeName_method_name
  2. Instance methods: Pass by value for built-in types
  3. Static methods: No self parameter
  4. User-defined types: Pass by pointer (e.g., Point*)

Best Practices

  1. Group related methods: Use one ext block per feature or concern
  2. Use static methods for constructors: e.g., int.from_string()
  3. Keep methods focused: Each method should do one thing well
  4. Document your methods: Add comments explaining purpose and usage
  5. Avoid naming conflicts: Be careful with common method names

Examples

Example 1: String Utilities

ext str {
    fn is_empty() bool {
        .size == 0
    }

    fn has_prefix(prefix str) bool {
        // Implementation depends on str functions
        false  // Placeholder
    }
}

fn main() {
    let s = "hello"
    print(s.is_empty())  // Output: false
}

Example 2: Integer Operations

ext int {
    fn abs() int {
        if self < 0 {
            -self
        } else {
            self
        }
    }

    fn clamp(min int, max int) int {
        if self < min {
            min
        } else if self > max {
            max
        } else {
            self
        }
    }
}

fn main() {
    let x = -5
    print(x.abs())  // Output: 5

    let y = 15
    print(y.clamp(0, 10))  // Output: 10
}

Example 3: Type Conversion

ext int {
    static fn from_string(s str) int {
        // Implementation would parse string to int
        0  // Placeholder
    }
}

fn main() {
    let x = int.from_string("42")
    print(x)  // Output: 42 (when implemented)
}

Limitations

  1. No generic methods: Currently cannot define generic ext methods
  2. No method overloading: Cannot have multiple methods with the same name and different parameters
  3. Runtime registration: Methods are registered at runtime, not compile-time
  4. Type inference: For complex expressions, you may need explicit type annotations

See Also