← Back to AILP Home

Dynamic Type — any

Overview

any is Nitpick's type-erased value container. It can hold a value of any concrete type, stored internally as a fat-pointer struct { ptr data, i64 size }. The data pointer points at a copy of the original value, and the size field records its byte width.

Note: dyn is NOT an alias for any. dyn is used exclusively for trait object dispatch (dyn TraitName in function parameters). See traits.

Declaration

any:box = 42i64;       // auto-boxes int64 into any
any:name = "hello";    // auto-boxes string into any

Any concrete type can be auto-boxed into any at assignment — this is implicit type erasure.

Access Methods

Use turbofish syntax to read, write, and resolve any values:

// Read with get::<T>() — extract the stored value as type T
int64:val = box.get::<int64>();

// Write with set::<T>(value) — overwrite the stored value
box.set::<int64>(100i64);

// Resolve — consuming transform to concrete pointer (T->)
int64->:ptr = box.resolve::<int64>();

These are the only valid operations on any. Direct member access, dereference, and arithmetic are all rejected with actionable error messages (see Diagnostics below).

Casting (v0.42.2)

any → T (extraction via @cast_unchecked)

Because any has no runtime type tag, a checked cast is meaningless — there is nothing to validate against. Extracting a value requires @cast_unchecked:

any:box = 42i64;
int64:val = @cast_unchecked<int64>(box);    // unsafe extraction

Using @cast (checked) is rejected with diagnostic NITPICK-ANY-004 and guidance to use either @cast_unchecked or the safe .get::<T>() API.

T → any (boxing via @cast)

Boxing is always safe — @cast<any>(value) wraps a concrete value into an any container:

int64:val = 99i64;
any:box = @cast<any>(val);     // explicit boxing

This is equivalent to a direct assignment (any:box = val;), but the explicit cast can be useful when you want to make the type erasure visible at the call site.

Summary

Direction Checked @cast Unchecked @cast_unchecked Safe API
any → T ❌ rejected ✅ allowed (unsafe) .get::<T>()
T → any ✅ allowed (safe) ✅ allowed direct assignment

any vs wild — When to Use Which

These two concepts are often confused. They serve fundamentally different purposes:

any wild
What it is Type-erased value container Memory region qualifier
IR layout { ptr data, i64 size } struct Raw pointer (no metadata)
GC interaction GC-managed by default Explicitly opt-out of GC
Type safety Safe via .get<T>() API None — raw pointer arithmetic
Use case Heterogeneous collections, plugin interfaces C FFI, manual memory, performance
Example any:box = 42i64; wild int32->:ptr = raw malloc(4);

Rule of thumb: Use any when you need to erase a type but stay within Nitpick's safety model. Use wild when you need to drop to raw memory management (typically at FFI boundaries).

Heterogeneous Collections

any[5]:items;
items[0] = 42i64;
items[1] = "hello";

any in Generic Type Arguments (v0.42.2)

any can be used as a turbofish type argument in generic functions:

func<T>:wrap = any(*T:value) {
    any:box = value;
    pass(box);
};

// Use any as a generic constraint
any:result = wrap::<int64>(42i64);

Diagnostics

ID Trigger Guidance
NITPICK-ANY-001 box.value (member access on any) Use .get::<T>(), .set::<T>(val), or .resolve::<T>()
NITPICK-ANY-002 <-box (dereference on any) Use .get::<T>() or .resolve::<T>()
NITPICK-ANY-003 box + 1 (arithmetic on any) Extract value first: box.get::<int64>() + 1
NITPICK-ANY-004 @cast<int64>(box) (checked cast from any) Use @cast_unchecked<T>() or .get::<T>()

Performance

Dynamic type checking has runtime overhead. Prefer static types wherever possible. Use any only when truly needed (e.g., heterogeneous containers, plugin interfaces).

Related