"Removing implicit `nil` initial value for Optional-typed variables"

When this has been discussed in the past it has also been suggested that it might make sense to maintain the behavior for all implicitly unwrapped optionals.

5 Likes

I'll just point out that it's not uncommon to use regular optionals for @IBOutlet too (at least in my code). So I'd rather have it linked to @IBOutlet than whether the optional is implicitly unwrapped or not.

2 Likes

It’s been a very long time since I’ve thought about @IBOutlet. Can you clarify for me (and for anyone else with the same confusion) - what is it about the nature of IBOutlet that would make it more onerous to write out = nil than in all of the other places that this proposed change would affect?

I don't think that having "= nil" would be of any harm in case of IBOutlets:

@IBOutlet var someView: UIView! = nil

So long as IBOutlet variable is properly set to whatever value it needs too be set to (like a view, etc).

It may be perceived as it gets set to nil first, then to a non-nil value, but I don't think that's a problem (whether this double set actually happens or not).

IMHO, implicitly unwrapped optionals could follow the same rules as normal optionals, without being treated specially. In particular, I'd expect this member wise initialiser to be done:

struct S {
    var x: Int!
    var y: Int! = nil
// automatic memberwise  init:
    init(x: Int!, y: Int! = nil) {
        ...
    }
}

While we are on this we could also finally take the opportunity of introducing the real type, "!" is a syntax sugar for:

var x: ImplicitlyUnwrappedOptional<T> // same as:
var x: T!

Agree. This would seem to fit with our treatment of IUOs in general.

We won’t be doing that, since we actively removed it:

7 Likes

In any of the discussions surrounding the recent announcement about Swift 6, has this breaking change been brought up? Any updates about whether or not implicit = nil will be removed?

2 Likes

Somehow the logic here seems the exact opposite of the logic for the shorthand

if let someOptional {

which of course has already been implemented.

I don't see them as very related (and therefore not as opposites, nor in any way contradictory).

if let someOptional { } was previously invalid code, and is now treated as shorthand for if let someOptional = someOptional { }. A rather straight-forward situation.

On the other hand, this code:

/// Code A
struct SomeStruct {
    var optionalProperty: Int?
}

is currently treated as shorthand for:

/// Code B
struct SomeStruct {
    var optionalProperty: Int? = nil
}

If we remove the interpretation of Code A as shorthand for Code B then the Code A is still valid, and importantly it has a rather different meaning than Code B. There is currently no way to express the "original" meaning of Code A, because it is always re-interpreted as shorthand for Code B.

To be clear, the "original" meaning of Code A is something like:

"This is a struct with a single property of type Int? for which an initializer should be synthesized, and that initializer should not provide a default argument for the optionalProperty parameter, and all other initializers must not forget to assign a value of type Int? to optionalProperty."

The meaning of Code B is something like:

"This is a struct with a single property of type Int? for which an initializer should be synthesized, and that initializer should provide the default argument of nil for the optionalProperty parameter, and if any other initializer does not assign a value to optionalProperty then the value nil should be automatically assigned."

3 Likes

Indeed. And while it is possible to "opt-out" of the current unfortunate behaviour by switching to a more explicit Optional<T> form (that doesn't exhibit this drawback!) it is not ideal and not possible at all for implicitly unwrapped optionals, so if you have a struct with an IUO you'd have to implement the struct's memberwise init manually to opt-out of the unwanted behaviour, which could be painful for a struct with many fields. +1 for the change.

2 Likes

Right, I was just in the middle of updating my post to point out that I was wrong. It is actually possible to express the "original" meaning of Code A (as you say, using var optionalProperty: Optional<Int>), which does mean that the situation is not quite as dire as I was painting it.

The current situation is still a bit of a foot-gun though, because it makes it easy to not realize that a property is being implicitly initialized with nil.

I hadn't thought of how property wrappers and attributes such as @IBOutlet (is that implemented as a property wrapper yet?) factor into the situation, and it sounds like they add some additional weight to the argument that we should remove the shorthand.

To be fair to the other side of the debate, this is a breaking change that would probably affect a lot of code, given the ubiquity of optionals... but on the bright side I think the migration would be pretty unambiguous. All var optionalProperty: SomeType? would be re-written as var optionalProperty: SomeType? = nil and no behavior should change, and now everyone is free to delete the = nil from wherever they want.

3 Likes

I thought that part of the thinking on this was that it's better to have explicit initializations rather than implicit initializations. Hence my seeing the opposite thinking with the if let someOptional { } shorthand.

Also, I was mostly thinking about local variables, not ivars, I guess, where the problems you're talking about aren't relevant.

I'm having a hard time thinking of cases where I want optionals to be uninitialized. They either need to be initialized to something or to nil. Same with IUOs. To write sensible code they need to be initialized before first use. IBOutlets are the canonical example of variables initialized after the init method. There are a few tricks you can play using ? with IUOs in some cases but those actually depend on initialization to nil. Adding = nil to every IBoutlet in the known universe doesn't seem like a productive use of anyone's time.

By this logic we should treat "var x: Int" as "var x: Int = 0", "var x: Bool" as "var x: Bool = false", etc. Don't think this reasoning is good. As mentioned adding "= nil" could happen automatically by a migration tool.

2 Likes

I'm not the one proposing a change.
In Obj-C I believe all variables are initialized to 0. All classes are alloced with calloc automaticallly so all variables are zero or nil. In a language that prides itself on safety I don't think that initializing all variables to zero is crazy talk.
Will the compiler still warn/error if a variable isn't initialized in all code paths? I sure hope so.
And regarding the fix-its: You're talking about modifying most of the Swift source code files in the world. Do you think every modification will be carefully inspected? That would be millions of hours of dev time spent on this change.

1 Like
  1. only instance, static and global variables. Local variables are not auto-initialised to 0.
  2. In Obj-C (that is C under the hood) you could still have a struct allocated on stack or with malloc, in which case struct's fields would be garbage.
  3. it is not necessarily a good thing.
enum Enum {
    a = 1,
    b = 2
};
enum Enum x;
int main() {
    printf("%d\n", x); // 0
}

Sure thing.

1 Like

Under ARC, all object-type variables are initialized to nil. And I believe there’s a compiler flag to initialize all locals to 0.

1 Like

Yes, but that's more of a last ditch effort to stop uninitialized reads from becoming exploitable than a recommended setting.

I don’t think the LSG has talked about this specifically, so this isn’t an official statement, but I can’t imagine us making this change under our current rubric for Swift 6. If we do it, it would have to be for a later language mode, and the source compatibility impact will be the major point of debate.

2 Likes

You can definitely have a language that consistently auto-initialises uninitialised variables to some default value, like so:

protocol DefaultInitializable {
    static var defaultValue: Self { get }
}
extension Int: DefaultInitializable {
    static let defaultValue = 0
}
extension Bool: DefaultInitializable {
    static let defaultValue = false
}
// and so on
// usage:

struct Foo {}

struct Bar: DefaultInitializable {
    static let defaultValue = Bar()
}

var x: Int
print(x) // ✅ 0
var foo: Foo
print(foo) // 🛑 Error, not initialized
var bar: Bar
print(bar) // ✅

I just don't think this could be ever in Swift.

With non-copyable support, do we have mechanisms powerful enough to track the ownership of globally shared variables now? It occurs to me that this is perhaps the most difficult part for implementation.

With concurrency and ownership guarantees we can improve the safety of global variables, and greatly narrow the gap between global and local variables.

How are optionals different from other variables in this respect?