Global init() functions


(Alan Cabrera) #1

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

Regards,
Alan


(Jean-Daniel) #2

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

···

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.


(Alan Cabrera) #3

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

···

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.


(John McCall) #4

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

John.

···

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.


(Alan Cabrera) #5

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code. The problem has always existed. I think the language should render some assistance.

Regards,
Alan

···

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.


(John McCall) #6

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

John.

···

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.


(Alan Cabrera) #7

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

Regards,
Alan

···

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com <mailto:adc@toolazydogs.com>> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.


(John McCall) #8

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P. This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.

In general, this kind of straightforward compilation task — "compilation" in the general sense of gathering like information from different sources — is pretty easy to support and much less problematic than features that rely on arbitrary code execution.

John.

···

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com <mailto:adc@toolazydogs.com>> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?


(David Waite) #9

In the past, I’ve seen issues with these sorts of module initialization functions:

- They promote a high degree of coupling between a module and its dependencies. Given an example of a test framework, you’ll see tests which support a single implementation of a test runner, with a globally registered/unorganized bundle of tests.
- Without an initialization order, you can be pretty limited in your ability to depend on other parts of the system. This leads your startup routine to be rather static.
- Likewise, you may want code to be run at some point other than initialization time, or you may depend on other components being initialized first. You might be able to depend on the startup routines of modules you depend on being initialized by nature of the system being a DAG, but peer modules may or may not run before you
- Finally, these systems often wind up depending on global state, which is generally a design anti-pattern. You typically want to supply the dependencies to a module (the D in SOLID)

I would prefer a more generalized version of @NSApplicationMain / @UIApplicationMain that supported possibly multiple implementers of a user protocol. Then, implementations of the protocol could be called when desired, with appropriate state, based on the logic of the framework defining said protocol.

-DW

···

On Nov 19, 2016, at 11:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Alan Cabrera) #10

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P.

Great, are there any keywords I can use to search for this thread?

This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.

Does the other thread go into detail about this? I’m not sure that I follow why it should be opt-in as opposed to simply searching for all implementations of a specific protocol. Is it because the protocol information is erased after compilation?

In general, this kind of straightforward compilation task — "compilation" in the general sense of gathering like information from different sources — is pretty easy to support and much less problematic than features that rely on arbitrary code execution.

Agreed, it solves the bulk of the problems I would have solved with global init functions without the temptations to indulge in dangerous proclivities.

Regards,
Alan

···

On Nov 19, 2016, at 8:57 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com <mailto:adc@toolazydogs.com>> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com <mailto:adc@toolazydogs.com>> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :


(Joe Groff) #11

I worry that there are registration use cases for which "get all protocol conformers" is a bit boilerplatey. For example, collecting test case functions is a classic use case, and requiring a separate type declaration for every case would be a bit heavyweight compared to just having each test be a free function.

-Joe

···

On Nov 19, 2016, at 8:57 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P. This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.


(Jay) #12

Alan,

The other thread subject is "Getting a list of protocol conformers"

···

On Sun, 20 Nov 2016 at 14:40 Alan Cabrera via swift-evolution < swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution < > swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution < > swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly
way of doing things. It would be pretty handy to be able to declare init()
functions in my module to register handlers. It’s a common pattern in
enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the
behavior. I think it would be cleaner to have these global init()
functions.

I’d rather like a swift attribute equivalent to : __attribute__((
constructor))

