Motivation
The semantics of *
in the current unavailability proposal are hard to express informally, not least because the actual proposed meaning is that something is not going to happen, rather than that it is. We haven’t yet seen a straightforward statement of the meaning that doesn’t provoke a “wait … what?” response, even if that’s followed by “oh … OK” some time later.
I’ve come to think that we can’t come to agreement on *
for unavailability because there’s an underlying problem with #available
, and we must solve that problem before settling the design of #unavailable
.
The problem with #available
is that it’s too easy read as a complex Boolean expression. That introduces confusion because combining the sub-parts is not as easy at it seems, especially when we look at negated (unavailability) conditions with similar syntax.
For example, an availability condition that mentions a specific OS version, such as #available(iOS 14, …)
is too easy to read as asking whether the following code should run if iOS 14+ is available. If it is not, what does that mean in the context of the whole expression? That iOS is not available? That iOS is available but it’s not version 14+?
Those questions have answers in the current design, but the answers are not as accessible as might be desired.
Proposed Solution: Availability
We start by fixing #available
with two small changes. One is a syntax change to suggest improved semantics, and the other is a semantic change to the troublesome “other platforms” part of an availability check.
(1) We change the syntax to suggest that #available
is more like a switch
over possible platforms than a Boolean expression combined by ||
. The suggested change is subtle. Instead of this:
if #available(iOS 14, watchOS 7, *) { … }
we write this:
if #available(iOS: 14, watchOS: 7, *: true) { … }
The idea is to emphasize that only one of the listed checks is made, depending on the compilation/run-time target: all the others are skipped. There is no sense in which the different parameters are combined as a Boolean result. (“The commas don’t mean OR”.)
This allows us to reason independently about each platform. Instead of reading this as “if available on iOS 14 or watchOS 7 or any version of any other platform”, we can read this as “here is what happens on iOS; here is what happens on watchOS; here is what happens on other platforms”. The different is slight, but important when we try to introduce syntax for unavailability checks.
(2) As shown above, the syntax of the catch-all case changes from *
to *: true
. It becomes a catch-all platform case, along with a value that says what to do in that case.
There is also a semantic change here. By specifying value true
, we indicate that there is no version check here (avoiding a wordy statement about how the check works, involving the deployment target version, along with a version comparison that cannot actually fail). We are simply specifying that for other platforms, the code associated with the if
statement will execute.
Note that a platform case for *
is always required for #available
, similar to the current rule.
Those are the only two necessary changes. However, with these changes comes the opportunity to round out the possibilities, and to introduce some conveniences.
— We allow *: false
as an alternative to *: true
, indicating that the body of the if
is not executed for unlisted platforms. This is not possible at present, and may be an unlikely case, but I see no reason to disallow it.
— We allow (well, re-allow) *
as shorthand for *: true
. Since it’s a very common case, the shorter form is useful.
— We allow specific platform names to have values true
and false
instead of version numbers, if desired. true
is roughly equivalent to testing against version 0; false
is roughly equivalent to testing against version maxInt
.
All of this means we can have availability expressions that look like this, with a mixture of true
and false
cases:
if #available(iOS: 14, tvOS: true, watchOS: false, *: true) { … }
This allows the programmer to easily customize how the conditional code is used according to platform (and version, of course). Note that the tvOS
case isn’t actually necessary here, but there’s no harm in allowing it to be specified explicitly, and it may be useful documentation sometimes.
Proposed Solution: Unavailability
The proposal for unavailability becomes almost identical to the current proposal, apart from being modified to match the above syntax.
The one important difference is that the *
platform case is no longer required, though it may optionally be specified as true
or false
as desired. The reverse of this availability check:
if #available(iOS: 14, watchOS: 7, *: true) { … }
now looks like this:
if #unavailable(iOS: 14, watchOS: 7) { … }
In other words, the body of the if
is executed on the listed platforms only , if the relevant platform’s version check fails. This example is equivalent to:
if #unavailable(iOS: 14, watchOS: 7, *: false) { … }
The following example is meaningful but means something different:
if #unavailable(iOS: 14, watchOS: 7, *: true) { … }
Note that *
as shorthand for *: true
(or *: false
— which would it be?) is not allowed for #unavailable
, because of the possibility of confusion.
As with #available
, a true
condition indicates that the body of the if
will be executed on that platform, and false
indicates that it will not . There is no confusing reference to the deployment target version, with a test that always fails.
Benefits
The above proposal removes any uncertainty or ambiguity about when a given platform test will be applied, and about what happens when a version test evaluates to false
.
There is still a duality between #available
and #unavailable
. Here it is in abbreviated and full forms:
— The reverse of #available(iOS: 14, watchOS: 7, *)
is #unavailable(iOS: 14, watchOS: 7)
.
— The reverse of #available(iOS: 14, watchOS: 7, *: true)
is #unavailable(iOS: 14, watchOS: 7, *: false)
.
And another duality:
— The reverse of #available(iOS: 14, watchOS: 7, *: false)
is #unavailable(iOS: 14, watchOS: 7, *: true)
.
Source Compatibility
If accepted, the above syntax would become the only syntax for #unavailable
, and the preferred syntax for #available
. The old syntax for #available
would be taken to mean the same thing as the new syntax without the new spellings, and would have the same effect if not exactly the same semantics (in terms of deployment target versions).
Later, the old syntax could be deprecated with a warning and an easy fixit, and eventually the old syntax could be removed from the language.