The Mixed Type

Sometimes you don't know what type you'll get. Maybe you're parsing JSON, building a plugin system, or interfacing with dynamic code. That's where mixed comes in.

What is Mixed?

mixed is a type that can hold any value:

$anything mixed = 42;
$anything = "hello";
$anything = User { "Alice", "alice@example.com" };
$anything = [1, 2, 3];

Any value can be assigned to mixed. It's Coco's escape hatch from static typing—use it when you genuinely can't know the type at compile time.

Type Assertions

To use a mixed value, you need to check its type with is:

$value mixed = get_dynamic_value();

// Type check - $value becomes typed inside the block
if $value is string {
    echo "Got string: {$value}";  // $value is now string
    echo "Length: {$value.len()}";
}

// With else
if $value is i32 {
    echo $value + 1;
} else {
    echo "Not an integer";
}

The is keyword checks the type at runtime. Inside the if block, the variable is automatically narrowed to that type.

Type Checking

Use is in match expressions for type switching:

$value mixed = something();

match $value {
    is i32 => echo "It's an integer: {$value}",
    is string => echo "It's a string: {$value}",
    is [mixed] => echo "It's an array",
    default => echo "Something else"
}

Inside each arm, $value is typed appropriately. You can also use type_of for string-based checks:

if type_of($value) == "string" {
    // $value is still mixed here - use is for narrowing
}

When to Use Mixed

JSON and Dynamic Data

fn parse_json($text string): !mixed {
    // Returns mixed because JSON can be any shape
}

$data = parse_json($response);

// Navigate dynamic structure
if $data is {string: mixed} {
    if $data["name"] is string {
        echo "Name: {$data[\"name\"]}";
    }
}

Plugin Systems

type Plugin {
    name string
    config mixed  // Each plugin has different config
}

fn load_plugin($p Plugin) {
    match $p.name {
        "logger" => {
            if $p.config is LoggerConfig {
                setup_logger($p.config);
            }
        }
        "cache" => {
            if $p.config is CacheConfig {
                setup_cache($p.config);
            }
        }
    }
}

Heterogeneous Collections

// When you truly need different types together
$items [mixed] = [1, "two", 3.0, true];

for $item in $items {
    match $item {
        is i32 => echo "int: {$item}",
        is string => echo "str: {$item}",
        is f64 => echo "float: {$item}",
        is bool => echo "bool: {$item}",
        default => echo "unknown"
    }
}

Mixed vs Generics vs Trait Objects

When should you use each?

Approach Type Safety Performance Use When
Generics Compile-time Zero-cost You know all types at compile time
Trait Objects Compile-time Small overhead Different types share behavior
Mixed Runtime Boxing overhead Truly dynamic data

Generics: Best Performance, Full Safety

fn process<T: Display>($item T) {
    echo "{}", $item;
}

// Compiler generates specialized versions
process(42);      // process_i32
process("hello"); // process_string

Use generics when you know the types at compile time but want code reuse.

Trait Objects: Type-Safe Polymorphism

trait Drawable {
    fn draw;
}

fn render($shapes [dyn Drawable]) {
    for $shape in $shapes {
        $shape.draw();
    }
}

Use trait objects when different types share behavior you can define in a trait.

Mixed: True Dynamic Typing

fn handle_event($data mixed) {
    // Could be anything - parse at runtime
}

Use mixed when you genuinely can't predict types—external data, user input, plugin systems.

Performance Considerations

mixed has runtime costs:

Boxing: Values are boxed (heap-allocated) to fit in the uniform mixed representation.

Type checks: Every type assertion requires a runtime check.

No optimization: The compiler can't optimize operations on mixed values.

// Slow - boxing and runtime checks
fn sum_mixed($items [mixed]): i64 {
    $total = 0;
    for $item in $items {
        if $item is i64 {
            $total += $item;  // Runtime check each time
        }
    }
    return $total;
}

// Fast - monomorphized, no checks
fn sum<T: Add>($items [T]): T {
    $total = T::zero();
    for $item in $items {
        $total += $item;  // Direct addition
    }
    return $total;
}

Common Patterns

Defensive Extraction

fn get_string($val mixed, $default string): string {
    if $val is string {
        return $val;
    }
    return $default;
}

fn get_int($val mixed, $default i32): i32 {
    if $val is i32 {
        return $val;
    }
    return $default;
}

$name = get_string($data["name"], "Unknown");
$age = get_int($data["age"], 0);

Type Switch

fn describe($val mixed): string {
    return match $val {
        is null => "null",
        is bool => if $val { "true" } else { "false" },
        is i32 => "integer: {}".format($val),
        is f64 => "float: {}".format($val),
        is string => "string: \"{}\"".format($val),
        is [mixed] => "array[{}]".format($val.len()),
        default => "unknown"
    };
}

Wrapping Typed APIs

// Internal: type-safe
fn process_user_internal($user User): !Result { ... }

// External: accepts dynamic data
pub fn process_user($data mixed): !Result {
    if $data is User {
        return process_user_internal($data);
    }
    return error("Expected User, got {type_of($data)}");
}

Best Practices

Prefer generics. If you know types at compile time, use generics. You get better performance and compile-time safety.

Prefer trait objects. If types share behavior, define a trait. You keep type safety with minimal overhead.

Use mixed at boundaries. Mixed is ideal at system boundaries—parsing external data, plugin interfaces, FFI.

Extract early. Convert mixed to concrete types as early as possible, then work with typed values.

// Good - extract once, work typed
$config = parse_config($text);  // Returns mixed
if $config is map[string]mixed {
    if $config["port"] is i32 && $config["host"] is string {
        $port = $config["port"];
        $host = $config["host"];
        start_server($host, $port);  // Work with typed values
    }
}

// Bad - stay in mixed land
fn start_server($host mixed, $port mixed) {
    // Now everything needs type checks
}

Document what's expected. When a function takes mixed, document what types it actually handles.

/// Process a value that can be:
/// - i32: treated as count
/// - string: parsed as "count:N"
/// - [i32]: summed as count
fn process($input mixed): i32 {
    // ...
}

Comparison to Other Languages

Language Equivalent Notes
Go any / interface{} Same semantics
PHP mixed Naming inspiration
TypeScript any But TS has no runtime checks
Java Object With boxing
Python Everything Python is dynamically typed

Coco's mixed gives you PHP/Go-style dynamic typing when you need it, while keeping the rest of your code statically typed and fast.

Copyright (c) 2025 Ocean Softworks, Sharkk