How to create a MacOS (Cocoa) App in Rust (Part 1)

There is surprisingly little information on how to actually build something of use in Cocoa with Rust. I worked my way through it with a lot of perseverance so hopefully I can save you, dear reader some of the frustration along the way. At the end of this Part 1 you’ll have a running app with a button that executes a block of code.

First, a bit of the state of the union that I’ve found. There is an ongoing project to wrap some of the Appkit and Foundation libraries going on over at the Mozilla sponsored Servo project. Specifically, Core Foundation RS. I’ve added a few small PRs to that. That project builds upon the work done by Steven Sheldon’s Rust-Objc

Ok, let’s get started. As with most things Rust, you’re going to need to to add the relevant dependancies to your Cargo.toml as of this publication

[dependencies]
"cocoa" = "0.19.1"
"core-graphics" = "0.17.3"
objc = "0.2.3"

From here let’s copy / paste the hello world example

extern crate cocoa;

use cocoa::base::{selector, nil, NO};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSAutoreleasePool, NSProcessInfo,
                        NSString};
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSWindow,
                    NSBackingStoreBuffered, NSMenu, NSMenuItem, NSWindowStyleMask,
                    NSRunningApplication, NSApplicationActivateIgnoringOtherApps};

fn main() {
    unsafe {
        let _pool = NSAutoreleasePool::new(nil);

        let app = NSApp();
        app.setActivationPolicy_(NSApplicationActivationPolicyRegular);

        // create Menu Bar
        let menubar = NSMenu::new(nil).autorelease();
        let app_menu_item = NSMenuItem::new(nil).autorelease();
        menubar.addItem_(app_menu_item);
        app.setMainMenu_(menubar);

        // create Application menu
        let app_menu = NSMenu::new(nil).autorelease();
        let quit_prefix = NSString::alloc(nil).init_str("Quit ");
        let quit_title =
            quit_prefix.stringByAppendingString_(NSProcessInfo::processInfo(nil).processName());
        let quit_action = selector("terminate:");
        let quit_key = NSString::alloc(nil).init_str("q");
        let quit_item = NSMenuItem::alloc(nil)
            .initWithTitle_action_keyEquivalent_(quit_title, quit_action, quit_key)
            .autorelease();
        app_menu.addItem_(quit_item);
        app_menu_item.setSubmenu_(app_menu);

        // create Window
        let window = NSWindow::alloc(nil)
            .initWithContentRect_styleMask_backing_defer_(NSRect::new(NSPoint::new(0., 0.),
                                                                      NSSize::new(200., 200.)),
                                                          NSWindowStyleMask::NSTitledWindowMask,
                                                          NSBackingStoreBuffered,
                                                          NO)
            .autorelease();
        window.cascadeTopLeftFromPoint_(NSPoint::new(20., 20.));
        window.center();
        let title = NSString::alloc(nil).init_str("Hello World!");
        window.setTitle_(title);
        window.makeKeyAndOrderFront_(nil);
        let current_app = NSRunningApplication::currentApplication(nil);
        current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps);
        app.run();
    }
}

Give it a go with

cargo run --release

Alright if that worked you should see something similar to this…

Classic Hello World

For me, that was amazing, no Xcode and we have an app up on OSX, my mind started imagining with the possibilities.

Ok, so to wrap up this tutorial we’re going to create a button that has an action and a target, simple right. Well, in short no. We need to dynamically create all the classes on the OBJ-C side, since we’re going to be mostly just passing messages.

First off, we’re going to create a custom class so that we can place code, objc-rust provides this functionality. Warning though we’re going to be running all unsafe code, as usage of raw pointers is the name of the game.

use objc::declare::ClassDecl;
#[macro_use]
extern crate objc;

    fn register_button() {
        unsafe {
            let superclass = class!(NSButton);
            let mut decl = ClassDecl::new("HelloWorldButton", superclass).unwrap();

            extern fn clicked(_this: &Object, _cmd: Sel) {
                unsafe {
                    println!("clicked {:?}", _this);

                     let alert:*const Object = msg_send!(class!(NSAlert), alloc);
                     let alert:*const Object = msg_send!(alert, init);


                     let alert_title = NSString::alloc(nil).init_str(&"Hello World".to_string()).autorelease();
                     let alert_body = NSString::alloc(nil).init_str(&"You Clicked Me!").autorelease();

                     let _alert_id: id = msg_send!(alert, setMessageText:alert_title);
                     let _alert_id: id = msg_send!(alert, setInformativeText:alert_body);
                     let _alert_id: id = msg_send!(alert, runModal);

                }
            }
            let clicked: extern fn(&Object, Sel) = clicked;
            decl.add_method(sel!(clicked), clicked);
            decl.register();
        }
    }

Ok, there’s a lot going on there, so let’s break it down. First we’re adding some imports into the file, for the lower level rust-objc dance we’re going to do. Now into the register_button fn. As I referenced before, there’s the unsafe block, get used to those.

Our first bit of interaction with objc starts here. The class! macro instantiates a class of the referenced name, objc must already have that declared, and in this case, it does as part of the standard library, NSButton.

Now some magic here, we’re going to dynamically create a custom class that we can instantiate ourselves later. We’re calling that HelloWorldButton and it’s a descendant of NSButton.

Now that we have a dynamic class let’s add some structure to it, below we declare the external fn that is to be called later, which will cause the below alert.

You clicked me!
       fn create_button(frame: NSRect, title:String) -> *mut Object {
        unsafe {

            let button: *const Object = msg_send![class!(HelloWorldButton), alloc];
            let button_with_frame: *mut Object = msg_send![button, initWithFrame:frame];
            let title_as_nsstring = NSString::alloc(nil).init_str(&title.to_string()).autorelease();
            let _title_return: id  = msg_send![button_with_frame, setTitle:title_as_nsstring];
            let _hello_world_button_msg: id = msg_send![button_with_frame, setTarget:button_with_frame];
            let _hello_world_button_msg: id = msg_send![button_with_frame, setAction:sel!(clicked)];
            button_with_frame
        }
    }

This block is hopefully a bit more straightforward, notice the re-use of HelloWorldButton which we registered above, we create the button & add the title. One tricky thing to remember is you need to create an objc string. Finally we’re going to direct objc to point the target and action of the click, this took me a long time to figure out, hopefully this will save someone hours of frustration. the sel! macro is interesting, once you have declared something on a native or custom class, it behaves the same a objc with unlimited parameters separated by “:” tokens.

Finally, let’s put it altogether.

register_button();

let hello_world_button_frame:NSRect = NSRect::new(NSPoint::new( 0., 30.), NSSize::new(20., 10.));
let hello_world_button = create_button(hello_world_button_frame, "Click Me".to_string());
...
window.setContentView_(hello_world_button);


We call the functions we created earlier to register and create, and while this won’t work for a larger app we’re just going to make the button be the contentView for the window at this point. NSButton derives at some point from NSView so it’s all good.