Reflecting on Weak Pointers: A Proposal for reflect-Based Weak Pointer Creation

With the introduction of weak pointers in Go 1.24, I’ve been exploring their use cases. I’ve found a situation where I need to create and manage weak pointers dynamically, based on types determined at runtime – essentially, I need reflection capabilities for weak pointers.

The current weak.Pointer is generic, and as far as I can tell, Go doesn’t currently support calling generic functions via reflection. This makes it impossible to create a weak.Pointer[T] when T is only known as a reflect.Type.

I’ve looked through the issue tracker and the golang-nuts mailing list, and I haven’t found any existing discussions directly addressing this. Before I submit a formal proposal on GitHub, I wanted to gauge the community’s opinion on whether adding reflection support for weak pointers would be a valuable addition to the language.

Here’s a rough sketch of what I envision the API might look like (using a hypothetical weak.PointerAny):
package weak // or perhaps a new package like reflect/weak

type PointerAny struct {
	typ reflect.Type
	u   unsafe.Pointer
}

func MakeAny(ptr any) PointerAny {
	ptr = abi.Escape(ptr)
	v := reflect.ValueOf(ptr)
	t := v.Type()
	if t.Kind() != reflect.Pointer {
		panic("ptr is not a pointer")
	}
	var u unsafe.Pointer
	if ptr != nil {
		u = runtime_registerWeakPointer(v.UnsafePointer())
	}
	runtime.KeepAlive(ptr)
	return PointerAny{typ: t.Elem(), u: u}
}

func (p PointerAny) Value() any {
	if p.u == nil {
		return nil
	}
	ptr := runtime_makeStrongFromWeak(p.u)
	if ptr == nil {
		return nil
	}
	return reflect.NewAt(p.typ, ptr).Interface()
}

This PointerAny would allow creating a weak pointer from an any (which must be a pointer) and retrieving the original value as an any.

Is there a compelling reason not to add this? Would adding reflection support for weak pointers introduce significant complexity or have unintended consequences? Perhaps there are fundamental reasons why this hasn’t been implemented already.

I’m eager to hear your thoughts. Thanks!

IMHO, weak pointer does not need to know the exact type of the value. Reflect is always very costly and I doubt it’s a common use. You can always simply add your own wrapper around weak pointer with its type:

type MyPointer struct {
    t reflect.Type
    p weak.Pointer
}

That wrapper doesn’t solve the generic type issue for reflection. I still get the cannot use generic type weak.Pointer[T any] without instantiation error.

Yeah, because reflection and generics operate in different fields. One is for comptime another is for runtime. You can also try to create pointer to any value and pass it to weak pointer creation, something like:

func Pointer[T any](val T) *T {
	v := reflect.ValueOf(val)
	if !v.CanAddr() {
		v = reflect.ValueOf(&val)
	}

	ptr, ok := v.Interface().(*T)
	if !ok {
		panic("")
	}

	return ptr
}

I need to call a Make generic function to create weak pointers when I only have a reflect.Value, which isn’t possible with the current code. It might look something like this:

func MakeAny(ptr any) PointerAny

If you mean bypassing the type like this, it will cause an internal error.

func MakeAny(ptr any) weak.Pointer[struct{}] {
	return weak.Make((*struct{})(reflect.ValueOf(ptr).UnsafePointer()))
}

You need a value to be a pointer to infer its type. And I meant something like this:

package main

import (
	"fmt"
	"reflect"
	"weak"
)

func Pointer[T any](val T) *T {
	v := reflect.ValueOf(val)
	if !v.CanAddr() {
		v = reflect.ValueOf(&val)
	}

	ptr, ok := v.Interface().(*T)
	if !ok {
		panic("")
	}

	return ptr
}

func main() {
	i := 5
	p := weak.Make(Pointer(i))
	fmt.Println(*p.Value()) // Output: 5
}

I need to use weak pointers in this scenario.

package main

import (
    "fmt"
    "reflect"
)

type WeaKPointer struct{}

