How do you "seal" an interface so other packages cannot implement it? Can the seal be bypassed?
Add an unexported method to the interface. Method names that are not exported are qualified by their package, so a type in another package cannot declare a method with that identity. Only types in the defining package can satisfy the interface.
package shape
type Shape interface {
Area() float64
sealed() // unexported: only package shape can implement it
}
type Circle struct{ R float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (Circle) sealed() {}
// ---- package main ----
type Mine struct{}
func (Mine) Area() float64 { return 0 }
func (Mine) sealed() {}
// var _ shape.Shape = Mine{} // error: Mine does not implement shape.Shape
// // (missing method sealed)
// Loophole: embedding promotes the unexported method
type Sneaky struct{ shape.Circle }
func (Sneaky) Area() float64 { return -1 }
var _ shape.Shape = Sneaky{} // compiles
The standard library uses this: testing.TB has a private() method so that new methods can be added to it without breaking anyone, and the go/ast node interfaces (Expr, Stmt, Decl) use exprNode(), stmtNode() and declNode() markers.
Uses: closed sum types (you can then write an exhaustive type switch, checked by linters such as exhaustive/go-sumtype), and interfaces you want to evolve by adding methods. The seal is a convention, not a security boundary: embedding an implementing type promotes the unexported method, as Sneaky shows.
More on Interfaces, Methods & Embedding
- Q108Can you modify a struct stored inside an interface? What does this print?
- Q109How do you write a generic function that needs to call a pointer-receiver method on
T? (thePT interface{ *T; M() }pattern) - Q111How do you check at runtime whether a type implements an interface using reflection? Why is
reflect.TypeOfawkward for interface types?