It will not force me to call my initializer init, and moreover it will let
me declare multiple functions so I would be able to register multiples
handlers from a single module without having to group all the register call
into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it
looks like an LLVM implementation bit. Do you mean defining a new Swift
declaration attribute named “constructor”? If so, I *really* like that
idea. I think that the specific attribute name “constructor” may be a bit
confusing though, since it’s not really constructing anything specific.
Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling
of the startup functions, thus reinforcing/focusing the startup functions’
role as global startup functions. Maybe global teardown functions would be
helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global
initialization is superficially attractive — what could be simpler than
just running some code at program launch? — but as a program scales up and
gains library dependencies, it very quickly runs into problems. What if an
initializer depends on another already having been run? What if an
initializer needs to be sensitive to the arguments or environment? What if
an initializer need to spawn a thread? What if an initializer needs to do
I/O? What if an initializer fails? Global initialization also has a lot
of the same engineering drawbacks as global state, in that once you've
introduced a dependency on it, it's extremely hard to root that out because
entire APIs get built around the assumption that there's no need for an
explicit initialization/configuration/whatever step. And it's also quite
bad for launch performance — perhaps not important for a server, but
important for pretty much every other kind of program — since every
subsystem eagerly initializes itself whether it's going to be used or not,
and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute
at program startup? It cannot be denied that the pattern is used and is
extremely useful though, as you point out above, it should be used
carefully. Thinking on it, there are always pros and cons to most language
features and one relies on best practices to avoid shooting oneself in the
foot. For each of the specters listed above, there are simple accepted
practices that can be adopted to avoid them; most of those practices are
already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex
applications’ app-delegate did-finish-launching methods are chucked full of
hand stitched roll calls to framework initialization code. This needlessly
places a brittle dependency/burden on the application developer in what
should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point
to CocoaPods, Carthage, and the other influx of enterprise influenced
tooling and frameworks. Today’s mobile applications are no longer simply
todo apps.

Global init() functions are a clean solution to what engineers are
*already* boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a
solution you're used to using, but they're not a *clean* solution, and
Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex
applications as some sort of argument for them, because those are exactly
the applications where, in my experience, global initializers completely
break down as a reasonable approach. It's the small applications that can
get away with poor software engineering practices, because the accumulated
maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain
specters that can still afflict applications without global init
functions. Any feature can be abused and it seems hyperbolic to provide
arguments that seems to ascribe the above problems as an *inevitability* solely
afflicting global init functions. My and others’ experience with them has
been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon
nap. I really like the idea of some kind of reflective discovery. How
would that work? Maybe having a special @tag attribute that can be
searched at runtime?

There was another thread that mentioned this idea recently, but it would
be reasonable to provide some way to get a P.Type for every type in the
program that conforms to a protocol P.

Great, are there any keywords I can use to search for this thread?

This would be opt-in at the protocol level, because we wouldn't want to be
prevented from e.g. stripping an unused type from the program just because
it implemented Equatable. There are some other complexities here, but
that's the basic idea, and it's totally reasonable to support.

Does the other thread go into detail about this? I’m not sure that I
follow why it should be opt-in as opposed to simply searching for all
implementations of a specific protocol. Is it because the protocol
information is erased after compilation?

In general, this kind of straightforward compilation task — "compilation"
in the general sense of gathering like information from different sources —
is pretty easy to support and much less problematic than features that rely
on arbitrary code execution.

Agreed, it solves the bulk of the problems I would have solved with global
init functions without the temptations to indulge in dangerous proclivities.

Regards,
Alan

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(John McCall) #13

We would want this to be opt-in on the protocol because it would inhibit removing a conforming type from the program. The compiler can ordinarily remove types that you never directly use, and that's an important optimization when e.g. linking in large libraries that you only use a small part of. We wouldn't want to lose the ability to do that just because a type implemented Equatable or Collection, because a lot of types implement those protocols, and the ability to iterate all those types is probably not very useful — certainly it isn't useful enough to justify losing that optimization. In contrast, when there's a protocol that is useful to iterate over all the conformances of, like a Deserializable protocol that registers types with a deserialization engine, you always certainly know that when you're defining the protocol.

John.

···

On Nov 20, 2016, at 6:40 AM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com <mailto:adc@toolazydogs.com>> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com <mailto:adc@toolazydogs.com>> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com <mailto:dev@xenonium.com>> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P.

Great, are there any keywords I can use to search for this thread?

This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.

Does the other thread go into detail about this? I’m not sure that I follow why it should be opt-in as opposed to simply searching for all implementations of a specific protocol.


(Dave Abrahams) #14

