Part II · Types & Data
any / interface{}
The empty interface is Go’s Object: type assertions, type switches, and the nil-interface trap that catches every Java developer once.
Section 08 of 31 4 min read
any is an alias for interface{}, the interface with no methods. Every type satisfies it. It is Go’s escape hatch for "I genuinely don’t know the type at compile time", and it should be rare in application code.
any vs Java Object
Object val = 42; // autoboxed to Integer
val = "now a string";
if (val instanceof String s) { // Java 16+
System.out.println(s.toUpperCase());
}
String t = (String) val; // ClassCastException if wrongvar val any = 42
val = "now a string"
// Comma-ok assertion: never panics
if s, ok := val.(string); ok {
fmt.Println(strings.ToUpper(s))
}
t := val.(string) // panics if val is not a stringType switches
static String describe(Object val) {
return switch (val) {
case String s -> "string: " + s;
case Integer i -> "int: " + i;
case byte[] b -> b.length + " bytes";
case null -> "null";
default -> "unknown: " + val.getClass();
};
}func describe(val any) string {
switch v := val.(type) {
case string:
return "string: " + v // v is a string here
case int:
return fmt.Sprintf("int: %d", v)
case []byte:
return fmt.Sprintf("%d bytes", len(v))
case nil:
return "nil"
default:
return fmt.Sprintf("unknown: %T", v) // %T prints the type
}
}Where any shows up in real code
// Decoding JSON whose shape you do not know
var doc map[string]any
if err := json.Unmarshal(data, &doc); err != nil {
return err
}
// JSON numbers decode as float64, never int!
count, ok := doc["count"].(float64) // ok
// n, ok := doc["count"].(int) // always false
// Nested objects are map[string]any, arrays are []any
tags, _ := doc["tags"].([]any)
// Standard library APIs that take any:
// fmt.Println(a ...any)
// json.Marshal(v any)
// context.WithValue(ctx, key, val any)The nil interface trap
An interface value has two parts: a type and a value. It is only nil when both are nil. Put a nil pointer into an interface and the interface is no longer nil, because it now knows the type. This bites hardest with the error interface.
return nil
return a nil *ValidationError
nil only when both are. Returning a nil *ValidationError as an error fills in the type, so the result is not nil even though nothing failed.type ValidationError struct{ Field string }
func (e *ValidationError) Error() string { return e.Field + " is invalid" }
func validate(name string) error {
var verr *ValidationError // nil pointer
if name == "" {
verr = &ValidationError{Field: "name"}
}
return verr // BUG: returns (type=*ValidationError, value=nil)
}
err := validate("ada")
fmt.Println(err == nil) // false! The interface holds a type.
// FIX: return a literal nil on the success path
func validate(name string) error {
if name == "" {
return &ValidationError{Field: "name"}
}
return nil
}