Skip to content

Instantly share code, notes, and snippets.

@iliakonnov
Created January 11, 2024 21:53
Show Gist options
  • Save iliakonnov/728e108a94d011329f2550d94ae5dd76 to your computer and use it in GitHub Desktop.
Save iliakonnov/728e108a94d011329f2550d94ae5dd76 to your computer and use it in GitHub Desktop.
package ifaces
import (
"unsafe"
)
// Preparations
// ---------------------
// Basic interface `Iface` and a basic struct `Impl` implementing it
type Iface interface {
Foo(x int) string
}
type Impl struct{}
func (self *Impl) Foo(x int) string {
return ""
}
// Introducing vtables
// ---------------------
// IfaceVtable is a vtable containing pointers to all `Iface` methods.
// Single unique instance of this struct is generated for each type `TSelf` implementing `Iface`.
type IfaceVtable struct {
// For any given method `TSelf.Foo(...args) ...` vtable contains field `Foo` containing `func(*TSelf, ...args) ...`
Foo func(self unsafe.Pointer, x int) string
}
// IfaceWrapper allows to erase any concrete type `TSelf` wrapping it.
type IfaceWrapper struct {
vtable *IfaceVtable // Stores methods, e.g. { Foo(*TSelf, ...), ... }
self unsafe.Pointer // Stores *TSelf so we can call these methods
}
// Foo
// Now interface methods can be called like that. Note that we don't need to have concrete TSelf now.
func (val *IfaceWrapper) Foo(x int) string {
return val.vtable.Foo(val.self, x)
}
// Unsugaring down to plain vtables
// ---------------------------------
// Foo is just a simple function returning an interface:
func Foo() Iface {
impl := Impl{}
return &impl
}
// IfaceVtableForImpl contains a vtable for type `Impl` implementing interface `Iface`.
// It's basically a constant.
var IfaceVtableForImpl = IfaceVtable{
Foo: func(self unsafe.Pointer, x int) string {
// Here we are guaranteed that `IfaceVtableForImpl` will be used only with `Impl` types.
// So we can safely convert untyped `unsafe.Pointer` to typed `*Impl`.
return ((*Impl)(self)).Foo(x)
},
}
// FooUnsugared is a simple function returning an interface, but without any of Go's magic.
func FooUnsugared() IfaceWrapper {
impl := Impl{}
return IfaceWrapper{
vtable: &IfaceVtableForImpl,
self: unsafe.Pointer(&impl),
}
}
// Showing why `Iface((*Impl) nil)` not equal to `nil`
// ---------------------------------------------------
// ReturningNil returns nil value wrapped into Iface
func ReturningNil() Iface {
var impl *Impl = nil
return impl
}
// ReturningNilUnsugared is the same, but unsugared.
// Note that vtable is not nil, because we do have vtable for type Impl implementing Iface.
// But the `self` pointer in wrapper is nil though.
func ReturningNilUnsugared() IfaceWrapper {
var impl *Impl = nil
return IfaceWrapper{
vtable: &IfaceVtableForImpl, // <- not nil !!!
self: unsafe.Pointer(impl), // <- nil
}
}
// RealNil returns IfaceWrapper with all fields set to nil values (what nil does by it's definition)
func RealNil() IfaceWrapper {
return IfaceWrapper{
vtable: nil, // <- nil
self: nil, // <- nil
}
}
// Here we can see that `ReturningNilUnsugared() != RealNil()`, so `ReturningNil() != nil`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment