any

  • any .

  • Raw_Any .

  • It is functionally equivalent to struct {data: rawptr, id: typeid}  with extra semantics on how assignment and type assertion works.

  • The any  value is only valid as long as the underlying data is still valid. Passing a literal to an any  will allocate the literal in the current stack frame.

Comparison any  vs union

  • any  is a topologically-dual to a union  in terms of its usage.

    • Both support assignments of differing types ( any  being open to any type, union  being 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 any  being open is a pointer+typeid, a union  is a blob+tag.

    • A union  does not need to store a typeid  because 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.

  • any  only 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 any  hold a value that outlasts the stack, the value needs to be stored in the heap. This is needed as an any  only 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

        • x  is a value on the stack.

        • a  stores   a.data = &x , which is a pointer to the value on the stack .

      • x: ^int = &i; a: any = x

        • i  is a value on the stack.

        • x  stores a pointer to something on the stack.

        • a  stores   a.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

        • x  is a array slice on the stack, that stores a pointer to something on the heap.

        • a  stores a.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 x  is indeed on the stack, as mentioned by 'Barinzaya' and 'rats'.

      • x: ^int = new_clone(123); a: any = x

        • x  is a pointer to the heap.

        • a  stores a.data = &x , which is a pointer on the stack , to a pointer on the heap.

    • Assigning explicitly (storing directly into the .data  field):

      • x: ^int = &i; a: any = { data = x, id = typeid_of(int) }

        • i  is a value on the stack.

        • x  stores a pointer to something on the stack.

        • a  stores 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:

      • a  will always  store a.data = &_tmp , where _tmp  is on the stack; therefore, it always stores a pointer to the stack , due to the indirection of   &_tmp .

      • a: any = 123

        • 123  is a literal, not yet stored.

        • _tmp: int = 123 .

        • a  stores a.data = &_tmp , which is a pointer to something on the stack .

      • x: int = 123; a: any = &x

        • &x  is a pointer to x ; the value is stored, but the pointer is not yet stored.

        • _tmp: ^int = &x .

        • a  stores a.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 to 123  on the heap; the value is stored on the heap, but the pointer is not yet stored.

        • _tmp: ^int = new_clone(123) .

        • a  stores a.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 .data  field):

      • 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 for x .

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]

        • x  is a array slice on the stack, that stores a pointer to something on the heap.

        • a  stores a.data = &x[2] , which is a pointer to something on the heap .

        • Even though x  is 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 .data  field):

      • x: ^int = new_clone(123, context.temp_allocator); a: any = { data = x, id = typeid_of(int) }

        • x  stores a pointer to something on the heap.

        • a  stores a pointer to something on the heap.

          • This is not  the same as doing:

            x: ^int = new_clone(123)
            a: any = x              
            
            • a  stores a pointer to x  on the stack, which stores a pointer to something on the heap.

        • Note how id  needs to be int , while x: ^int .

        • When unwrapping the data, we'll get an int , not the original ^int .

        • The original ^int  can 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 ^int  is not retrieved, then the pointer is lost and the memory cannot be freed; to avoid this, this technique should use of arena allocators, such as context.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 _tmp  be 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 .data  field):

      • 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 for x .

About array/slices with any
  • Barinzaya:

    • A slice is a pointer and length, in x := make([]int)   x  would 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 x  is. 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

    • Raw_Slice .

  • Caio:

    • is it possible to do something like x := make([]int); a: any = x.data , so the a.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 any  won'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 delete ing/ free ing 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 as a.(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 .id  id 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 , b  and c  here are always printed the same, while c  has the type of a .

Using switch v in a {}
  • a  is the any  variable.

  • v  is 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 .

  • as_pointer .

    • Attempts to convert an any  to a rawptr .

    • This only works for ^T , [^]T , cstring , cstring16  based types.

    // Various considerations first.
    result = (^rawptr)(any_value.data)^
    
  • as_raw_data .

    • Returns the equivalent of doing raw_data(v)  where v  is a non-any value

    // Various considerations first.
    result = any_value.data
    
  • as_string .

  • as_string16 .

Etc

Is
  • is_nil .

    • Returns true if the any  value is either nil  or the data stored at the address is all zeroed

Etc
  • deref .

    • Dereferences any  if it represents a pointer-based value ( ^T -> T )

  • enum_name_from_value_any .

    • Returns the name of enum field if a valid name using reflection, otherwise returns "", false

  • equal .

    • Checks to see if two any  values are semantically equivalent

  • get_union_variant .

    • Returns the underlying variant value of a union. Panics if a union was not passed.

  • get_union_variant_raw_tag .

    • 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 nil  if not possible

Examples
  • See the example below about typeid s.