Pre-pitch: remove the implicit initialization of Optional variables

Yes, but mostly no. :lying_face:
It's semantically incorrect. We don't actually have a truthful mechanism for properties that cannot be initialized during initialization.*

final class ViewController: UIViewController {
  @IBOutlet private unowned var label: UILabel! = uninitializedImplicitlyUnwrappedOptional()
/// The "value" of an implicitly unwrapped `Optional` that has not yet been assigned a real value.
///
/// This is technically `.none` / `nil`, but it should never be used directly.
/// If it is *ever* valid for getting an `Optional`'s value to result in `nil`,
/// then it should not be implicitly unwrapped.
func uninitializedImplicitlyUnwrappedOptional<Wrapped>() -> Wrapped! {
  nil
}

* There are proposed property wrappers that could help, but don't, for the most common use case, because IBOutlets are required to be optional.

1 Like

just recently we introduced implicit returns from single-expression closures... now the new comers have to remember that this is good:

{
    "hello"
}

and this is not

{
    print("inside closure")
    "hello"
}

if we want to be consistent we need to either always require return or never, right? i wouldn't mind the latter.

implicit returns from single-expression closures optimisation breaks consistency and reduces visual clutter by removing some noise characters. var v: Int? is exactly the same thing.

if we do consistency in one place but not another - we are ... inconsistent.

6 Likes

@jrose isn’t saying that consistency is the most important goal — in fact, he says that it isn’t the most important goal. Rather, he’s saying that we should be consistent unless there’s a good reason to be inconsistent (unless I’ve misunderstood what he said). It would be theoretically nice if Swift were a totally consistent language, but in practice it would be verbose, take a long time to compile, and/or cause bugs.

Implicit returns for single-expression functions helps make code more readable by removing useless return statements. Furthermore, single-expression closures were already inconsistent with multiple-expression closures before this feature was implemented since type inference only applies to single-expression closures, not multiple-expression closures. And I think the rule is simple enough that its complexity cost is justified.

4 Likes

yep, that's how i understood him as well. looks like the decision whether to prefer "more noise plus more consistency" vs "less noise plus less consistency" is taken on a case by case basis.

1 Like

I think every exception should have a rationale. I don't think there is a good one for implicitly initialized Optionals.

By the way, I’m not sure how practical this would actually be, but I’d love some form of “multi-stage” initialization that allows you to call instance functions to set properties. I suspect that’s infeasible, since you’d need some way of confirming at compile-time that the relevant instance methods don’t depend on uninitialized properties, but I can dream.

Implicitly-unwrapped Optionals are meant to solve this problem, of course, but they remain dangerous (if not unsafe in the sense of undefined behavior) forever.

1 Like

listing the previous attempts:

nov 2017 Pitch: Remove default initialization of optional bindings
jan 2019 Remove implicit initialization of Optionals?
jun 2019 PrePitch: Optional variables should require explicit initialization

the other radical way of solving this inconsistency is to introduce some DefaultValueInitializable (?) protocol. Optionals would confirm to it, so could user types.

:-( I didn’t realize there’d been an attempt so recently. Still, it seems like it’s got pretty broad support, so it may still be worth me writing out the full pitch.

EDIT: And I replied to the most recent one too, ha.

7 Likes

These issues are not separate at all; they heavily overlap. When you write var x: Int?, the compiler implicitly adds the "variable initialization expression" nil as if you had written var x: Int? = nil, hence "implicit initialisation of T?".

MyStruct.s is only initialized once: var s: S = S(1). In MyStruct.init(s:), you're merely modifying the value: self.s = s. And you can do that whenever you want, including in the initializer, because MyStruct.s is a mutable var property.

This behavior is very straightforward to me. I don't understand why you think this is a bug.

not just me, the core team as well. also it is somewhat unexpected.

fwiw, that's how C++ handles it
#include <stdio.h>

struct A {
	A(int v) {
		printf("A(%d)\n", v);
	}
};

struct C {
	A a = 1;
	C(int n) : a(n) {}
	C() {}
};

int main() {
	printf("b4 C()\n");
	C();
	printf("b4 C(2)\n");
	C(2);
}

full output:

b4 C()
A(1)
b4 C(2)
A(2)

Definitely. I don’t personally think of Jun 2019 as very “recent”. A lot has happened in between. The community has matured, views have changed. And most importantly, the window is briefly open for this change now that Swift 6 is on the horizon.

5 Likes

The consistency is that computed var and closures always behaved like this, and I think it's quite natural. And the compiler will help you if you don't think so...

1 Like

@jrose as the topic's author you can include a poll panel in the initial post like here. it won't automatically lead to anything material but at least you'd immediately know what people think about it, and maybe that would make this change successful compared to the previous attempts. make sure you make it clear that this change is supposed to happen in all cases (structs/enums, classes, local variables, implicitly unwrapped optionals).

ps. it would still be a -1 from me for the reasons i stated above.

pps. i feel the majority of the votes would be positive, perhaps 80/20

ppps. i would be more happy to lose on this one should the majority decide this is the way to go and should there be enough motivation to make the actual fix.

1 Like

These forums are far from representative of the Swift community or any subset of it, and while the presence or absence of a rough consensus can certainly send strong signals, decisions aren't taken on votes. The purpose of pitching ideas is to solicit good arguments for or against a design and to refine ideas iteratively.

15 Likes

yep, that's what i meant; 80/20 positive vote would send a strong signal; nothing less, nothing more.

somewhat related. this:

struct S: Decodable {
    var x: Int
    var y: Optional<Int>
}

let data = "{\"x\": 1}".data(using: .utf8)!
let x = try! JSONSerialization.jsonObject(with: data, options: [])

must fail, as y is not marked as having default value. it currently doesn't fail in any form (including short-hand Int?). the current behaviour doesn't allow you to opt-opt of this "no key ==> nil" behaviour. the fixed version would make it more flexible and allow to differentiate:

"{ x:1}"
    var: y: Int?         ===>  ERROR *** not the current behaviour
    var y: Int? = nil  ===>  y=nil
    var y: Int? = 3    ===>  y= Optional(3)  *** not the current behaviour

"{ x:1, y: null}"
    var: y: Int?         ===>  y=nil
    var y: Int? = nil  ===>  y=nil
    var y: Int? = 3    ===>  y=null

"{ x:1, y: 2}"
    var: y: Int?         ===>  y=Optional(2)
    var y: Int? = nil  ===>  y=Optional(2)
    var y: Int? = 3   ===>  y= Optional(2)

How often do people use the full form Optional<T> to avoid implicit initialization? I guess, not often enough to give up a convenient syntactic sugar. Initialization with nil is the most common case and it is good to suppress ? = nil noise. Also, as mentioned above, this behavior is consistent with property wrappers that can have an implicit initializer.

1 Like

Yes, nil is a very natural starting value for an optional, but that is not a valid reason since there are lots of other types with natural default values, like empty arrays, 0 for all numeric types etc.

This is very inconsistent, and the potential behaviour of property wrappers doesn't really change that, they are not basic types and part of the point of having them is to add special behaviour like that.

4 Likes

This is a great question – it has come up in a few discussions of this (pre)pitch or similar ones.

To be honest I had no idea this was a thing and the fact that it exists seems like an oversight / bug to me rather than something that was intentionally designed as such.

To me its existence brings up many more questions than problems it may solve; to me it's another reason to remove the special behaviour of Type?.

That doesn't answer the question directly but I'd be curious what others think.

This topic is also discussed at length in this thread: PrePitch: Optional variables should require explicit initialization

Better question, how many people even know that Optional<T> avoids implicit initialization in the first place?

10 Likes

I think that 'very inconsistent' is a strong descriptor here, especially when considering the history of Swift implementing a lot of 'features' (implicit returns, trailing closures which explicitly contradict the declaration of a function, and the entirety of control flow in ResultBuilders) for the sake of having 'light weight syntax'.

Implicitly assigning nil to an optional is an example of 'light weight syntax' which no real drawbacks.

If implied = nil was not a current feature, I imagine that we'd have a pitch to "Allow the programmer to elide = nil on optional declarations". The proposal would note that this would create 'light weight syntax', and that there would be no effect on ABI. And most of us would cheer.

3 Likes