[Pre-Pitch] SwiftAwake()

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.

4 Likes

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.

-1 as proposed.

I don't like the idea of undefined order. It leads to possibility of a program that breaks every Thursday. :/

2 Likes

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).

2 Likes

What would you recommend for a defined ordering? I was just copying +initialize, so I am not tied to the order being undefined.

Maybe alphabetical, based on the name of the type and the framework? Arbitrary, but consistent is much better than undefined

1 Like

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.

4 Likes

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.

STRAW_MAN_TYPE<Bool>.awake {
  // do whatever
}
2 Likes

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.

10 Likes

Actually, being able to get a list of all types conforming to a protocol would solve all the use cases I can think of for this...

1 Like

-1 I don't think this is suitable for built-in functionality. I don't think it fits in with the natural order or style of the language.

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.

It seems you've had this idea before, so it must be good:

And here's another related discussion that seems to come to the same conclusion:

3 Likes

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)

4 Likes

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.

6 Likes

I tend to agree and I very much want this.

3 Likes

C# has static constructors. Slightly different, but they also get executed first time class is invoked in any way.

3 Likes

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()
}
2 Likes

It's still possible, but nobody has done anything about it.

Ok. What are the outstanding parts of the design which would need to be figured out in order to make a formal proposal?

Terms of Service

Privacy Policy

Cookie Policy