Moving toward deprecating force unwrap from Swift?

Time to wrap this pitch up. Apologies for the provocative OP, I’ve moderated my position since then. Along the way I’ve concluded:

  • Deprecating the force unwrap postfix operator is out of the question, source breaking changes to Swift at this point are out of scope. Many are still confident they are able to wield it with the discretion it requires though I’m not completely convinced. There is however a use case in less critical code bases such as for scripting.

  • Things might have been slightly better if Swift had adopted Kotlin's !! instead of ! as it would have been easier to audit for.

  • It may be useful to create non-nilable initialisers for URL when it is initialised with a static string as this was so often the case-in-point.

  • I have continued to explore these alternatives in the Unwrap SPM package

  • I submitted the PR in the end to add a -warn-force-error option to the compiler though It was rejected pretty quickly as “the role of a linter”.

It even blew up on Twitter where you can gauge exactly what the broader community feels:

It is still my belief it's regrettable that force-unwrap is one of the first things developers new to Swift learn and likely one of the last to master. Having the vision to build nil-able types into a language and then negating much of the benefit with such a flimsy construct has pros and cons but in the end I came down of the side of “it does more harm than good”. If it's an assertion, require an annotation, add a bit of friction so it receives the attention it merits.

Replacing it with a guard is not quite the answer either. When debugging, nothing is worse than a section of code that fails silently so remember to log something to the console. I assume this is the origin of the belief that “safe” crashing is preferable but this doesn’t pan out well in the hands of users.

Thanks for the replies, All the best for 2021.

7 Likes

Is adding

Optional<U>.forceUnwrap(
  _ message: @autoclosure () -> String = String(),
  file: StaticString = #file,
  line: UInt = #line
) -> U

to the standard library out of the question?

I feel users, particularly novice users, are unlikely to actually use a Swift package that adds such a method. The key is to allow unwrapping with a custom failure message and as little additional syntax as possible. Something you could teach before mentioning !.

I feel the rationale for putting this in the standard library itself is similar to that of Result: there are numerous third-party implementations doing the same thing, it is generally useful across pretty much every Swift program, and it improves readability at the point of use.

Hello John.

Although you proposed something that you knew would be provocative among the community, and I happen to disagree with you on this topic, that twitter thread you linked has somewhat saddened me. I have never interacted with you before but I’ve seen you around the forums since the earliest days and know you are not just someone looking for trouble or looking to stir up the community unnecessarily, so I read your proposal seriously without reading too much between the lines, while keeping an open mind. I feel some of the feedback in that twitter thread has the power to demotivate other people from wanting to share their opinion and experiences on the language in the future, in the forums. I know you had to be prepared for it even before writing the proposal, hopefully it didn’t affect you too much and you will continue writing new proposals in the future.

All the best for 2021!

Ricardo

10 Likes

It is an interesting solution, but why do you think compile time optimisation is not Swifty way? As I remember, Swift already does some computation while building, such as checking that a dictionary is not initialised with duplicate keys or calculating simple expressions like [1, 2].map { $0 * 2 }.sum(). I see your point about compilation time, but if reducing build time is a critical moment, we should probably disable these features too.

What I mean is, the Swift compiler is already slow. I have examples of trivial CGFloat expressions with some 6-7 operations that take literally seconds to compile. At least on macOS, don't know if it's the same on Linux.

I'm not familiar with swiftc internals but optimizations and compile time evaluations like the above could be done on the llvm level in principle. constexpr on the other hand requires a higher-level evaluation, because the compiler needs to know whether a given expression is const or not in ther first place, i.e. it's not a question of optimization. That's my understanding anyway. C++ does a lot of this type of work when expanding templates etc. but I suspect for Swift it will be a lot of additional work that would slow it down even more. I might be wrong, though!

1 Like

You are right. Although I really like the idea to allow developers do some work at compile time, I absolutely agree with you, it will make the compiler even slower :pensive:

core team feedback

Hi John,

Thanks for the thread wrap up. The core team discussed this thread and, as you surmise, deprecating the force unwrap postfix operator is out of the question, though not just for source stability reasons. We believe force-unwrap and force-try are valuable tools and would not entertain a proposal to deprecate or remove them. We think adding some wording to this effect to the Commonly Proposed document probably makes sense.

Participants are welcome to continue to discuss the stylistic merits of avoiding force unwraps, but that should be done in the Using Swift section of the forum rather than the Evolution section.

Cheers,
Ben

17 Likes

Hi Ben, thanks for the feedback. I'm still happy I asked the question. I've had a difficult relationship with force-unwrap over the years from the very beginning. Could we not have had an opt-in warning as a compromise? The PR was prepared and is of very modest scope. Go on, Go on Go on Go on.

1 Like

What's the motivation for an opt-in warning as opposed to the status quo, where users who wish to discourage/prohibit force unwrapping are free to use a linter to produce a warning/error?

Well that's all very well but my last encounter with a linter was on a large Open Source project that had it preconfigured into the build. I had to a) install it and b) ended up filing a PR on the linter's project as it was crashing (due to a force unwrap IIRC). I personally don't feel the compiler should shy away from linting options just on principle. Internally the compiler has assembled the AST on which such analysis can really be performed definitively to the extent that the PR I have in mind is only 22 lines, I don't think it can be rejected on grounds of risk.

