Open madsmtm opened 2 years ago
There are effectively two versions that affect availability and what we should do: the deployment target and the SDK version.
To illustrate, let's assume an API fn foo() { ... }
that's introduced in some version i
, later deprecated in some version d
, and finally removed in some version r
*.
Deployment target | SDK version | fn declaration |
foo() |
if_available(introduced_version_or_above) { foo() } |
---|---|---|---|---|
..i |
..i |
None | Fails | Fails |
..i |
i..d |
fn foo() { assert_available(introduced_version); ... } |
Panics | Success |
..i |
d..r |
#[deprecated] fn foo() { assert_available(introduced_version); ... } |
Warning + Panics | Warning |
..i |
r.. |
None* | Fails | Fails |
i..d |
i..d |
fn foo() { ... } |
Success | Success |
i..d |
d..r |
#[deprecated] fn foo() { ... } |
Warning | Warning |
i..d |
r.. |
None* | Fails | Fails |
d..r |
d..r |
#[deprecated] fn foo() { ... } |
Warning | Warning |
d..r |
r.. |
None* | Fails | Fails |
r.. |
r.. |
None* | Fails | Fails |
*Note that I don't really know a case where an API has been removed, but we can handle it if need be.
From the above, we can see that the logic in cfg_available
is basically:
Deployment target < Introduced version
-> Add assertion that the user has checked the availability before calling
Deprecation version <= SDK version
-> Mark the item as deprecated
Removal version <= SDK version
-> Remove the item
We should similarly try to support such attributes on each impl
item
EDIT: Moved to https://github.com/madsmtm/objc2/issues/285
We need to do both a static and a dynamic check - the static one is fairly straightforward, but I'm unsure of how we should do the dynamic one?
I would have thought that clang just called some library function, but it's actually implemented as a compiler builtin, which calls into CoreFoundation and reads /System/Library/CoreServices/SystemVersion.plist
, see os_version_check.c
.
Do we really need that as well? Or can we perhaps get by with just reading kCFCoreFoundationVersionNumber
(or maybe NSFoundationVersionNumber
)?
Actually, turns out the @available
in Objective-C is much newer than i thought - see the initial proposal here.
We could reconsider the check to be just is_available!(MyClass::doSomething)
, but I think the reasoning in that post (better for control-flow) apply to us as well, at least if we do end up getting something like contexts and capabilities. The optimization potential once the user switches to a higher deployment target is also nice.
Swift's #available
works similarly, see Availability.swift
and Availability.mm
.
Though they use (a weak symbol to) os_system_version_get_current_version
, are we allowed to do that too? And where is that even defined?
There's a new RFC that would help with OS compile-time detection: https://github.com/rust-lang/rfcs/pull/3379 (though we'd still want a macro for the compile-time + runtime detection fallback)
A short, very incomplete list I made a while ago on different behaviour in clang
based on the deployment target (clang v13 source):
My conclusion was that it's not super important for the runtime i.e. objc2
to know the deployment target statically, especially not after https://github.com/madsmtm/objc2/pull/530.
(That said, it's of course still very important for the user to know, so we still need this feature in some shape or form).
I wrote some ideas for how the availability check might work internally in this playground.
We have rustc --print deployment-target
for retrieving the current deployment target, and since https://github.com/rust-lang/cc-rs/pull/848 the cc
crate has been automatically using that for setting the deployment target for build scripts. I've opened https://github.com/rust-lang/cargo/issues/13115 for making the deployment target even more easily accessible from build scripts.
This also affects the "unstable-static-class"
feature, we'd have to make sure to weakly link the class if the deployment target is not high enough for it to always be available.
Have been working on this recently, I think there is value in exposing just the runtime lookup functionality of this (i.e. an available!
macro), and deferring the compile-time checks to later.
Another idea would be to have a flag ASSUME_LOW_VERSION
or something that the user can set, and which makes all availability checks return the deployment target + makes all APIs that require newer versions panic instead. That way, the user could still test their application for invalid assumptions about availability, without having the hardware to test it on.
Yet another idea would be to develop this in rustc
itself.
Yet another idea would be to develop this in rustc itself.
I suggest that whenever the topic of OS version checking or availability come up in github.com/rust/
, like before in https://github.com/rust-lang/rfcs/pull/3379#issuecomment-1850531357 (you also commented there). Maybe we should fork/revive that RFC in a smaller form? Would love to see std
have an available!
macro if nothing else that does the mix of static deployment target and runtime checking.
Had some experiments earlier in the year too looking at how possible it would be to duplicate the codegen LLVM injects into builds to handle dynamic checking at runtime via #available
and nothing there looked unstable enough to be impossible...
Yeah, I'm actually in the process of writing the available!
macro right now that does static deployment target + runtime checking, see https://github.com/madsmtm/objc2/pull/661 for my current progress. And while doing this, it has indeed become apparent that such a macro really belongs in std
.
Alongside that, I'm considering implementing a #[cfg(rustc_os_version(macos = 10.14.7, ios = ...))]
attribute or similar to do the cfg
-based checking of the deployment target. That way we can experiment with it in std
first, before really figuring out the RFC that's needed to get it stable. I'll probably need to file an MCP for that first.
Alongside that, I'm considering implementing a #[cfg(rustc_os_version(macos = 10.14.7, ios = ...))] attribute or similar
I'd try and keep rustc
or cargo
out of it tbh. You can set an env variable for rustc
to read today but some of the proposals want to make this cargo
configurable, so keeping the attribute agnostic might help it age better.
In Apple's Objective-C headers, most classes and methods are annotated with an availability attribute such as
API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0))
; this is absolutely a great idea (!!!), it very cleanly allows you to mark which APIs you may use, and which ones are not usable on your current deployment target.We should have some way of doing the same as
@available
in Objective-C; however, since we are not a compiler likeclang
is, this is quite tricky!A quick demonstration of what I want:
Prevent the user from using said API if their deployment target is not high enough:
For this, Contexts and capabilities come to mind, similar to how it would be useful for autorelease pools, but alas, we can't do that yet, so this will probably end up as a debug assertions runtime check.
See also the original https://github.com/SSheldon/rust-objc/issues/111, a WIP implementation can be found in https://github.com/madsmtm/objc2/pull/212.