Apologies if this or something similar has already been proposed, I searched and couldn’t find anything but the keywords to search for are a bit ambiguous.
I have many times wished that Swift would let me do something that is possible in C-based languages, basically enabled by the fact that declarations and definitions can be separated. I can declare something like a function or class in a header, import it into libraries and use it. That library is then coupled to an interface, but not to any implementation. However, there is no runtime polymorphism. Eventually the symbols will be resolved statically (at link time), and it’s proven that exactly one implementation is included (none = “missing symbols” error, multiple = “duplicate symbols” error).
For example I can declare a log function in a header and include it everywhere, even in libraries that get built externally, then when I build my app, I can pick among multiple implementations of that function, which allows me to swap out a logging system at build time, but still know that one and only one log system is used everywhere (no “configure” or “setup” code at startup is necessary).
You can hack this kind of behavior into Swift by building two statically linked frameworks with identical module names, but the build system doesn’t reliably detect <1 or >1 linked frameworks, and you have to confusingly say that other frameworks link to one of these implementing frameworks, when really they don’t link to a particular implementation, just the static interface that framework implements. Plus this totally doesn’t work with SPM.
I’ve thought about what a proper, modern, Swifty solution to this problem would be, and concluded that it is “Module Polymorphism”.
Basically we define the concept of a “Module Interface” (possibly bad name since that also refers to other unrelated build artifacts), which is like a protocol but on a module level. You declare global functions with no body, global vars with { get }
or { get set }
, global associated types instead of type aliases, and concrete types (structs, enums, classes, actors) that declare but do not define their members (they’re concrete but their bodies look like protocols).
Then in package manifests you can make one module depend on another module interface, and declare that one module implements a module interface. When an executable in built, it should prove that every required module interface is implemented by exactly one included module. You can vary behavior, but the variation is at build time, which avoids both the performance and semantic noise of dealing with runtime variation that really doesn’t exist but there’s no way today to express that it doesn’t exist.
I can go into more detail about how this could be useful and what exactly it would look like, but first I wanted to see if anyone has had similar ideas, or what people think of this on a high level. Is it a useful feature? Is this the Swifty way to do it?