Yeah, well you could make them methods of a conforming type, but then
you still want a general introspection method so you can crawl the
type's declarations and find them.

···

on Mon Nov 28 2016, Joe Groff <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is
a Swift-ly way of doing things. It would be pretty handy to
be able to declare init() functions in my module to register
handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover
it will let me declare multiple functions so I would be able
to register multiples handlers from a single module without
having to group all the register call into a single init()
function.

I’m not quite following what “__attribute__((constructor))”
means; it looks like an LLVM implementation bit. Do you mean
defining a new Swift declaration attribute named “constructor”?
If so, I really like that idea. I think that the specific
attribute name “constructor” may be a bit confusing though,
since it’s not really constructing anything specific. Maybe
“startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent
direct calling of the startup functions, thus
reinforcing/focusing the startup functions’ role as global
startup functions. Maybe global teardown functions would be
helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think.
Eager global initialization is superficially attractive — what
could be simpler than just running some code at program launch?
— but as a program scales up and gains library dependencies, it
very quickly runs into problems. What if an initializer depends
on another already having been run? What if an initializer
needs to be sensitive to the arguments or environment? What if
an initializer need to spawn a thread? What if an initializer
needs to do I/O? What if an initializer fails? Global
initialization also has a lot of the same engineering drawbacks
as global state, in that once you've introduced a dependency on
it, it's extremely hard to root that out because entire APIs get
built around the assumption that there's no need for an explicit
initialization/configuration/whatever step. And it's also quite
bad for launch performance — perhaps not important for a server,
but important for pretty much every other kind of program —
since every subsystem eagerly initializes itself whether it's
going to be used or not, and that initialization generally has
terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still
execute at program startup? It cannot be denied that the pattern
is used and is extremely useful though, as you point out above,
it should be used carefully. Thinking on it, there are always
pros and cons to most language features and one relies on best
practices to avoid shooting oneself in the foot. For each of the
specters listed above, there are simple accepted practices that
can be adopted to avoid them; most of those practices are already
being employed for other situations.

And the pattern is not just useful in enterprise software.
Complex applications’ app-delegate did-finish-launching methods
are chucked full of hand stitched roll calls to framework
initialization code. This needlessly places a brittle
dependency/burden on the application developer in what should be
a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I
would point to CocoaPods, Carthage, and the other influx of
enterprise influenced tooling and frameworks. Today’s mobile
applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They
may be a solution you're used to using, but they're not a *clean*
solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex
applications as some sort of argument for them, because those are
exactly the applications where, in my experience, global
initializers completely break down as a reasonable approach. It's
the small applications that can get away with poor software
engineering practices, because the accumulated
maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that
contain specters that can still afflict applications without global
init functions. Any feature can be abused and it seems hyperbolic
to provide arguments that seems to ascribe the above problems as an
inevitability solely afflicting global init functions. My and
others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my
afternoon nap. I really like the idea of some kind of reflective
discovery. How would that work? Maybe having a special @tag
attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it
would be reasonable to provide some way to get a P.Type for every
type in the program that conforms to a protocol P. This would be
opt-in at the protocol level, because we wouldn't want to be
prevented from e.g. stripping an unused type from the program just
because it implemented Equatable. There are some other complexities
here, but that's the basic idea, and it's totally reasonable to
support.

I worry that there are registration use cases for which "get all
protocol conformers" is a bit boilerplatey. For example, collecting
test case functions is a classic use case, and requiring a separate
type declaration for every case would be a bit heavyweight compared to
just having each test be a free function.

--
-Dave


(John McCall) #15

I agree with you, but I'm not sure that global initializers fit this use-case well either.

John.

···

On Nov 28, 2016, at 11:53 AM, Joe Groff <jgroff@apple.com> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P. This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.

I worry that there are registration use cases for which "get all protocol conformers" is a bit boilerplatey. For example, collecting test case functions is a classic use case, and requiring a separate type declaration for every case would be a bit heavyweight compared to just having each test be a free function.


(Jay) #16

