You know, come to think of it, I totally agree, and here's why: A normal initializer (if #2 is accepted) would *conceptually* have the signature:
mutating func `init`(...) -> Self Which would mean that both `self` and the returned result are non-optional. A failable initializer could then have the signature: mutating func `init`() -> Self? Which would make the returned result optional, but leave `self` non-optional. This would make `return nil` less out-of-place, like you said, while still leaving `self` as a set-exactly-once `inout Self`. A factory initializer would then have the signature: static func `init`(...) -> Self or in case of a failable factory initializer: static func `init`(...) -> Self? Which would still make sense with the now legal `return ...` syntax, while adding the restriction of not having any `self` at all. So, annotating the initializer with the keyword `factory` would cause it to change the signature as well as remove any compiler assumptions about the dynamic type of the returned instance. In addition, idea #3 would be available for more deterministic in-place initialization. > On Jun 9, 2017, at 2:47 PM, Xiaodi Wu <[email protected]> wrote: > > On Fri, Jun 9, 2017 at 07:33 Gor Gyolchanyan <[email protected] > <mailto:[email protected]>> wrote: > So far, we've discussed two ways of interpreting `self = nil`, both of which > have a sensible solution, in my opinion: > > 1. It's a special rule like you said, which can be seen as counter-intuitive, > but recall that `return nil` is just as much of a special rule and is also > largely counter-intuitive. > > `return nil` is “special,” but it doesn’t conflict with any other syntax > because the initializer notionally has no return value. Personally, I have > always disliked `return nil` in failable initializers for that reason, but I > couldn’t come up with a better alternative. > > Your proposed idea to allow returning any value is interesting because, in > the case of a failable initializer, `return nil` continues to have the same > meaning if we consider the return value of the initializer to be of type > `Self?`. For that reason, I think your idea #2 is quite clever, and it would > go a long way in making `return nil` a lot less odd. It also increases the > expressivity of initializers because it allows one to set the value of self > and also return in one statement, clearly demonstrating the intention that no > other code in the initializer should be run before returning. > > For all of those reasons, I think idea #2 is a winning idea. > > The benefit of `self = nil` is that it's much more in line with > initialization semantics, it provides more uniform syntax and it's a bit less > restrictive. > > 2. It's an `inout Self!`, like Greg said, which can be seen as more > cumbersome. Implicitly unwrapped optionals are a bit difficult, but this > "variation" of it is much more restrictive then the normal ones, because > unlike normal implicitly unwrapped optionals, this one cannot be accessed > after being assigned nil (and it also cannot be indirectly assigned `nil`, > because escaping `self` is not allowed before full initialization), so there > is only one possible place it can be set to nil and that's directly in the > initializer. This means that `self` can be safely treated as `inout Self` > before being set to nil (and after being set to nil, it doesn't matter any > more because you aren't allowed to access it, due to not being fully > initialized). > > I have to say, I don’t like either of these explanations at all. I think > having a “special” IUO is a difficult sell; it is just conceptually too > complicated, and I don’t agree that it gains you much. > > By your own admission, `self = nil` is wonky, and making the language wonkier > because it currently has a parallel wonky feature in `return nil` seems like > the wrong way to go. In addition, there’s nothing gained here that cannot be > done with a defer statement; of course, defer statements might not be very > elegant, but it would certainly be less wonky than inventing a new variation > on an IUO to allow assignment of nil to self... For those reasons, I conclude > that I’m not excited about your idea #1. > > Overall, I'd go with #2 because it involves much less confusing magic and the > restrictions of `self as inout Self!` are imposed by already existing and > well-understood initialization logic, so the provided guarantees don't really > come at the cost of much clarity. > >> On Jun 9, 2017, at 2:23 PM, Xiaodi Wu <[email protected] >> <mailto:[email protected]>> wrote: >> >> >> On Fri, Jun 9, 2017 at 07:12 Gor Gyolchanyan <[email protected] >> <mailto:[email protected]>> wrote: >> I think a good approach would be to have `self = nil` only mean `the >> initializer is going to fail` because if your type is >> ExpressibleByNilLiteral, it means that the `nil` of your type already >> carries the same meaning as if your type was not ExpressibleByNilLiteral and >> was an optional instead, so having a failable initializer doesn't really >> make sense in that case (since you could've initialized `self` to its own >> `nil` in case of failure). Still, some valid use cases may exist, so the >> natural (and quite intuitive) way to circumvent this would be to call >> `self.init(nilLiteral: ())` directly. >> >> So you would create a special rule that `self = nil` means a different thing >> in an initializer than it does in a function? Essentially, then, you’re >> creating your own variation on an implicitly unwrapped optional, where >> `self` is of type `inout Self?` for assignment in initializers only but not >> for any other purpose. Implicitly unwrapped optionals are hard to reason >> about, and having a variation on it would be even harder to understand. I >> don’t think this is a workable design. >> >> It might be possible to have `self` be of type `inout Self?`; however, I do >> think Greg is right that it would create more boilerplate than the current >> situation. >> >>> On Jun 9, 2017, at 2:07 PM, Xiaodi Wu <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> >>> On Fri, Jun 9, 2017 at 06:56 Gor Gyolchanyan <[email protected] >>> <mailto:[email protected]>> wrote: >>> The type of `self` could remain `inout Self` inside the failable >>> initializer. The ability to assign nil would be a compiler magic (much like >>> `return nil` is compiler magic) that is meant to introduce uniformity to >>> the initialization logic. >>> >>> The idea is to define all different ways initialization can take place and >>> expand them to be used uniformly on both `self` and all its members, as >>> well as remove the ways that do not make sense for their purpose. >>> >>> Currently, there are 3 ways of initializing self as a whole: >>> 1. delegating initializer >>> 2. assigning to self >>> 3. returning nil >>> >>> #1: The delegating initializer is pretty much perfect at this point, in my >>> opinion, so no changes there. >>> >>> #2: The only exception in assigning to self is the `nil` inside failable >>> initializers. >>> >>> #3: The only thing that can be returned from an initializer is `nil`, >>> which is compiler magic, so we can thing of it as a misnomer (because we >>> aren't really **returning** anything). >>> >>> If, for a second, we forget about potential factory initializers, returning >>> anything from an initializer doesn't make much sense, because an >>> initializer is conceptually meant to bring an existing object in memory to >>> a type-specific valid state. This semantic was very explicitly in >>> Objective-C with `[[MyType alloc] init]`. Especially since even >>> syntactically, the initializer does not specify any return type, the idea >>> of returning from an initializer is counter-intuitive both syntactically >>> and semantically. >>> >>> The actual *behavior* of `return nil` is very sensible, so the behavior, I >>> imagine `self = nil`, would largely mean the same (except not needed to >>> return immediately and allowing non-self-accessing code to be executed >>> before return). Being able to assign `nil` to a non-optional >>> (ExpressibleByNilLiteral doesn't count) may feel a bit wonky, >>> >>> What happens when Self is ExpressibleByNilLiteral and you want to >>> initialize self to nil? That is what `self = nil` means if `self` is of >>> type `inout Self`. If `self` is of type `inout Self` and Self is not >>> ExpressibleByNilLiteral, then it must be an error to assign nil to self. >>> Anything else does not make sense, unless `self` is of type `inout Self?`. >>> >>> but not as wonky as returning nil from something that is meant to >>> initialize an object in-place and doesn't look like it should return >>> anything. >>> >>> # Factory Initializers >>> >>> In case of factory initializers, the much discussed `factory init` syntax >>> could completely flip this logic, but making the initializer essentially a >>> static function that returns an object. In this case the initializer could >>> be made to specify the return type (that is the supertype of all possible >>> factory-created objects) and assigning to self would be forbidden because >>> there is not self yet: >>> >>> extension MyProtocol { >>> >>> public factory init(weCool: Bool) -> MyProtocol { >>> self = MyImpl() // error: cannot assign to `self` in a factory >>> initializer >>> self.init(...) // error: cannot make a delegating initializer >>> call in a factory initializer >>> if weCool { >>> return MyCoolImpl() >>> } else { >>> return MyUncoolImpl() >>> } >>> } >>> >>> } >>> >>> # In-place Member Initializers >>> >>> In addition, member initialization currently is only possible with #2 (as >>> in `self.member = value`), which could be extended in a non-factory >>> initializer to be initializable in-place like this: >>> >>> self.member.init(...) >>> >>> This would compliment the delegating initialization syntax, while giving a >>> more reliable performance guarantee that this member will not be >>> copy-initialized. >>> >>>> On Jun 9, 2017, at 1:32 PM, Xiaodi Wu <[email protected] >>>> <mailto:[email protected]>> wrote: >>>> >>>> If `self` is not of type `inout Self?`, then what is the type of `self` >>>> such that you may assign it a value of `nil`? >>>> >>>> It certainly cannot be of type `inout Self`, unless `Self` conforms to >>>> `ExpressibleByNilLiteral`, in which case you are able to assign `self = >>>> nil` an unlimited number of times–but that has a totally different meaning. >>>> >>>> Could `self` be of type `inout Self!`? Now that implicitly unwrapped >>>> optionals are no longer their own type, I’m not sure that’s possible. But >>>> even if it were, that seems unintuitive and potentially error-prone. >>>> >>>> So I think Greg is quite right that, to enable this feature, `self` would >>>> have to be of type `inout Self?`–which is intriguing but potentially more >>>> boilerplatey than the status quo. >>>> On Fri, Jun 9, 2017 at 05:24 Gor Gyolchanyan via swift-evolution >>>> <[email protected] <mailto:[email protected]>> wrote: >>>> Good point, but not necessarily. >>>> Since you cannot access `self` before it being fully initialized and since >>>> `self` can only be initialized once, this would mean that after `self = >>>> nil`, you won't be allowed to access `self` in your initializer at >>>> all.You'll be able to do any potential, cleanup though. >>>> Also, since there can be only one `self = nil`, there's no reason to treat >>>> `self` as `inout Self?`, because the only place it can be `nil` is the >>>> place it cannot be accessed any more. >>>> >>>> >>>>> On Jun 9, 2017, at 7:45 AM, Greg Parker <[email protected] >>>>> <mailto:[email protected]>> wrote: >>>>> >>>>> >>>>>> On Jun 8, 2017, at 5:09 AM, Gor Gyolchanyan via swift-evolution >>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>> >>>>>> 1. Arbitrary `self` Assignments In Intializers >>>>>> >>>>>> The first ideas is to allow `self = nil` inside failable initializers >>>>>> (essentially making `self` look like `inout Self?` instead of `inout >>>>>> Self` with magical `return nil`), so that all initializers uniformly can >>>>>> be written in `self = ...` form for clarity and convenience purposes. >>>>>> This should, theoretically, be nothing but a `defer { return nil }` type >>>>>> of rewrite, so I don't see any major difficulties implementing this. >>>>>> This is especially useful for failable-initializing enums where the main >>>>>> switch simply assigns to self in all cases and the rest of the >>>>>> initializer does some post-processing. >>>>> >>>>> I don't see how to avoid source incompatibility and uglification of >>>>> failable initializer implementations here. Allowing `self = nil` inside a >>>>> failable initializer would require `self` to be an optional. That in turn >>>>> would require every use of `self` in the initializer to be nil-checked or >>>>> forced. I don't think that loss everywhere outweighs the gain of `self = >>>>> nil` in some places. >>>>> >>>>> >>>>> -- >>>>> Greg Parker [email protected] <mailto:[email protected]> Runtime >>>>> Wrangler >>>>> >>>>> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] <mailto:[email protected]> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
