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.