Why not just say that this doesn't affect the removal of types (i.e. they
can still be discarded) and add something to prevent specific types being
discarded even if they're not statically used?

@nodiscard SomeType

This way, rather than a protocol opting in that anything implementing it is
automatically `@nodiscard` a program or library would declare some types as
non-discardable and could safely say "there will be at least 1
SomeProtocol" available" without saying what specific type it is in the
public interface/docs?

···

On Mon, 21 Nov 2016 at 22:50 John McCall via swift-evolution < swift-evolution@swift.org> wrote:

On Nov 20, 2016, at 6:40 AM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution < > swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution < > swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly
way of doing things. It would be pretty handy to be able to declare init()
functions in my module to register handlers. It’s a common pattern in
enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the
behavior. I think it would be cleaner to have these global init()
functions.

I’d rather like a swift attribute equivalent to : __attribute__((
constructor))

It will not force me to call my initializer init, and moreover it will let
me declare multiple functions so I would be able to register multiples
handlers from a single module without having to group all the register call
into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it
looks like an LLVM implementation bit. Do you mean defining a new Swift
declaration attribute named “constructor”? If so, I *really* like that
idea. I think that the specific attribute name “constructor” may be a bit
confusing though, since it’s not really constructing anything specific.
Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling
of the startup functions, thus reinforcing/focusing the startup functions’
role as global startup functions. Maybe global teardown functions would be
helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global
initialization is superficially attractive — what could be simpler than
just running some code at program launch? — but as a program scales up and
gains library dependencies, it very quickly runs into problems. What if an
initializer depends on another already having been run? What if an
initializer needs to be sensitive to the arguments or environment? What if
an initializer need to spawn a thread? What if an initializer needs to do
I/O? What if an initializer fails? Global initialization also has a lot
of the same engineering drawbacks as global state, in that once you've
introduced a dependency on it, it's extremely hard to root that out because
entire APIs get built around the assumption that there's no need for an
explicit initialization/configuration/whatever step. And it's also quite
bad for launch performance — perhaps not important for a server, but
important for pretty much every other kind of program — since every
subsystem eagerly initializes itself whether it's going to be used or not,
and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute
at program startup? It cannot be denied that the pattern is used and is
extremely useful though, as you point out above, it should be used
carefully. Thinking on it, there are always pros and cons to most language
features and one relies on best practices to avoid shooting oneself in the
foot. For each of the specters listed above, there are simple accepted
practices that can be adopted to avoid them; most of those practices are
already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex
applications’ app-delegate did-finish-launching methods are chucked full of
hand stitched roll calls to framework initialization code. This needlessly
places a brittle dependency/burden on the application developer in what
should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point
to CocoaPods, Carthage, and the other influx of enterprise influenced
tooling and frameworks. Today’s mobile applications are no longer simply
todo apps.

Global init() functions are a clean solution to what engineers are
*already* boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a
solution you're used to using, but they're not a *clean* solution, and
Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex
applications as some sort of argument for them, because those are exactly
the applications where, in my experience, global initializers completely
break down as a reasonable approach. It's the small applications that can
get away with poor software engineering practices, because the accumulated
maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain
specters that can still afflict applications without global init
functions. Any feature can be abused and it seems hyperbolic to provide
arguments that seems to ascribe the above problems as an *inevitability* solely
afflicting global init functions. My and others’ experience with them has
been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon
nap. I really like the idea of some kind of reflective discovery. How
would that work? Maybe having a special @tag attribute that can be
searched at runtime?

There was another thread that mentioned this idea recently, but it would
be reasonable to provide some way to get a P.Type for every type in the
program that conforms to a protocol P.

Great, are there any keywords I can use to search for this thread?

This would be opt-in at the protocol level, because we wouldn't want to be
prevented from e.g. stripping an unused type from the program just because
it implemented Equatable. There are some other complexities here, but
that's the basic idea, and it's totally reasonable to support.

Does the other thread go into detail about this? I’m not sure that I
follow why it should be opt-in as opposed to simply searching for all
implementations of a specific protocol.

