Crates.io | objective-rust-macros |
lib.rs | objective-rust-macros |
version | 0.1.0 |
source | src |
created_at | 2024-03-12 16:48:48.916655 |
updated_at | 2024-03-12 16:48:48.916655 |
description | Macros for objective-rust. |
homepage | |
repository | https://github.com/Bright-Shard/objective-rust/tree/main/objective-rust-macros |
max_upload_size | |
id | 1170794 |
size | 55,152 |
objective-rust is a blazingly-fast, dependency-free Objective-Cringe FFI library for Rust. Unlike other Objective-C libraries, objective-rust allows you to use Objective-C classes as regular Rust types; it doesn't try to introduce weird Objective-C syntax into Rust.
Here's a simple demo program, which imports and uses the NSApplication
class from AppKit:
// The `objrs` attribute macro, which generates FFI for you
use objective_rust::objrs;
use std::ptr::NonNull;
// Declare Objective-C types with the `#[objrs]` macro and an
// `extern "objc"` block
#[objrs]
extern "objc" {
// The class to import
type NSApplication;
// Methods for instances of the class (takes &self or &mut self)
fn run(&self);
// Static methods for the class itself (doesn't take self)
fn sharedApplication() -> *mut Self;
// You can also change which method objective-rust will
// call internally, with the selector attribute
#[selector = "sharedApplication"]
fn shared() -> *mut Self;
}
fn main() {
// Call class methods just like associated functions in Rust
let shared = NSApplication::shared();
// `from_raw` is added by objective-rust, and just converts a pointer
// to an instance into a useable Rust type
// It requires a non-null pointer
let shared = NonNull::new(shared).unwrap();
let shared = unsafe { NSApplication::from_raw(shared) };
// Call instance methods just like methods in Rust
shared.run();
}
// Without this, Rust won't link to AppKit and AppKit classes won't get loaded.
// This doesn't import anything from AppKit. It just links to it so we can use
// its classes.
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
Everything from the way types and methods are declared (in extern
blocks) to the way they're used (associated functions and methods) to their behaviour (release
is automatically called when an instance is dropped) is designed to feel like native Rust. The only real difference is having to construct an instance from a raw pointer.
By the way, the objrs
macro also works on entire modules:
#[objrs]
mod ffi {
// All `extern "objc"` blocks in the module will get parsed.
extern "objc" {
type NSApplication;
#[selector = "sharedApplication"]
fn shared() -> *mut Self;
fn run(&self);
}
// This isn't in an `extern "objc"` block, so it is ignored/not processed
pub struct SomeType {}
}
use ffi::{NSApplication, SomeType};
In the future, if crate-level macros are ever stabilised, you can add #![objective_rust::objrs]
to the top of a crate, and then use extern "objc"
anywhere in that crate to generate FFI.
Note: If you want to see this in action, run cargo install cargo-expand
, then cargo expand
on any objective-rust project. It'll show the macro output.
objective-rust uses Apple's Objective-C Runtime API to interact with Objective-C classes. Unlike other Objective-C crates, or even Objective-C itself, it doesn't rely on message passing; instead, objective-rust uses the API to get the underlying C function for Objective-C methods and calls that function directly.
objective-rust will use thread local storage to store pointers to any Objective-C methods imported via the objrs
macro. When you call a method, it loads that function pointer from thread local storage, and calls the function with the appropriate arguments.
When you declare a type in an extern "objc"
block, objective-rust will generate these three structs for it (with
<class>
: A struct with the same name as the class. This has all of the methods implemented for it, and is the type you use in your program. It's the "Rust wrapper type" for an Objective-C class.<class>Instance
: An opaque type that represents an Objective-C instance of the class you're importing. This just exists to semantically separate the Objective-C type from the Rust wrapper type; it has no methods or other functionality.<class>VTable
: A struct used by objective-rust to store function pointers for all of <class>
's methods.When you declare a function in an extern "objc"
block, objective-rust adds a field to the <class>VTable
struct for that function. The field stores the selector for that function and a pointer to the function itself. objective-rust will then store an instance of <class>VTable
in thread-local storage.
When you call a method in <class>
, objective-rust gets the function pointer and selector for the function from the <class>VTable
instance in thread-local storage, and calls the function with all the arguments you give it.
Stuff that may be helpful to anyone else working with the Objective-C runtime:
extern "C" fn(instance: *mut Self, selector: Selector, <function arguments>)
- in short, the first argument is always the instance this method is running on (the self
pointer), the second argument is the selector of the function, and anything after that is the function's actual arguments (if it has any).class_getMethodImplementation
function.class_getMethodImplementation
, you pass the metaclass for the class
argument. You can get a metaclass with objc_getMetaClass
.