Skip to content

Commit

Permalink
Rewrite a bit of documentation
Browse files Browse the repository at this point in the history
Make the first example that users see a bit more interesting, and move
a bit of documentation from the README into the objc2 crate's topics.
  • Loading branch information
madsmtm committed Jan 22, 2025
1 parent bf3bd63 commit 4c02571
Show file tree
Hide file tree
Showing 17 changed files with 378 additions and 269 deletions.
136 changes: 13 additions & 123 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,125 +3,21 @@
[![License](https://badgen.net/badge/license/MIT/blue)](./LICENSE.txt)
[![CI](https://github.com/madsmtm/objc2/actions/workflows/ci.yml/badge.svg)](https://github.com/madsmtm/objc2/actions/workflows/ci.yml)

The crates you're interested in is probably:
- [`objc2`], which provides bi-directional interop between Rust and
Objective-C, including support for defining Objective-C classes in Rust.
- [`objc2-*`] crates, which provides an autogenerated interfaces to Apple's
Objective-C frameworks (`AppKit`, `Foundation`, `Metal`, `WebKit`, you name
it, we [aim to have it](https://github.com/madsmtm/objc2/issues/393)).
- [`block2`], which provides bindings for Apple's C blocks, the
C-equivalent of a Rust closure.
- [`dispatch2`], which provides bindings for Apple's Grand Central Dispatch.

[`objc2`]: ./crates/objc2
[`objc2-*`]: ./framework-crates
[`block2`]: ./crates/block2
[`dispatch2`]: ./crates/dispatch2
**See [`objc2`] for top-level documentation**, including its [documentation on framework crates]. Also check out [`block2`] and [`dispatch2`].

[`objc2`]: https://docs.rs/objc2/
[documentation on framework crates]: https://docs.rs/objc2/latest/objc2/topics/about_generated/index.html
[`block2`]: https://docs.rs/block2/
[`dispatch2`]: https://docs.rs/dispatch2/


## Contact Us

Always feel free to [open an issue on GitHub](https://github.com/madsmtm/objc2/issues/new/choose).
Always feel free to [open an issue on GitHub](https://github.com/madsmtm/objc2/issues/new/choose) if you find a problem or have any questions.

If you prefer to have a more synchronous and less "formal" discussion, we have [a Matrix workspace](https://matrix.to/#/#objc2:matrix.org), feel free to ask any questions in the "Users" room.


## Goals

There are many conflicting priorities in an open-source project like this
(performance, ergonomics, understandability, portability, ...), but the
following two can be seen as the guiding principles for everything else in
this project.


### 1. Complete Soundness

The non-negotiable goal of these crates is to be completely "sound", meaning
it **must not be possible for safe Rust to cause undefined behaviour**!

As of January 2023, I (`@madsmtm`) have yet to find a single Rust crate or
project calling Objective-C soundly. Issues I have found include:
- Wrong method calling ABI.
- Wrong memory management (in the best cases they just leak a lot).
- Incorrect usage of `&mut`.
- Incorrect main-thread safety.

I don't state this to throw shade at these projects, it is very much
understandable! Objective-C and Rust have vastly different semantics, so
tackling these issues require vigilance and a focus that e.g. a system
clipboard crate just doesn't have!
Rather, I state it to provide reassurance: Rust's fearless concurrency can be
won back! This project's [approach to the issue][layered-safety] _works_,
and leaks, segfaults, race conditions and so on _can_ be completely
eliminated!

Needless to say, nothing is perfect, so if you think you've found a soundness
hole, please don't hesitate to report it on the [issue tracker]. Known
soundness holes (however theoretical) are tracked in the [`I-unsound`] label.

[layered-safety]: ./crates/objc2/src/topics/layered_safety.md
[`I-unsound`]: https://github.com/madsmtm/objc2/labels/I-unsound
[issue tracker]: https://github.com/madsmtm/objc2/issues/new


### 2. Idiomatic Rust

Soundness would be easy to achieve if we just marked every API as `unsafe`,
and called it a day (the precursor to this, `objc`, is basically sound).
However, that just pushes the burden onto you, the user, and then we're not
much better off!

As such, we'll try to be as safe and idiomatic as possible; using references
instead of pointers to represent objects and their (interior) mutability, `Option`
instead of `null`, doing memory management automatically instead of manually,
and so on (see again [these notes on "Layered Safety"][layered-safety]). These
abstractions should ideally be zero-cost, but this is of course a balancing
act against being ergonomic.

Some APIs in `objc2` and `block2` will still have to remain `unsafe`, so these
contain thorough `# Safety` sections, to let you know exactly which safety
guarantees you need to uphold.
The framework crates are a bit difficult in this regard, since they are mostly
autogenerated, which means that almost nothing can be safe by default!
However, we can still try to mitigate this problem by marking manually audited
functionality as safe (which we [need your help with][header-data]).

[header-data]: ./crates/header-translator/README.md


## Minimum Supported Rust Version (MSRV)

The _currently_ minimum supported Rust version is `1.71`; this is _not_
defined by policy, though, so it may change in at any time in a patch release.

Help us define a policy over in [#203].

[#203]: https://github.com/madsmtm/objc2/issues/203


## Migrating from `objc` and family

If size of your project is fairly small, it'll probably be easiest to just
jump straight into replacing everything to use the framework crates (when you
do, don't forget to [mark `unsafe` methods that you use as safe][header-data]
along the way).

If your project is large, you can consider upgrading in small steps, following
the changelog at each step of the way. For the most common cases, the
changelogs will include a helpful example on how to upgrade.

As an example you'd start by using `objc2` instead of `objc` in your
`Cargo.toml`:
```toml
[dependencies]
objc = { package = "objc2", version = "0.2.7" }
```

Afterwards, you can upgrade to the next release, in this case
`v0.3.0-alpha.0`, and make the required changes to your code following the
changelog. And so on, with every following release.


## License

The licensing of the project is a bit complicated, see
Expand All @@ -132,18 +28,12 @@ The licensing of the project is a bit complicated, see

This repository is a merge of the following projects, see reasoning for the
fork [here](https://github.com/SSheldon/rust-objc/issues/101):
- [`objc`](https://github.com/SSheldon/rust-objc)
- Renamed to `objc2`.
- [`objc-encode`](https://github.com/SSheldon/rust-objc-encode)
- Renamed to `objc2-encode`.
- [`objc_exception`](https://github.com/SSheldon/rust-objc-exception)
- Moved to `objc2::exception`.
- [`objc_id`](https://github.com/SSheldon/rust-objc-id)
- Moved to `objc2::rc`.
- [`objc-foundation`](https://github.com/SSheldon/rust-objc-foundation)
- Renamed to `objc2-foundation`.
- [`block`](https://github.com/SSheldon/rust-block)
- Renamed to `block2`.
- [`objc`](https://github.com/SSheldon/rust-objc), renamed to `objc2`.
- [`objc-encode`](https://github.com/SSheldon/rust-objc-encode), renamed to `objc2-encode`.
- [`objc_exception`](https://github.com/SSheldon/rust-objc-exception), moved to `objc2::exception`.
- [`objc_id`](https://github.com/SSheldon/rust-objc-id), moved to `objc2::rc`.
- [`objc-foundation`](https://github.com/SSheldon/rust-objc-foundation), renamed to `objc2-foundation`.
- [`block`](https://github.com/SSheldon/rust-block), renamed to `block2`.

These were created almost solely by [@SSheldon](https://github.com/SSheldon),
so a huge thanks for their fantastic work on these crates!
Expand Down
24 changes: 24 additions & 0 deletions crates/objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ unstable-winobjc = ["gnustep-1-8"]
# Link to ObjFW. Default in clang is version 0.8
unstable-objfw = []

# The hello world app example requires macOS.
#
# Workaround for Cargo not supporting target-specific examples.
unstable-requires-macos = []

[dependencies]
objc2-encode = { path = "../objc2-encode", version = "4.0.3", default-features = false }
objc2-proc-macros = { path = "../objc2-proc-macros", version = "0.1.3", optional = true }
Expand All @@ -147,12 +152,16 @@ objc2-core-foundation = { path = "../../framework-crates/objc2-core-foundation",
objc2-foundation = { path = "../../framework-crates/objc2-foundation", default-features = false, features = [
"std",
"NSArray",
"NSArray",
"NSDate",
"NSDictionary",
"NSDictionary",
"NSEnumerator",
"NSError",
"NSKeyValueObserving",
"NSNotification",
"NSNotification",
"NSNumberFormatter",
"NSObjCRuntime",
"NSObject",
"NSRunLoop",
Expand All @@ -165,8 +174,19 @@ libc = "0.2.158"

[target.'cfg(target_os = "macos")'.dev-dependencies]
objc2-app-kit = { path = "../../framework-crates/objc2-app-kit", default-features = false, features = [
"std",
"objc2-core-foundation",
"NSApplication",
"NSColor",
"NSControl",
"NSFont",
"NSGraphics",
"NSResponder",
"NSRunningApplication",
"NSText",
"NSTextField",
"NSView",
"NSWindow",
] }

[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dev-dependencies]
Expand All @@ -181,6 +201,10 @@ core-foundation = "0.10.0"
name = "autorelease"
harness = false

[[example]]
name = "hello_world_app"
required-features = ["unstable-requires-macos"]

[package.metadata.docs.rs]
default-target = "aarch64-apple-darwin"
features = ["exception"]
Expand Down
132 changes: 132 additions & 0 deletions crates/objc2/examples/hello_world_app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#![deny(unsafe_op_in_unsafe_fn)]
use std::cell::OnceCell;

use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{define_class, msg_send, DefinedClass, MainThreadOnly};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSAutoresizingMaskOptions,
NSBackingStoreType, NSColor, NSFont, NSTextAlignment, NSTextField, NSWindow, NSWindowDelegate,
NSWindowStyleMask,
};
use objc2_foundation::{
ns_string, MainThreadMarker, NSNotification, NSObject, NSObjectProtocol, NSPoint, NSRect,
NSSize,
};

#[derive(Debug, Default)]
struct AppDelegateIvars {
window: OnceCell<Retained<NSWindow>>,
}

define_class!(
// SAFETY:
// - The superclass NSObject does not have any subclassing requirements.
// - `Delegate` does not implement `Drop`.
#[unsafe(super = NSObject)]
#[thread_kind = MainThreadOnly]
#[name = "Delegate"]
#[ivars = AppDelegateIvars]
struct Delegate;

// SAFETY: `NSObjectProtocol` has no safety requirements.
unsafe impl NSObjectProtocol for Delegate {}

// SAFETY: `NSApplicationDelegate` has no safety requirements.
unsafe impl NSApplicationDelegate for Delegate {
// SAFETY: The signature is correct.
#[unsafe(method(applicationDidFinishLaunching:))]
fn did_finish_launching(&self, notification: &NSNotification) {
let mtm = self.mtm();

let app = unsafe { notification.object() }
.unwrap()
.downcast::<NSApplication>()
.unwrap();

let text_field = unsafe {
let text_field = NSTextField::labelWithString(ns_string!("Hello, World!"), mtm);
text_field.setFrame(NSRect::new(
NSPoint::new(5.0, 100.0),
NSSize::new(290.0, 100.0),
));
text_field.setTextColor(Some(&NSColor::colorWithSRGBRed_green_blue_alpha(
0.0, 0.5, 0.0, 1.0,
)));
text_field.setAlignment(NSTextAlignment::Center);
text_field.setFont(Some(&NSFont::systemFontOfSize(45.0)));
text_field.setAutoresizingMask(
NSAutoresizingMaskOptions::ViewWidthSizable
| NSAutoresizingMaskOptions::ViewHeightSizable,
);
text_field
};

// SAFETY: We disable releasing when closed below.
let window = unsafe {
NSWindow::initWithContentRect_styleMask_backing_defer(
NSWindow::alloc(mtm),
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(300.0, 300.0)),
NSWindowStyleMask::Titled
| NSWindowStyleMask::Closable
| NSWindowStyleMask::Miniaturizable
| NSWindowStyleMask::Resizable,
NSBackingStoreType::Buffered,
false,
)
};
// SAFETY: Disable auto-release when closing windows.
// This is required when creating `NSWindow` outside a window
// controller.
unsafe { window.setReleasedWhenClosed(false) };

// Set various window properties.
window.setTitle(ns_string!("A window"));
let view = window.contentView().expect("window must have content view");
unsafe { view.addSubview(&text_field) };
window.center();
unsafe { window.setContentMinSize(NSSize::new(300.0, 300.0)) };
window.setDelegate(Some(ProtocolObject::from_ref(self)));

// Show the window.
window.makeKeyAndOrderFront(None);

// Store the window in the delegate.
self.ivars().window.set(window).unwrap();

app.setActivationPolicy(NSApplicationActivationPolicy::Regular);

// Activate the application.
// Required when launching unbundled (as is done with Cargo).
#[allow(deprecated)]
app.activateIgnoringOtherApps(true);
}
}

// SAFETY: `NSWindowDelegate` has no safety requirements.
unsafe impl NSWindowDelegate for Delegate {
#[unsafe(method(windowWillClose:))]
fn window_will_close(&self, _notification: &NSNotification) {
// Quit the application when the window is closed.
unsafe { NSApplication::sharedApplication(self.mtm()).terminate(None) };
}
}
);

impl Delegate {
fn new(mtm: MainThreadMarker) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(AppDelegateIvars::default());
// SAFETY: The signature of `NSObject`'s `init` method is correct.
unsafe { msg_send![super(this), init] }
}
}

fn main() {
let mtm = MainThreadMarker::new().unwrap();

let app = NSApplication::sharedApplication(mtm);
let delegate = Delegate::new(mtm);
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));

app.run();
}
7 changes: 4 additions & 3 deletions crates/objc2/src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! # Support for type-encodings.
//!
//! This module contains traits for annotating types that has an Objective-C
//! type-encoding: Specifically [`Encode`] for structs/numeric types and
//! [`RefEncode`] for references.
//! The Objective-C runtime includes encodings for each method that describe
//! the argument and return types. This module contains traits for annotating
//! types that has an Objective-C type-encoding: Specifically [`Encode`] for
//! structs/numeric types and [`RefEncode`] for references.
//!
//! Additionally, this exports the [`Encoding`] and [`EncodingBox`] types from
//! [`objc2-encode`][objc2_encode], see that crate for a few more details on
Expand Down
Loading

0 comments on commit 4c02571

Please sign in to comment.