We would want this to be opt-in on the protocol because it would inhibit
removing a conforming type from the program. The compiler can ordinarily
remove types that you never directly use, and that's an important
optimization when e.g. linking in large libraries that you only use a small
part of. We wouldn't want to lose the ability to do that just because a
type implemented Equatable or Collection, because a lot of types implement
those protocols, and the ability to iterate all those types is probably not
very useful — certainly it isn't useful enough to justify losing that
optimization. In contrast, when there's a protocol that is useful to
iterate over all the conformances of, like a Deserializable protocol that
registers types with a deserialization engine, you always certainly know
that when you're defining the protocol.

John.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #17

That wasn't what I was going for, sorry for not being clear. I more meant that having another mechanism for discovering a set of declarations with a common attribute across a program might be called for besides just types with protocol conformances. Someone earlier in the thread suggested a @tag attribute; something like that could be allowed to apply not only to types but to functions as well.

-Joe

···

On Nov 29, 2016, at 1:54 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 28, 2016, at 11:53 AM, Joe Groff <jgroff@apple.com> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P. This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.

I worry that there are registration use cases for which "get all protocol conformers" is a bit boilerplatey. For example, collecting test case functions is a classic use case, and requiring a separate type declaration for every case would be a bit heavyweight compared to just having each test be a free function.

I agree with you, but I'm not sure that global initializers fit this use-case well either.


(John McCall) #18

In language design, we usually find that semantic annotations (which tell us what the programmer is trying to do) are superior to imperative annotations (which order the compiler to do something without much explanation why). They allow the implementation to provide a better programming experience (e.g. giving useful diagnostics about likely errors and selecting more thoughtful default behaviors), they're easier to apply sensibly to new language constructs, and they allow better optimization with fewer unintended side-effects. All of that applies here as well.

In this case, because it's really *the conformance of a type to a specific protocol* that should not be stripped, tying the annotation to the protocol is the more semantic approach, and as expected, it has a number of benefits. Let's dig in to why.

First, if the annotation has to be on every conforming type, there's an easy error of omission that could lead to bugs. The really unfortunate thing here is that it often *won't* lead to bugs, because you'll only miss it if the type is actually stripped, and there might be all sorts of reasons why a type that's "really" unused is not stripped — maybe the implementation isn't running the analysis at all (which it probably won't in unoptimized builds), or maybe the implementation of the analysis just isn't very good, or maybe you have test code that still uses the type, or maybe there's some old code still linked into the project. We really don't want to encourage a situation where e.g. you have an old implementation of a library sitting around, and you really want to delete it but you can't because when you do it breaks the build (but only in release mode), and two years later someone actually has the time to look into it and figures out that it's because you were missing this annotation on a bunch of types.

In contrast, if the annotation is on the protocol, it's really easy to check at the point of iterating over a protocol's conformances that the protocol has the right attribute. And in fact, we can use this iteration as the "root" use of all the conformances, so that e.g. if we can prove that nothing in the program actually iterates the conformances of that specific protocol, we can go back to dropping them.

Second, if the annotation is on types, it's unclear what exactly about the type can't be stripped. Do we have to keep all of its code around just in case something unexpectedly links to it? Do we have to keep around every protocol conformance it has? What does that mean if we add the ability to synthesize protocol conformances, or imply conformances post-hoc from other conformances?

So that's why this belongs on the protocol, and I hope the discussion gives you an idea of how to approach similar problems in the future.

John.

···

On Nov 25, 2016, at 7:59 AM, Jay Abbott <jay@abbott.me.uk> wrote:
Why not just say that this doesn't affect the removal of types (i.e. they can still be discarded) and add something to prevent specific types being discarded even if they're not statically used?

@nodiscard SomeType

This way, rather than a protocol opting in that anything implementing it is automatically `@nodiscard` a program or library would declare some types as non-discardable and could safely say "there will be at least 1 SomeProtocol" available" without saying what specific type it is in the public interface/docs?


