I'd like to propose adding a special (optional-to-impliment) static function called swiftAwake() to all structs, enums, and classes.
The compiler would then build a free function _callSwiftAwake() that just calls swiftAwake() on every entity that defines it. This would then be called first thing in main. The order in which the various swiftAwake functions are called would be undefined.
This would make things which need to register themselves (e.g. plug-ins) much easier to create and maintain, because they can just register themselves by implementing swiftAwake() instead of having to hand build a function that registers everything (which is a common source of bugs in my experience).
struct MyStruct {
static func swiftAwake () {
///This automatically gets called first thing and gives the type a
///chance to register itself or do any other setup work
}
}
Update
From the discussion, it looks like a better solution is to create a way to be able to ask for all conformers of specially marked protocols.
I‘m missing some context but to me this feels like a some user implementation details moved up into the compiler which is hacky. So far I‘m not convinced by this idea.
I feel exactly the opposite, but I don't think I have explained myself well. It is basically a hook for when you need to do something on initialization (think +initialize from ObjC). It isn't something you need all the time, but when you do, there are currently no good options for non-NSObjects in Swift without resorting to hacky imitations of what should be built-in behavior.
That is: the user is forced to create and maintain a list of Types (and then call a static function on those types), which can easily get out of sync. That is something the compiler could easily do... which would also allow dynamic things like auto-registering plug-ins, which are currently not possible in pure Swift (w/o ObjC).
I've personally been replacing this with something like:
struct Foo {
private static let _initialize: Void = {
// Do some initialization here.
}()
init() {
Foo._innitialize
}
}
As the _initialize property gets computed exactly once, all you have to do is call it in the Foo.init() initializer. The basic downsides of this:
Will not be called when you call other static methods on the struct/class (+initialize in ObjC is called when the class is first used altogether, even before +alloc). Unless you manually call self._initialize, of course.
If you are using struct's generated initializer, you will need to write it manually.
With class hierarchy, this is not exactly overridable, but the cases where it needs to be are IMHO very rare (you can still create a static variable like this in each class and override the initializers and call it).
I believe Apple is strongly against anything that gets called automatically at launch as is forces the linker to load the symbols immediately (vs. lazy loading), etc. There are several WWDC talks on this addressing launch-time.
Well I‘m not trying to demotivate you by any means. However I think the proposed design is a potential oversimplification which makes it hard to see the bigger picture here.
There are tons of questions that are on my mind:
What happens if the static method called multiple times?
On which thread should it be called, do we want to provide a way for alteration?
What happens across module boundaries?
Do you expect stdlib types to have such a method?
How would the automatic synthetized call handle generic types that implements it?
What about generic recursion?
etc.
Why wouldn‘t you want some kind of registration type provided by the stdlib which would accept a closure for a given type. Something like MemoryLayout.
Another question here is if there are use cases that wouldn't be better served by improved reflection capabilities, e.g. to find all types conforming to a protocol.
This seems oddly specific to a certain problem you're having where a different solution might be a better fit. I agree with the concerns @DevAndArtist shared and also vote a -1 on this.
And I think it was a similar type of problem that led to it again...
Reading the threads you linked, it looks like the possible solution was to have an annotation on the protocol that causes the compiler to keep a list of all conformers, that can then be asked for in some way. Maybe CaseIterable is a good example to follow here. Instead of an annotation, it could be a special inheritance that generates a static var on the protocol itself:
protocol MyProtocol : ConformerIterable { ///Need a better name
///Autogenerates an `allConformers` static var
static func foo()
}
for conformingType in MyProtocol.allConformers {
///Call a method from the protocol on the conformer
conformingType.foo()
}
Note: I am sure there is a better name. We should pick something that could work for subclasses too.
@John_McCall: Has there been any progress on this since the discussion in 2016? Is it more or less possible now (especially considering ABI stability)
For me personally, these are the kind of code patterns I would try to avoid. I've come to favour explicit initialisation in a single location in main.swift over spreading this kind of information over multiple files (this is one of the reasons why I don't like e.g. Spring Boot - initialisation logic is just all over the place).
That said, you can easily create functionality like this yourself by using code generation, e.g. sourcery.
Having a module initializer is akin to +load methods in ObjC which have important performance issues when loading frameworks with large dependency graphs. I wouldn’t be surprised if the Swift team is actively trying to avoid the can of worms that is running arbitrary code on load.
I’m a strong +1 on the ConformerIterable idea. I also find myself needing this fairly often.
There is the issue of Protocol witnesses only working for instances and not class methods so maybe it must be defined as:
protocol ConformerIterable {
init()
}
// floating function to make it clear we can’t override it
// compiler generated overload per protocol
func createdInstancesOfTypes<T: ConformerIterable>(conformingTo: T.Type) -> LazySequence<T> { ... }
// cannot have Self requirements or associatedtypes
protocol Command: ConformerIterable {
func foo()
}
for command in createdInstancesOfTypes(conformingTo: Command.self) {
command.foo()
}
Well, if we assume that there's an attribute you put on a protocol that makes its conformances dynamically enumerable, I can see a few basic design questions:
What's the name of the attribute?
How do you enumerate the conformances of a protocol in code?
Are there ordering guarantees in that enumeration?
What happens to generic types that conform to the protocol?
Does the enumeration include subclasses of classes that conform to the protocol?
Does the enumeration include non-public types?
Is there a way to opt out of the enumeration?
On systems that allow code to be dynamically loaded, should there be some way to get notified of the existence of future types that conform to the protocol?