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:
dynis NOT an alias forany.dynis used exclusively for trait object dispatch (dyn TraitNamein 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).