I’m using Go 1.22+ weak pointers to implement a read-only iterator that holds a weak reference to a file system. The issue is that my file system is stored as an interface field, and that interface embeds another interface.
The Problem:
I have:
type FileStorageGargoyle struct {
mu sync.RWMutex
fs gargoyle.FileSystem // interface type
}
type FileStorageGargoyleReadFileIterator struct {
fs weak.Pointer[gargoyle.Snapshot] // want weak ptr to embedded interface
}
func (s *FileStorageGargoyle) ReadFile(fileId string (io.ReadSeekCloser, error) {
s.mu.RLock()
defer s.mu.RUnlock()
// This doesn't work - type mismatch:
// fs: weak.Make(&s.fs), // s.fs is FileSystem, not Snapshot
}
Where gargoyle.FileSystem embeds gargoyle.Snapshot:
type FileSystem interface {
Snapshot // embedded
CreateFile(id string) error
// ... other methods
}
What I Tried:
Direct assignment: weak.Make(&s.fs) — compilation error, type mismatch
This works because FileSystem embeds Snapshot, so the memory layout is compatible. The unsafe.Pointer cast lets me point directly to the s.fs field in the struct, which persists for the object’s lifetime.
My Questions:
Is this the intended way to handle weak pointers to embedded interface fields?
Are there safer alternatives that don’t involve unsafe?
Should the weak package documentation cover this use case?
An embedded struct field is just like a normal struct field with some added syntax sugar. gargoyle.FileSystem has a field named “Snapshot” which is of type gargoyle.Snapshot. If you want a reference to this field, you should make a weak pointer to that field: weak.Make(&s.fs.Snapshot)
Oh I just realized you were working with embedded interfaces and not embedded structs. So this should even be less of a problem, since interfaces are just contracts. Your pointer is always to a struct and you can cast it to any interface it implements at runtime or compile time.
You have found one way using generics, but you should not even need them, because weak.Pointer is already generic. You can just cast your interface to the type you need, or just provide an explicit type parameter when instanciating your weak pointer like `var p weak.Pointer[Base]`
I think what you’re describing are my first attempts..and all failed.
The point is that i’m implementing an iterator that should work on all types that implement “Base” interface. The point is that being an iterator should not “own” the datastructure its iterating (this is a requirement on my implementation). The point is that the expression weak.Make(&k) create a weak.Pointer[K] where K is the type of &k. The point is there’s not implicit conversion from weak.Pointer[K] to another weak.Pointer[T] (even if K embeds T).
I think using generics solves my problem.. with that said thank you for you help!
You are right. I didn’t understand at first that your want a generic iterator that accepts any interface value which might satisfy your Base interface. Since an interface value is effectively a typed strong pointer, you are passing a pointer to a strong pointer to weak.Make, effectively creating a weak pointer to a strong typed pointer (an interface value).
In this case generics are the only way, since you have to accept different types (could be a struct or an interface value) anything which can fulfill the interface Base. So your iterator will be a generic type accepting anything which extends Base, which will allow you to call the method after unwrapping the value from the weak Pointer.
By the way: I think your idea with using unsafe.Pointer is actually undefined behavior and may break. Since an interface value is actually a combination of a pointer to the underlying struct combined with an itab (a table to jump to the function calls) just casting it to a different type will expect a different itab and they may be completely different.