Casting

Casting

All explicit type conversions in Kairo use the as keyword. There are no separate cast operators like C++‘s static_cast, dynamic_cast, reinterpret_cast, and const_cast as handles all conversion categories, with safety determined by the source and target types.

For implicit conversions (numeric widening, T to T?, derived-to-base pointers), see Type System.


Numeric Casts

Widening

Integer and float widening is implicit no as required:

var a: i32 = 42
var b: i64 = a       // implicit

as is permitted but redundant for widening conversions.

Narrowing (truncation)

Narrowing conversions require an explicit as. The cast truncates by keeping the low bits of the source value:

var x: i64 = 1000
var y: i8 = x as i8    // truncates to low 8 bits: -24

var big: u32 = 0xDEADBEEF
var small: u8 = big as u8   // 0xEF

Truncation never panics. The as keyword is the programmer explicitly accepting potential data loss.

Float to integer

Float-to-integer casts truncate toward zero, matching C++ behavior. Out-of-range values saturate instead of producing undefined behavior:

var f: f64 = 3.9
var i: i32 = f as i32      // 3 (truncates toward zero)

var neg: f64 = -2.7
var n: i32 = neg as i32    // -2

var huge: f64 = 1.0e18
var s: i32 = huge as i32   // i32 max (2147483647) saturates

Integer to float

Integer-to-float casts may lose precision for large values but never fail:

var x: i64 = 9007199254740993   // 2^53 + 1
var f: f64 = x as f64           // rounded f64 cannot represent this exactly

Signed/unsigned conversion

Casting between signed and unsigned integers of the same width reinterprets the bit pattern:

var s: i8 = -1
var u: u8 = s as u8    // 255 (same bits, different interpretation)

Pointer Casts

Upcasting (derived to base)

Derived-to-base pointer conversion is implicit no as required:

class Animal { ... }
class Dog derives Animal { ... }

var dog = Dog("Rex", "Labrador")
var animal: *Animal = &dog   // implicit upcast

Downcasting (base to derived)

Base-to-derived casts come in two forms:

Asserting downcast panics if the runtime type does not match. The function must have the panic specifier or the cast must be inside a try block:

fn process(animal: *Animal) panic {
    var dog = animal as *Dog   // panics if animal is not a Dog
    dog->fetch()
}

Checked downcast returns a nullable pointer. &null if the runtime type does not match:

fn process(animal: *Animal) {
    var dog = animal as *Dog?   // null if animal is not a Dog
    if dog != &null {
        dog->fetch()
    }
}

Both forms perform a runtime type check using the vtable (the class must have at least one virtual method). Downcasting a non-polymorphic class is a compile error.

Raw pointer cast

Casting to unsafe *T reinterprets the pointer with no type checking equivalent to C++‘s reinterpret_cast. The compiler performs no validation:

var ptr: *i32 = &some_value
var raw = ptr as unsafe *void    // type erasure
var back = raw as unsafe *i32    // reinterpret caller must ensure correctness

Casting between unsafe *T types is always permitted. The result is the same pointer value with a different type no runtime check, no adjustment.

Caution

Raw pointer casts bypass AMT’s safety guarantees. Casting to unsafe *void erases type information permanently the compiler cannot verify the correctness of a subsequent cast back. Use only for C/C++ interop, custom allocators, and other low-level scenarios.

Pointer to integer

Casting a pointer to an integer extracts the numeric address. No unsafe block is required:

var ptr: *i32 = &some_value
var addr = ptr as usize    // ok: numeric address

var truncated = ptr as u8  // warning: truncation of pointer value

usize is the natural target type since it matches pointer width. Casting to a narrower integer produces a truncation warning.

Integer to pointer

Casting an integer to a pointer fabricates a pointer from a numeric address. The result must be an unsafe pointer safe pointers require provenance tracking that an integer cannot provide:

var addr: usize = 0x7FFE_0000_1000
var ptr = addr as unsafe *i32   // ok: fabricating an unsafe pointer

// var bad = addr as *i32       // compile error: cannot create safe pointer from integer

