-
any. -
Raw_Any. -
It is functionally equivalent to
struct {data: rawptr, id: typeid}with extra semantics on how assignment and type assertion works. -
The
anyvalue is only valid as long as the underlying data is still valid. Passing a literal to ananywill allocate the literal in the current stack frame.
Comparison
any
vs
union
-
anyis a topologically-dual to aunionin terms of its usage.-
Both support assignments of differing types (
anybeing open to any type,unionbeing closed to a specific set of types). -
Both support type assertions (
x.(T)). -
Both support
switch in.
-
-
The main internal difference is how the memory is stored.
-
A
anybeing open is a pointer+typeid, aunionis a blob+tag. -
A
uniondoes not need to store atypeidbecause it is a closed ABI-consistent set of variant types.
-
Structure
Raw_Any :: struct {
data: rawptr, // pointer to the data
id: typeid, // type of the data
}
@(require_results)
any_data :: #force_inline proc(v: any) -> (data: rawptr, id: typeid) {
return v.data, v.id
}
Storing data
-
It always stores a pointer to the data.
-
anyonly works by having a pointer to something. This something can be stored in the heap or on the stack. -
If the data is already stored somewhere the operation is more direct, otherwise a temp variable is created on the stack and a pointer to this temp variable is used instead.
-
The only way to make
anyhold a value that outlasts the stack, the value needs to be stored in the heap. This is needed as ananyonly stores a pointer to something; this indirection makes things a quite more annoying.
Loose examples
-
The value is already stored :
x: int = 123
a: any = x
// equivalent to
a: any = { data = &x, id = typeid_of(type_of(x)) }
x: ^int = new(123)
a: any = x
// equivalent to
a: any = { data = &x, id = typeid_of(type_of(x)) }
-
The value is not yet stored :
a: any = 123
// equivalent to
_tmp: int = 123 // variable created on the stack
a: any = { data = &_tmp, id = typeid_of(type_of(_tmp)) }
x: int = 123
a: any = &x
// equivalent to
_tmp: ^int = &x // variable created on the stack
a: any = { data = &_tmp, id = typeid_of(type_of(_tmp)) }
Storing a pointer to something on the stack
-
It's possible to get a pointer to the value (the value is stored):
-
Assigning implicitly:
-
x: int = 123; a: any = x-
xis a value on the stack. -
astoresa.data = &x, which is a pointer to the value on the stack .
-
-
x: ^int = &i; a: any = x-
iis a value on the stack. -
xstores a pointer to something on the stack. -
astoresa.data = &x, which is a pointer to something on the stack , to a pointer to something on the stack.
-
-
x := make([]int, 3); a: any = x-
xis a array slice on the stack, that stores a pointer to something on the heap. -
astoresa.data = &x, which is a pointer to the array slice on the stack , which then points to the heap. -
This is a really weird one, but
xis indeed on the stack, as mentioned by 'Barinzaya' and 'rats'.
-
-
x: ^int = new_clone(123); a: any = x-
xis a pointer to the heap. -
astoresa.data = &x, which is a pointer on the stack , to a pointer on the heap.
-
-
-
Assigning explicitly (storing directly into the
.datafield):-
x: ^int = &i; a: any = { data = x, id = typeid_of(int) }-
iis a value on the stack. -
xstores a pointer to something on the stack. -
astores a pointer to something on the stack . -
Note how an indirection is removed, when comparing to
x: ^int = &i; a: any = x.
-
-
-
-
It's not possible to get a pointer to the value (the value is not stored):
-
Assigning implicitly:
-
awill always storea.data = &_tmp, where_tmpis on the stack; therefore, it always stores a pointer to the stack , due to the indirection of&_tmp. -
a: any = 123-
123is a literal, not yet stored. -
_tmp: int = 123. -
astoresa.data = &_tmp, which is a pointer to something on the stack .
-
-
x: int = 123; a: any = &x-
&xis a pointer tox; the value is stored, but the pointer is not yet stored. -
_tmp: ^int = &x. -
astoresa.data = &_tmp, which is a pointer to something on the stack , to a pointer to something on the stack.
-
-
a: any = new_clone(123)-
new_clone(123)is a pointer to123on the heap; the value is stored on the heap, but the pointer is not yet stored. -
_tmp: ^int = new_clone(123). -
astoresa.data = &_tmp, which is a pointer to something on the stack , to a pointer to something on the heap.
-
-
-
Assigning explicitly (storing directly into the
.datafield):-
a: any = { data = &i, id = typeid_of(int) }-
Is the same case as
x: ^int = &i; a: any = { data = x, id = typeid_of(int) }, but removing the need forx.
-
-
-
Storing a pointer to something on the heap
-
It's possible to get a pointer to the value (the value is stored):
-
Assigning implicitly:
-
x := make([]int, 3); a: any = x[2]-
xis a array slice on the stack, that stores a pointer to something on the heap. -
astoresa.data = &x[2], which is a pointer to something on the heap . -
Even though
xis on the stack,x[i]is a value on the heap, so&x[i]is a pointer on the heap.
-
-
-
Assigning explicitly (storing directly into the
.datafield):-
x: ^int = new_clone(123, context.temp_allocator); a: any = { data = x, id = typeid_of(int) }-
xstores a pointer to something on the heap. -
astores a pointer to something on the heap.-
This is not the same as doing:
x: ^int = new_clone(123) a: any = x-
astores a pointer toxon the stack, which stores a pointer to something on the heap.
-
-
-
Note how
idneeds to beint, whilex: ^int. -
When unwrapping the data, we'll get an
int, not the original^int. -
The original
^intcan actually be retrieved by doing(cast(^int)a.data), instead of(cast(^int)a.data)^; this has to be done manually.-
The second option is done automatically by
a.(int). -
Doing something like
a.(^int)in this case will just cause a failure, as(cast(^^int)a.data)^is not valid; the data is not^^int, but^int.
-
-
If the original
^intis not retrieved, then the pointer is lost and the memory cannot be freed; to avoid this, this technique should use of arena allocators, such ascontext.temp_allocator. -
(2025-11-08)
-
I tested this and it worked correctly:
batch := new_clone(Batch(T){ index = i32(i), offset = i32(offset), data = data[offset:min(offset + max_batch_size, len(data))], }, context.temp_allocator) args[0] = { data = batch, id = typeid_of(Batch(T)) } -
-
-
-
-
It's not possible to get a pointer to the value (the value is not stored):
-
Assigning implicitly:-
This makes a
_tmpbe created, which will always be on the stack, so this is not possible if you want to store a pointer to something on the heap.
-
-
Assigning explicitly (storing directly into the
.datafield):-
a: any = { data = new_clone(123, context.temp_allocator), id = typeid_of(int) }-
Same case as
x: ^int = new_clone(123, context.temp_allocator); a: any = { data = x, id = typeid_of(int) }, but removing the need forx.
-
-
-
About array/slices with
any
-
Barinzaya:
-
A slice is a pointer and length, in
x := make([]int)xwould still be on the stack.
-
-
Rats:
-
Variables are always on the stack.
-
You can't have a "heap allocated variable", but you can have a variable holding a pointer into the heap.
-
-
Barinzaya:
-
That's what
xis. The actual data in the slice is behind the pointer, and can be anywhere (heap, stack, mapped file, static data, etc.) -
A slice is ultimately just a kind of pointer, it just points to an array of a variable number of things rather than just one thing
-
-
Caio:
-
is it possible to do something like
x := make([]int); a: any = x.data, so thea.data = &x.data, which then is a pointer to the heap?
-
-
Barinzaya:
-
Kind of, but you wouldn't be able to keep the length
-
That's basically what
a: any = x[0]would do -- it would store a pointer to the first element in the backing data. But it loses the length. -
If you knew the length, you could "rebuild" the slice, but
anywon't really help with that.
-
-
Caio:
-
so then, there's no way for me to store a whole array inside a
any?
-
-
Barinzaya:
-
You'd have to allocate the slice itself too
x_data := make([]int, 4) x := new_clone(x_data) a: any = x^-
But that means you need to handle
deleteing/freeing both levels of indirection. If you're getting to that point, maybe it's time to reconsider why you need that.
-
Getting the underlying value
-
(cast(^T)a.data)^is the same asa.(T). -
Barinzaya:
-
Also asserting the
id, but otherwise yes, they are the same.
-
-
Not possible:
-
(cast(^(a.id))a.data)^ -
or
-
a.(a.id) -
As the
.idid runtime known, not comp-time known.
-
Using
.()
My_Struct :: struct{
x: int,
y: intrinsics.Atomic_Memory_Order,
}
main :: proc() {
{
a: int = 123
b: any = a
c := b.(int)
fmt.printfln("a: %v, b: %v, c: %v", a, b, c)
}
{
a: [4]bool
b: any = a
c := b.([4]bool)
fmt.printfln("a: %v, b: %v, c: %v", a, b, c)
}
{
a := make([dynamic]My_Struct, context.temp_allocator)
append(&a, My_Struct{}, My_Struct{ 2, .Relaxed })
b: any = a
c := b.([dynamic]My_Struct)
fmt.printfln("a: %v, b: %v, c: %v", a, b, c)
}
{
a := make([dynamic]My_Struct, context.temp_allocator)
append(&a, My_Struct{}, My_Struct{ 2, .Relaxed })
b: any = a[:]
c := b.([]My_Struct)
fmt.printfln("a: %v, b: %v, c: %v", a, b, c)
}
}
-
a,bandchere are always printed the same, whilechas the type ofa.
Using
switch v in a {}
-
ais theanyvariable. -
vis the unwrapped value.
a: any = 123
switch v in a {
case int:
fmt.printfln("Is int. Value: %v", v)
// prints "Is int. Value: 123"
case []byte:
}
Using the
reflect
procedures
-
They do the same operation as shown, but fancier.
-
as_bool. -
as_bytes.@(require_results) as_bytes :: proc(v: any) -> []byte { if v != nil { sz := size_of_typeid(v.id) return ([^]byte)(v.data)[:sz] } return nil } -
as_f64. -
as_i64. -
as_u64. -
as_int. -
as_uint. -
-
Attempts to convert an
anyto arawptr. -
This only works for
^T,[^]T,cstring,cstring16based types.
// Various considerations first. result = (^rawptr)(any_value.data)^ -
-
-
Returns the equivalent of doing
raw_data(v)wherevis a non-any value
// Various considerations first. result = any_value.data -
Etc
Is
-
is_nil.-
Returns true if the
anyvalue is eithernilor the data stored at the address is all zeroed
-
Etc
-
deref.-
Dereferences
anyif it represents a pointer-based value (^T -> T)
-
-
-
Returns the name of enum field if a valid name using reflection, otherwise returns
"", false
-
-
equal.-
Checks to see if two
anyvalues are semantically equivalent
-
-
-
Returns the underlying variant value of a union. Panics if a union was not passed.
-
-
-
UNSAFE: Returns the underlying tag value of a union. Panics if a union was not passed.
-
-
index.-
Gets the value by an index, if the type is indexable. Returns
nilif not possible
-
Examples
-
See the example below about
typeids.