rust-objc icon indicating copy to clipboard operation
rust-objc copied to clipboard

How to call @available?

Open jbis9051 opened this issue 2 years ago • 8 comments

if (@available(macOS 10.15, *)){
   /// whatever
}

How would I do this in rust?

jbis9051 avatar Apr 06 '22 19:04 jbis9051

Your question is a little off topic from this repo but I don't know of much better place.

The Swift @available attribute and the objective-c API_AVAILABLE aren't quite the same in rust. The objective-c macro I think is evaluated at compile time. I'm not sure how the @available attribute works in swift.

I think the best way to emulate this type of thing is to use the sysinfo crate's os_long_version with something like...

let s = System::new();
if(s.os_version().starts_with("11.0")) { // for macOS
    // whatever
}

The implementation for long_os_version shows a few of the options.

You may also use the #[cfg(target_os("macos"))] attribute for this type of thing but it doesn't give you OS version granularity.

simlay avatar Apr 06 '22 20:04 simlay

@simlay Thank you for your reply. I agree, it's a bit off topic but wasn't sure where to ask.

I could be wrong, but I believe @available in ObjC is runtime. https://stackoverflow.com/a/47334301/7886229

The sysinfo crate would be a last resort. I'd like to tap into the same thing @available uses.

jbis9051 avatar Apr 07 '22 16:04 jbis9051

Hmm. The docks from clang: https://clang.llvm.org/docs/LanguageExtensions.html#objective-c-available

Before LLVM 5.0, when calling a function that exists only in the OS that’s newer than the target OS (as determined by the minimum deployment version), programmers had to carefully check if the function exists at runtime, using null checks for weakly-linked C functions, +class for Objective-C classes, and -respondsToSelector: or +instancesRespondToSelector: for Objective-C methods. If such a check was missed, the program would compile fine, run fine on newer systems, but crash on older systems.

This implies that it may actually do the check for you at runtime. I'm not sure how to send that to the objective-c runtime. @madsmtm has been doing some interesting work on objc2 and may have some thoughts.

simlay avatar Apr 07 '22 17:04 simlay

However, https://godbolt.org/z/s8Gzx3hME, this seems to indicate that it's compile time if I am reading the assembly right

jbis9051 avatar Apr 07 '22 17:04 jbis9051

@available is a compile-time directive, it conditionally compiles the code depending on the deployment target (can be set using the MACOSX_DEPLOYMENT_TARGET / IPHONEOS_DEPLOYMENT_TARGET / ... environment variables).

I am working on a crate to help do this in Rust, as it would be beneficial for a few things only available in newer versions, namely certain optimizations, linking to frameworks and some C symbols.

However, it'll probably take a while before it is done, so I would recommend the sysinfo crate for now.

madsmtm avatar Jun 11 '22 02:06 madsmtm

@madsmtm I'd like to be able to do runtime checks as well.

Something like from https://nshipster.com/available/:

@available(iOS 13.0, *)
final class CustomCompositionalLayout: UICollectionViewCompositionalLayout { … }

func createLayout() -> UICollectionViewLayout {
    if #available(iOS 13, *) { // runtime check!
        return CustomCompositionalLayout()
    } else {
        return UICollectionViewFlowLayout()
    }
}

I'm not familiar with Swift (or low level stuff) enough to know how exactly that works.

Is it dynamic linking or something? Is that possible now in Rust? If not, would the crate you are making be able to do runtime checks as well?

jbis9051 avatar Jun 11 '22 04:06 jbis9051

Actually it's a little more complex than I thought: Objective-C's @available / Swift's #available acts as both a compile-time and a run-time check!

As an example, if you have the following Objective-C code:

if (@available(macOS 10.10, *)) {
    printf("Hello, macOS > 10.10!\n");
}
if (@available(macOS 10.14, *)) {
    printf("Hello, macOS > 10.14!\n");
}

And tell clang that the deployment version is 10.12, it can remove the first @available check at compile-time, but has insert a runtime check for the second @available. Note that removing the first branch at compile-time is an optimization - it could choose not to!

Check out a tweaked version of your godbolt example, where we set -mmacos-version-min=10.12 (equivalent to setting environment variable MACOSX_DEPLOYMENT_TARGET=10.12).

madsmtm avatar Jun 11 '22 07:06 madsmtm

But again, using the sysinfo crate you can essentially do the exact same runtime checks (albeit less efficiently).

madsmtm avatar Jun 11 '22 07:06 madsmtm