That has been the longstanding principle articulated by the core team through multiple proposals.

3 Likes

@xwu, please give the core team time to speak for themselves if they want to. I didn't follow it but I thought the previous conversations about adding options were about not wanting to open the floodgates to options to turn warnings off as you can with clang for bad imports from Objective-C though there may be other instances (and in general suppressing warnings is a pretty bad idea.) In this case it is an option to opt-into a warning which for me stands out as something people might be advised to use.

Speaking personally, not on the behalf of the core team:

We've generally aimed to reduce number of compiler flags in general. This makes build system / package management simpler, and reduces the fragmentation of the Swift community into subdialects. This is an issue that (for example) C++ has had where there are large groups that use -fno-exceptions and that code doesn't work optimally with camps that use exceptions.

I think that what you're asking for here (played forward) is a little bit different though - you're suggesting that we build linting tools into the compiler and make it possible to enable them as part of the flow.

I'm (speaking personally) not necessarily opposed to that - I think that a well considered framework for this and infra to support it could be useful. However, I would recommend starting from the framework (incl the overall UX, e.g. should these be a new kind of issue?) rather than adding a few one-off warnings.

-Chris

9 Likes

Agreed. TypeScript supports some lint checks in the TSConfig file. It would be great if Swift understood a SwiftConfig file that could carry similar configs. https://www.typescriptlang.org/tsconfig

After reading this forum for several weeks, I am fully in opposition to removing forced unwrap from the language as it is spelled and enabled today. It is clear that even in my own usage of forced unwrap, it is used in specific cases that make my life easier and cause zero harm. For others, the feature seems used for optimization (I don't actually know if forced unwrap is quicker than if let, but it seems reasonable to assume so. In my case, I use it for three reasons:

  1. When debugging <--- ESPECIALLY. There have been bugs in Swift that don't report something is nil, never break on the correct line, crash and I have NO idea why. Forced unwrap solved the problem SO many times and forced the app to crash correctly.
  2. When I have extremely large data structures that are guaranteed to have non nil optionals after a particular flag is set. The structures and classes I design are often large and arrive at such a condition after loading lots of external resources. I have ZERO desire to write an *if let when that condition is set.
  3. When I have a function that needs to initialize a variable (which is optional) and then I need to return that same object. e.g
class NotEverythingIsNonNilUntilAllThingsAreLoaded {
    var thing: SomeThing?
    func someFunctionThatIsCalledWhenSomethingIsNeeded() -> SomeThing {
        self.thing = SomeThing()
        self.thing!.cat = 100 //it was just initalized, I don't need to check if self.thing not nil
        return self.thing! //I'm definitely not wrapping this in an if let to find out what I already know.
    }
}

This is similar to what might be written in a lazy like getter minus the check to see if it was already initialized.

You're not going to make me believe I should have used an if let to return what I just created or that I need to create another variable just to return that variable with out have to use forced unwrap.

As for people who are very excited to add messages everywhere. This example does NOT require some sort of assertion with an even more detailed explanation of why it crashed. If it crashed, it wasn't the forced unwrap that caused it, it was some fault in the OS or Runtime.

Can you imagine how many programmers are going to just put "Something happened! Pretty impossible but it did, sorry your app crashed bro." or leave the messages as "" <-- those are blank quotes.

Furthermore, the default in Swift is to not use optionals at all. Most properties of a class or structure are asked to be initialized by default-- at least that is what the language almost forces us into--. So allowing the continued existence of the forced unwrap postfix operator spelled '!' is not anything to concern about.

We've already reached the edge of where the Nanny-programming language needs to hold power.

I understand what the intent was in this proposal, but it ignores the useful cases which make it handy and hardly dangerous. It's also overkill on safety.

Swift can not be perfect, it's written in C++ for God's sake.

No need for if let here

class NotEverythingIsNonNilUntilAllThingsAreLoaded {
    var thing: SomeThing?
    func someFunctionThatIsCalledWhenSomethingIsNeeded() -> SomeThing {
        let thing = SomeThing()
        thing.cat = 100 // no need for force unwrap
        self.thing = thing
        return thing
    }
}

Not disagreeing with the other things you wrote, just this one example seems like it can be solved.

Thank you!

2 Likes

There are times that a property has the force unwrap because it can’t be assigned a default or given a value at init but the class cannot function without it. I don’t think it’s a sign of lazy programming to do the force at the property declaration and not unwrap it 20 times in the body of the class.

I'm closing this thread, to stop the ongoing discussion of a topic the core team's already ruled on, since newcomers to the thread won't see the buried core team guidance.

To summarize:

  • The core team won't entertain any proposals to deprecate or remove force-unwraps (or force tries). That includes a mode enabled via a compiler flag.
  • This is for more than just source compatibility reasons: we believe they are a legitimately useful part of the language.
  • Style discussions about when to use or avoid them is a fine discussion topic but should be done over on the "Using Swift" section of the forums
  • There is a potential topic of whether the Swift compiler should gain more general "linting" capabilities, which could enable an effect similar to a targeted "warn on force-unwrap" capability. The core team doesn't have any official position on whether this is a good or bad idea yet if anyone wanted to explore it. Discussion of this could be done on a spin-off topic (probably in the discussion section rather than pitches).
32 Likes