# Go Generics in Witness

Source: https://www.testifysec.com/blog/generics-in-witness/

Author: TestifySec

Published: 2023-10-18

Updated: 2026-10-06

How we implemented Go generics in the Witness project for better type safety.

Editorial update · October 6, 2026

The original code blocks were lost during migration. TestifySec Editorial added a new standalone example and corrected the type explanation. The publication date and URL are preserved; this is not a current Witness API tutorial.

- [Current TestifySec docs →](https://www.testifysec.com/docs)
- [Trust architecture →](https://www.testifysec.com/docs/cilock/trust)

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](https://github.com/in-toto/go-witness/tree/main/registry). 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](https://go.dev/doc/tutorial/generics) and the [Go interface-slice explanation](https://go.dev/wiki/InterfaceSlice) for the language rules. For using Witness rather than extending its internals, start with the [Witness documentation](https://witness.dev/).