(John McCall) #19

Sure, that's a good point.

John.

···

On Nov 29, 2016, at 1:56 PM, Joe Groff <jgroff@apple.com> wrote:

On Nov 29, 2016, at 1:54 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 28, 2016, at 11:53 AM, Joe Groff <jgroff@apple.com> wrote:

On Nov 19, 2016, at 8:57 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc@toolazydogs.com> wrote:

On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall@apple.com> wrote:

On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev@xenonium.com> wrote:

Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not sure if this was proposed or not; or even if this is a Swift-ly way of doing things. It would be pretty handy to be able to declare init() functions in my module to register handlers. It’s a common pattern in enterprise software.

Currently, I have to generate a lot of boilerplate code to emulate the behavior. I think it would be cleaner to have these global init() functions.

I’d rather like a swift attribute equivalent to : __attribute__((constructor))

It will not force me to call my initializer init, and moreover it will let me declare multiple functions so I would be able to register multiples handlers from a single module without having to group all the register call into a single init() function.

I’m not quite following what “__attribute__((constructor))” means; it looks like an LLVM implementation bit. Do you mean defining a new Swift declaration attribute named “constructor”? If so, I really like that idea. I think that the specific attribute name “constructor” may be a bit confusing though, since it’s not really constructing anything specific. Maybe “startup” would be a more descriptive attribute name?

@startup
func registerHandlers() {
}

The attribute would also help the compiler and IDEs prevent direct calling of the startup functions, thus reinforcing/focusing the startup functions’ role as global startup functions. Maybe global teardown functions would be helpful as well.

I’m going to try goofing around with the idea on my fork.

Some sort of reflective discovery would be better, I think. Eager global initialization is superficially attractive — what could be simpler than just running some code at program launch? — but as a program scales up and gains library dependencies, it very quickly runs into problems. What if an initializer depends on another already having been run? What if an initializer needs to be sensitive to the arguments or environment? What if an initializer need to spawn a thread? What if an initializer needs to do I/O? What if an initializer fails? Global initialization also has a lot of the same engineering drawbacks as global state, in that once you've introduced a dependency on it, it's extremely hard to root that out because entire APIs get built around the assumption that there's no need for an explicit initialization/configuration/whatever step. And it's also quite bad for launch performance — perhaps not important for a server, but important for pretty much every other kind of program — since every subsystem eagerly initializes itself whether it's going to be used or not, and that initialization generally has terrible locality.

Very good points. I recognize the dangers. However.

Don’t these problems already exist given that user code can still execute at program startup? It cannot be denied that the pattern is used and is extremely useful though, as you point out above, it should be used carefully. Thinking on it, there are always pros and cons to most language features and one relies on best practices to avoid shooting oneself in the foot. For each of the specters listed above, there are simple accepted practices that can be adopted to avoid them; most of those practices are already being employed for other situations.

And the pattern is not just useful in enterprise software. Complex applications’ app-delegate did-finish-launching methods are chucked full of hand stitched roll calls to framework initialization code. This needlessly places a brittle dependency/burden on the application developer in what should be a simple behind the scenes collaboration.

One could argue that such a thing was never needed before. I would point to CocoaPods, Carthage, and the other influx of enterprise influenced tooling and frameworks. Today’s mobile applications are no longer simply todo apps.

Global init() functions are a clean solution to what engineers are already boiler plating with static singleton code.

No, they aren't a clean solution for the reasons I listed. They may be a solution you're used to using, but they're not a *clean* solution, and Swift's line against providing them is for the best.

I'm surprised that you keep talking about enterprise / complex applications as some sort of argument for them, because those are exactly the applications where, in my experience, global initializers completely break down as a reasonable approach. It's the small applications that can get away with poor software engineering practices, because the accumulated maintenance/complexity/performance costs are, well, small.

It’s difficult to subscribe to the slippery slope arguments that contain specters that can still afflict applications without global init functions. Any feature can be abused and it seems hyperbolic to provide arguments that seems to ascribe the above problems as an inevitability solely afflicting global init functions. My and others’ experience with them has been very different from yours.

