[swift-evolution] Global init() functions

Dave Abrahams dabrahams at apple.com
Mon Nov 28 21:53:50 CST 2016


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

>> On Nov 19, 2016, at 8:57 PM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>>> On Nov 19, 2016, at 6:03 PM, Alan Cabrera <adc at toolazydogs.com> wrote:
>>>> On Nov 19, 2016, at 4:02 PM, John McCall <rjmccall at apple.com> wrote:
>>>> 
>
>>>>> On Nov 19, 2016, at 3:31 PM, Alan Cabrera <adc at toolazydogs.com> wrote:
>>>>>> On Nov 19, 2016, at 1:21 PM, John McCall <rjmccall at apple.com> wrote:
>>>>>> 
>>>>>>> On Nov 19, 2016, at 10:07 AM, Alan Cabrera via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>>>> On Nov 19, 2016, at 9:27 AM, Jean-Daniel <dev at xenonium.com> wrote:
>>>>>>>> 
>>>>>>>> 
>>>>>>>>> Le 19 nov. 2016 à 15:58, Alan Cabrera via swift-evolution <swift-evolution at 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.

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.

-- 
-Dave



More information about the swift-evolution mailing list