Go generics let a reusable container preserve the type of the values it manages. The original Witness registry design used that idea for components such as attestors and signer providers.
The code blocks in the October 2023 article were lost during a content migration. The example below is a new, standalone illustration of the pattern, not a reconstruction of those missing blocks or a copy of the current Witness API.
The background
A CLI needs to discover components by name and create an instance of the right kind. A registry of factories can provide that indirection. Making the registry generic lets the compiler distinguish a registry of attestors from a registry of signer providers.
Here is a minimal Go example. It requires Go 1.18 or later and uses only the standard library. Registration happens before use; this example does not implement concurrent mutation or component configuration.
package main
import "fmt"
type Registry[T any] struct {
factories map[string]func() T
}
func NewRegistry[T any]() *Registry[T] {
return &Registry[T]{factories: make(map[string]func() T)}
}
func (r *Registry[T]) Register(name string, factory func() T) error {
if name == "" || factory == nil {
return fmt.Errorf("name and factory are required")
}
if _, exists := r.factories[name]; exists {
return fmt.Errorf("already registered: %s", name)
}
r.factories[name] = factory
return nil
}
func (r *Registry[T]) Create(name string) (T, error) {
factory, ok := r.factories[name]
if !ok {
var zero T
return zero, fmt.Errorf("unknown registration: %s", name)
}
return factory(), nil
}
type Attestor interface{ Name() string }
type Environment struct{}
func (Environment) Name() string { return "environment" }
func main() {
registry := NewRegistry[Attestor]()
if err := registry.Register("environment", func() Attestor { return Environment{} }); err != nil {
panic(err)
}
attestor, err := registry.Create("environment")
if err != nil {
panic(err)
}
fmt.Println(attestor.Name())
if _, err := registry.Create("missing"); err == nil {
panic("unknown registration must fail")
}
}
The program prints environment. It also checks that requesting an unknown component returns an error. The factory explicitly returns the Attestor interface, so every entry in this registry produces that type.
Configuration options
Different component options may have different value types. A generic Option[string] and an Option[bool] remain distinct types; they do not automatically become interchangeable elements of one typed slice. A shared interface can expose the non-generic operations needed by the caller, while each implementation retains its specific value type.
That design involves a tradeoff. If a caller needs the underlying typed value, it must recover or dispatch on the type explicitly. Generics do not remove the need to design the interface.
Signer providers
The same registry structure can hold factories for a signer-provider interface by choosing a different type argument. That reuses the discovery mechanism without allowing a signer provider to be registered as an attestor by accident.
For the actual implementation and its current configuration model, read the go-witness registry source. Pin a module version before building against its API.
Conclusion
This pattern is useful when several kinds of components need the same factory and lookup behavior. Keep the example’s limits explicit: it does not perform attestation, signing, policy verification, or thread-safe registry updates.
See the Go generics tutorial and the Go interface-slice explanation for the language rules. For using Witness rather than extending its internals, start with the Witness documentation.


