Availability and the Standard Library

(Ben Cohen) #1

Hi everyone,

Now that Swift can ship in the OS, all new features in the standard library will need to have an availability annotation for platforms that do so, to ensure that the feature is only used when it’s available.

For example:

// in the standard library
@available(macOS 10.14, iOS 12, watchOS 5, tvOS 12, *)
func funkyNewFeature() -> Int { return 42 }

// and in user code
if #available(iOS 12, *) {
  total += funkyNewFeature()
} else {
  // OS too old, no new feature for you, 
  // implement some workaround...
  total += 42
}

This presents a challenge for ongoing development of the standard library, because those OS versions do not yet exist. Even if you chose to guess in which future version number of an OS a new feature will land, you would not be able to test your new function because you will not be running that version of the OS.

To facilitate this, a special case has been added to availability checking for platforms that now ship as part of an OS release i.e. macOS, iOS, tvOS, and watchOS. When checking the version of the OS at runtime, major version 9999 will always succeed. The following will now print “I’m available!" on a compiler built from master, whereas in Swift 5.0 it will build, but fail at runtime:

@available(macOS 9999, *)
func f() { print("I'm available!") }

if #available(macOS 9999, *) {
  f()
} else {
  fatalError("nope")
}

When releasing a new version of the OS with the latest standard library, the platform owner will go through all newly available functions and update their availability (and any associated tests) with the appropriate real version number.

Going forward, when raising PRs against the standard library, please use this convention in new functions (including internal-but-inlinable ones) as well as when writing tests.

Thanks!

Ben

5 Likes
(Chris Lattner) #3

It looks like an upthread post was deleted, but I fundamentally agree with it.

Have you considered the approach of adding availability info to the standard library that indicates what swift version the standard library symbol corresponds to, then have an apple-driven mapping that maps Apple OS deployment targets to Swift versions?

9 Likes
(Alexander Momchilov) #4

I'm not a fan of deriving function availability from the os versions it happens to have been introduced in? Why can't we design this in a way that we can just directly query function availability?

if @available(funkyNewFeature()) {
     useIt()
}
4 Likes
(Jordan Rose) #5

Fun fact: Apple used to recommend this pattern for Objective-C! That is, rather than checking the version, you'd just see if a particular method or declaration exists at run time. This was deprecated for a few reasons:

  • It was a lot harder for the compiler to enforce because it was done manually. (This reason doesn't really apply here, because we control the syntax used to do the checks.)

  • Using versions means that implications work, i.e. "both UIToasterController and NSToast were introduced in the same OS, so I don't need two different checks" or "NSToast was introduced on a newer OS than NSBread, so I can assume NSBread already exists when I extend NSToast."

  • @available isn't the only source of information about what's available; your minimum deployment target also matters.

  • Depending on how you check for the presence of a feature, it used to be possible to get an answer of "yes, this feature is present" before the feature was publicly released, which is a problem if it was only implemented for very specific use cases on an older OS!

However, if you're proposing that the compiler expand #available(someDecl) to #available(the OS availability declared on someDecl), that could work. I'm personally against that because I think it's clearer to see what's actually implied by that check, but it's a lot more straightforward to implement. (@available could work the same way, inheriting the availability of another declaration, but that's a lot more dangerous when bringing declarations to a new platform.)

(This response is specific to using a declaration for availability checking, not to any alternative syntax besides OSs.)

2 Likes
(Jean-Daniel) #6

Having #available(somedecl) would make things easier, especially for feature early adopters, as they won't have to update all checks to match when the @availability declaration is updated.

Having to know the exact system version where a feature has been introduced is cumbersome, especially if you target many platforms (macOS, iOS, watchOS, …)

And of course, if this is supported, it should be based on the "#available(someDecl) = #available(the OS availability declared on someDecl" model.

1 Like