Use a type assertion or a type switch to check if a value implements an interface at runtime. If you need to know the result without panicking, use the two-value form of a type assertion; if you need to handle multiple possible types, a type switch is cleaner.
Here is the two-value type assertion pattern, which is the standard way to check for a specific interface without causing a panic:
func checkInterface(v any) {
// Check if v implements fmt.Stringer
if s, ok := v.(fmt.Stringer); ok {
fmt.Println("Implements Stringer:", s.String())
} else {
fmt.Println("Does not implement Stringer")
}
}
If you need to distinguish between several interfaces or concrete types, a type switch is more idiomatic and readable:
func processValue(v any) {
switch val := v.(type) {
case fmt.Stringer:
fmt.Println("Stringer:", val.String())
case io.Reader:
fmt.Println("Reader detected")
case int:
fmt.Println("Integer:", val)
default:
fmt.Println("Unknown type")
}
}
For static analysis (compile-time checks), you can use the interface{} type in a function signature or assign the value to a variable of the interface type. If the type doesn't implement the interface, the code will fail to compile, which is often preferable to runtime checks.
// Compile-time check: this will error if MyType doesn't implement fmt.Stringer
func requireStringer(s fmt.Stringer) {
fmt.Println(s.String())
}
// Usage:
// requireStringer(myValue) // Fails to compile if myValue lacks String() method
Note that checking if a value implements an interface is a runtime operation unless you rely on the compiler to enforce it. The two-value assertion (val, ok := v.(T)) is safe and returns false if the type doesn't match, whereas the single-value form (val := v.(T)) will panic if the assertion fails. Always prefer the two-value form when the type is uncertain.