With that said, I took some time to re-read your reply, after my afternoon nap. I really like the idea of some kind of reflective discovery. How would that work? Maybe having a special @tag attribute that can be searched at runtime?

There was another thread that mentioned this idea recently, but it would be reasonable to provide some way to get a P.Type for every type in the program that conforms to a protocol P. This would be opt-in at the protocol level, because we wouldn't want to be prevented from e.g. stripping an unused type from the program just because it implemented Equatable. There are some other complexities here, but that's the basic idea, and it's totally reasonable to support.

I worry that there are registration use cases for which "get all protocol conformers" is a bit boilerplatey. For example, collecting test case functions is a classic use case, and requiring a separate type declaration for every case would be a bit heavyweight compared to just having each test be a free function.

I agree with you, but I'm not sure that global initializers fit this use-case well either.

That wasn't what I was going for, sorry for not being clear. I more meant that having another mechanism for discovering a set of declarations with a common attribute across a program might be called for besides just types with protocol conformances. Someone earlier in the thread suggested a @tag attribute; something like that could be allowed to apply not only to types but to functions as well.


(Jay) #20

Yes, these are some really useful insights, thanks.

···

On Mon, 28 Nov 2016 at 01:58 John McCall <rjmccall@apple.com> wrote:

> On Nov 25, 2016, at 7:59 AM, Jay Abbott <jay@abbott.me.uk> wrote:
> Why not just say that this doesn't affect the removal of types (i.e.
they can still be discarded) and add something to prevent specific types
being discarded even if they're not statically used?
>
> ```swift
> @nodiscard SomeType
> ```
>
> This way, rather than a protocol opting in that anything implementing it
is automatically `@nodiscard` a program or library would declare some types
as non-discardable and could safely say "there will be at least 1
SomeProtocol" available" without saying what specific type it is in the
public interface/docs?

In language design, we usually find that semantic annotations (which tell
us what the programmer is trying to do) are superior to imperative
annotations (which order the compiler to do something without much
explanation why). They allow the implementation to provide a better
programming experience (e.g. giving useful diagnostics about likely errors
and selecting more thoughtful default behaviors), they're easier to apply
sensibly to new language constructs, and they allow better optimization
with fewer unintended side-effects. All of that applies here as well.

In this case, because it's really *the conformance of a type to a specific
protocol* that should not be stripped, tying the annotation to the protocol
is the more semantic approach, and as expected, it has a number of
benefits. Let's dig in to why.

First, if the annotation has to be on every conforming type, there's an
easy error of omission that could lead to bugs. The really unfortunate
thing here is that it often *won't* lead to bugs, because you'll only miss
it if the type is actually stripped, and there might be all sorts of
reasons why a type that's "really" unused is not stripped — maybe the
implementation isn't running the analysis at all (which it probably won't
in unoptimized builds), or maybe the implementation of the analysis just
isn't very good, or maybe you have test code that still uses the type, or
maybe there's some old code still linked into the project. We really don't
want to encourage a situation where e.g. you have an old implementation of
a library sitting around, and you really want to delete it but you can't
because when you do it breaks the build (but only in release mode), and two
years later someone actually has the time to look into it and figures out
that it's because you were missing this annotation on a bunch of types.

In contrast, if the annotation is on the protocol, it's really easy to
check at the point of iterating over a protocol's conformances that the
protocol has the right attribute. And in fact, we can use this iteration
as the "root" use of all the conformances, so that e.g. if we can prove
that nothing in the program actually iterates the conformances of that
specific protocol, we can go back to dropping them.

Second, if the annotation is on types, it's unclear what exactly about the
type can't be stripped. Do we have to keep all of its code around just in
case something unexpectedly links to it? Do we have to keep around every
protocol conformance it has? What does that mean if we add the ability to
synthesize protocol conformances, or imply conformances post-hoc from other
conformances?

So that's why this belongs on the protocol, and I hope the discussion
gives you an idea of how to approach similar problems in the future.

John.