Wandering Thoughts archives

2024-12-16

Some notes on "closed interfaces" in Go

One reaction to basic proposals for union types in Go is to note that "closed interfaces" provide a lot of these features (cf). When I saw this I had to refresh myself about what such a closed interface is, and then think about some of the potential issues and limitations involved, leaving me with some things I want to note down for my later reference.

What I've seen called a closed interface is an interface that requires an unexported method:

type Closed interface {
  isClosed()

  NormalMethod1(...) ...
  NormalMethod2(...) ...
  [...]
}

Although it's not spelled out exactly in the Go language specification, an interface with an unexported method like this can't be implemented by any type outside of its package, because such an external type can't have the right 'isClosed()' method on it (since it's not exported, the identifier for the method is unique to the package, or at least I believe that's how the logic flows). The 'isClosed()' method doesn't do anything and need never be called, it just has to be declared as an (empty) method function on everything that is going to be part of the Closed interface.

This means that there is a finite and known list of types that implement the Closed interface, instead of the potentially unknown list of them that could exist for an open interface. Go tooling can use this knowledge to see, for example, that a type switch on Closed is exhaustive, or that a given type assertion will never succeed. However, in the current state of godoc, I don't believe that generated package documentation will automatically tell you which types implement Closed; you'll have to remember to document that explicitly.

(I don't know if there's any Go tooling that does this today, especially when run in the context of other packages instead of the package that defines the Closed interface.)

Code in other packages can still construct a nil Closed interface value, or zero values of any exported types that implement Closed (and then turn such a zero value into a non-nil Closed interface value). If you want to be extra tricky, you can make all the types that implement Closed be unexported; at that point the only way people outside your package can easily create or manipulate those types is through an instance of Closed that you give them, and implicitly only through its methods. The Closed interface is not merely a closed interface, it has become an opaque interface (short of people getting out the chainsaw of reflect and perhaps unsafe).

However, this is also a limitation of the Closed interface, and of closed interfaces in general. For good reason, you can't add methods to types declared outside your package, so you can't make an outside type be a member of the Closed interface, even though you control the interface's definition in your package. In order to induct a useful outside type into the world of Closed, you must wrap it in a type from your package, and this type must be a concrete type, even if what you want to pull in is another interface. I believe that under at least some circumstances, this will cost you some extra memory. More broadly, I don't think you can really have two separate packages that cooperate so each defines some types that are part of Closed. One way or another you have to put all the types in one package.

In my view, this means that a closed interface isn't really useful to document inside your package that this particular interface will only ever contain a limited number of outside types (or a mixture of outside types and inside types), including outside types from a related package. You can use it for this but there's a chunk of bureaucracy involved for each outside type you want to pull in. If you go to this effort, presumably you have tooling that can deduce what's going on and take advantage of this knowledge.

(These days you could define a generic type that wraps another type and implements your Closed interface for it, making this sort of bureaucracy easier, at least.)

programming/GoClosedInterfacesNotes written at 22:43:29;


Page tools: See As Normal.
Search:
Login: Password:

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.