func MakeAny(ptr any) WeaKPointer {
    panic("not implemented")
}

func (p WeaKPointer) Value() any {
    panic("not implemented")
}

var instances = make(map[string]WeaKPointer)

// ExampleFunc creates a weak pointer for all pointer types in the struct and saves them to the global variable instances.
// This function is called by others in a library, and we cannot know the type of T in advance.
func ExampleFunc[T any](val T) {
    rfType := reflect.TypeOf(val)
    rfValue := reflect.ValueOf(val)
    if rfType.Kind() != reflect.Struct {
        panic("T must be a struct")
    }
    for i := 0; i < rfType.NumField(); i++ {
        typeField := rfType.Field(i)
        valueField := rfValue.Field(i)
        if typeField.Type.Kind() == reflect.Pointer {
            instances[typeField.Name] = MakeAny(valueField.Interface())
        }
    }
}

type Struct1 struct {
    Field1 *int
    Field2 *string
}

func main() {
    s1 := Struct1{
        Field1: new(int),
        Field2: new(string),
    }
    ExampleFunc(s1)
    fmt.Println(instances)
}
package main

import (
	"fmt"
	"reflect"
	"weak"
)

var instances = make(map[string]weak.Pointer[reflect.Value], 0)

// ExampleFunc creates a weak pointer for all pointer types in the struct and saves them to the global variable instances.
// This function is called by others in a library, and we cannot know the type of T in advance.
func ExampleFunc[T any](val T) {
	rfType := reflect.TypeOf(val)
	rfValue := reflect.ValueOf(val)
	if rfType.Kind() != reflect.Struct {
		panic("T must be a struct")
	}

	for i := 0; i < rfType.NumField(); i++ {
		typeField := rfType.Field(i)
		valueField := rfValue.Field(i)
		if typeField.Type.Kind() == reflect.Pointer {
			instances[typeField.Name] = weak.Make(&valueField)
		}
	}
}

type Struct1 struct {
	Field1 *int
	Field2 *string
}

func main() {
	s1 := Struct1{
		Field1: new(int),
		Field2: new(string),
	}
	*s1.Field1 = 5
	*s1.Field2 = "test"
	ExampleFunc(s1)
	for n, v := range instances {
		fmt.Println(n, v.Value())
	}
}

Here, Make only creates weak pointers to reflect.Value, not weak pointers to the type itself.

package main

import (
	"fmt"
	"reflect"
	"runtime"
	"weak"
)

var instances = make(map[string]weak.Pointer[reflect.Value], 0)

// ExampleFunc creates a weak pointer for all pointer types in the struct and saves them to the global variable instances.
// This function is called by others in a library, and we cannot know the type of T in advance.
func ExampleFunc[T any](val T) {
	rfType := reflect.TypeOf(val)
	rfValue := reflect.ValueOf(val)
	if rfType.Kind() != reflect.Struct {
		panic("T must be a struct")
	}

	for i := 0; i < rfType.NumField(); i++ {
		typeField := rfType.Field(i)
		valueField := rfValue.Field(i)
		if typeField.Type.Kind() == reflect.Pointer {
			instances[typeField.Name] = weak.Make(&valueField)
		}
	}
}

type Struct1 struct {
	Field1 *int
	Field2 *string
}

func main() {

	s1 := Struct1{
		Field1: new(int),
		Field2: new(string),
	}
	*s1.Field1 = 5
	*s1.Field2 = "test"
	ExampleFunc(s1)

	runtime.GC()

	for n, v := range instances {
		fmt.Println(n, v.Value())
		if v.Value() == nil {
			panic(fmt.Sprintf("%v should not be nil", n))
		}
	}

	runtime.KeepAlive(s1)
	*s1.Field1 = 10
}

Now you are making it even more confusing. I just fixed the code you’d sent. Coming back to your first desire to store type and value from reflection you need to create your own container for this purpose. Create weak pointer to it. And do your own wrappers how to process its kind and value. But the biggest issue here, IMHO that you clearly do not understand well how generics work.