Enum Casts

Plain enums

Plain enums can be cast to their underlying integer type and back:

enum Direction derives u8 {
    North = 0,
    East = 1,
    South = 2,
    West = 3,
}

var raw = Direction::North as u8   // 0
var dir = 2u8 as Direction         // Direction::South

Casting an integer to an enum that has no matching discriminant is undefined behavior. The compiler does not insert a runtime check.

The cast target must match the underlying type. Direction::North as i32 requires the enum to be backed by i32, or an intermediate cast: Direction::North as u8 as i32.

ADT enums

ADT enums cannot be cast to integers. The discriminant tag is an internal implementation detail. If you need the tag value, expose it through an extend method:

enum <T> ParseResult {
    Success { value: T, consumed: i32 },
    Error   { message: string },
    EndOfInput,
}

// ParseResult::Success { ... } as u32   // compile error: ADT enums cannot be cast to integers

extend <T> ParseResult<T> {
    fn tag(self) const -> u32 {
        match self {
            case .Success { 0 }
            case .Error   { 1 }
            case .EndOfInput { 2 }
        }
    }
}

Nullable Collapsing Cast

Casting a nullable value T? to its underlying type T produces a non-null value. If the source is null, a default-constructed value of T is used instead:

var x: i32? = null
var y = x as i32     // 0 (default-constructed i32)

var s: string? = "hello"
var t = s as string  // "hello"

The target type T must be trivially default-constructible. If the type has a deleted default constructor, the collapsing cast is a compile error:

class NoDefault {
    fn NoDefault(self) = delete
}

var obj: NoDefault? = null
// var x = obj as NoDefault   // compile error: NoDefault has no default constructor

This differs from unwrap!(), which panics on null instead of default-constructing:

x as Tunwrap!(x)
Source is non-nullReturns the valueReturns the value
Source is nullReturns T() (default)Panics
Requires default constructorYesNo
Requires panic / tryNoYes

See Variables for other nullable operations (?., ??, null checking).


User-Defined Conversions (op as)

Types can define custom conversions by overloading the op as operator:

class Temperature {
    var celsius: f64

    fn Temperature(self, c: f64) { self.celsius = c }

    fn op as(self) -> f64 {
        return self.celsius
    }

    fn op as(self) -> string {
        return f"{self.celsius}C"
    }
}

var temp = Temperature(100.0)
var f = temp as f64      // 100.0
var s = temp as string   // "100.0C"

op as can be overloaded for multiple target types. The compiler selects the overload based on the target type in the as expression. op as must take only self as a parameter and return the target type.

See Operators for the full operator overloading reference.


Cast Summary

CastSyntaxSafetyBehavior on failure
Numeric wideningImplicitSafeN/A
Numeric narrowingx as i8TruncatesLow bits kept
Float to intx as i32Truncates/saturatesSaturates on overflow
Int to floatx as f64May lose precisionRounded
Derived-to-base ptrImplicitSafeN/A
Base-to-derived ptr (asserting)ptr as *DerivedRuntime checkPanics
Base-to-derived ptr (checked)ptr as *Derived?Runtime checkReturns &null
Raw pointer castptr as unsafe *TNo checkReinterpret
Pointer to integerptr as usizeSafeAddress value
Integer to pointern as unsafe *TUnsafeFabricated pointer
Plain enum to inte as u8SafeDiscriminant value
Int to plain enumn as DirectionUB if no matchNo runtime check
ADT enum to intN/ACompile errorUse extend method
Nullable collapsex as TSafeDefault-constructs on null
User-definedx as TargetTypeDepends on op asCalls user code
T to T?ImplicitSafeN/A
T? to Tx as TCollapsing castDefault on null

Casts Not in Kairo

C++ CastKairo Equivalent
static_cast<T>(x)x as T
dynamic_cast<T*>(p)p as *T? (checked) or p as *T (asserting)
reinterpret_cast<T*>(p)p as unsafe *T
const_cast<T*>(p)Not supported const cannot be stripped at runtime