Rust Away Mac OS

Posted on  by
8 March 2016
  1. Rust Away Mac Os Update
  2. Rust Away Mac Os Catalina
  3. Rust Away Mac Os X

I managed to get my hands on Raspberry Pi 3B. I found it to be a great opportunity to mess around with Rust. But some problems appeared right away: how do I run my Rust programs on Raspi? Can I compile them on my Mac or should I compile them on the device?

DOWNLOAD LINK: PASSWORD: 1234-.

I didn’t have much experience with compiling stuff for other platforms, but turns out it’s not that dramatic – at least for the simple ones I wrote so far.

  1. Get an unrestricted 30-day free trial of FreshBooks at 10% and Free Worldwide Shipping at Ridge Wallets by using offe.
  2. My migration away from Mac OS X is still going well. I rarely use Mac OS any more. The one remaining application I'm yet to find a suitable replacement for is MoneyWell. This is still the case. About once a month I open up the MacBook to balance finances and track my budget.
  3. Calling all my mac bois, how good does rust play on your mac? Question I have a late 2013 iMac with a 2.9 GHz Intel Core i5 processor, 8Gb of RAM and the NVIDIA GeForce GT 750M 1024 MB, I got rust for free from a friend and I played once on Mediocre graphics and it ran poorly, like 15 fps or so, wondering how it is for the others out there.
  4. Erasing your Mac permanently deletes its files. If you want to restore your Mac to factory settings, such as to prepare it for a new owner, first learn what to do before you sell, give away, or trade in your Mac. Then erase your Mac as the final step.

The thing that’s complicated on OS X

The process we’re interested in is called cross compilation. To cross compile a program for other platform, we need a suitable toolchain.1 One can find many tutorials on how to cross compile Rust code for Raspberry Pi, but most of them are for Linux users. We could trying building a toolchain by ourselves, though doing that on OS X is a total pain. The amount of outdated information on the Web and the number of hurdles we’d have to go over renders Macs unusable for that.

If you don’t believe me, check out the outdated docs on how to get crosstool-ng to work on OS X. The main issues with OS X are the incompatibility of FreeBSD-based command line tools with their GNU counterparts and the case insensitivity of HFS+.

The solution

Instead, let’s use information gathered in Erik’s rust-on-raspberry-pi repo. While building the cross compiler by hand won’t work for us, schnupperboy dockerized the whole process which makes it a breeze.

I was skeptical at first, as I didn’t use Docker before and thought it’s an overkill. Turns out it’s pretty handy for this use case and saves us a lot of hassle. After installing Docker Toolbox and going through its Getting Started Guide, we should be able to follow instructions from rust-on-raspberry-pi’s DOCKER.md without any issues. Keep in mind that Docker commands have to be run from the Docker Quickstart Terminal session.

I ran into problems with permissions in Docker, but after some digging I found the post called “Web development with Docker on Mac OS X”. The user created inside the Docker image needs to have uid set to 1000 to access files from the host filesystem. The PR which fixes this has been already merged into the rust-on-raspberry-pi master branch.

Away

It took me a couple of days, but cross compiling finally works on my machine and the compiled programs seem to run well on Raspi. I got reminded once again that with such dynamic technologies, the blogposts written two years ago could as well not exist. It’s the reason I wrote this one and I hope it’ll serve people for another few months. 🎉

Shout out to Arek and all the amazing people at Estimote thanks to whom I was able to experiment with Raspberry Pi!

Rust Away Mac OS

Discuss on /r/rust.

  1. As Mats Petersson said on SO, a toolchain is a set of tools needed to produce executables for the target platform, like a compiler, a linker, shared libraries and so on. ↩

Rust is a systems programming language focused on speed and safe concurrency, and which I’ve been using for personal projects heavily since the 1.0 release last year. Most of these projects have been replacements for existing scripts in my workflows or new command line tools, but I wanted to create a Mac application and determine if it would benefit from Rust’s memory efficiency, safety, and robust library ecosystem.

I’ve done iOS and Mac application development for many years and it's worth noting that the hardest part of Cocoa development has always been learning the frameworks rather than the languages. This experiment is about applying Cocoa and Rust knowledge to create something safe and yet easy to work with.

Getting started with Cocoa crates

There are already crates for working with the Objective-C runtime, such as the [CODE]objc[/CODE] and [CODE]block[/CODE] crates, which are for using the runtime directly and interfacing with Apple’s block extensions respectively. The [CODE]objc[/CODE] crate in particular provides the [CODE]msg_send![/CODE] macro, which is a basic interface to messaging Objective-C objects. Here’s an example of creating an [CODE]NSObject[/CODE]:

-- CODE language-rust --
unsafe {
let cls = Class::get('NSObject').unwrap();
let obj: *mut Object = msg_send![cls, new];
}

The [CODE]cocoa[/CODE] crate builds on this to provide an interface to using frameworks including AppKit for drawing windows and views onscreen. It also has an interesting take on implementing Objective-C classes in that translates them to traits which are implemented by a generic [CODE]NSObject[/CODE] type. This snippet creates an app and a window, and presents it on screen:

-- CODE language-rust --
unsafe {
let _pool = NSAutoreleasePool::new(nil);
let app = NSApp();
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
let window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_(
NSRect::new(NSPoint::new(0., 0.), NSSize::new(200., 200.)),
NSTitledWindowMask as NSUInteger,
NSBackingStoreBuffered,
NO
).autorelease();
let title = NSString::alloc(nil).init_str('Hello World!');
window.setTitle_(title);
window.makeKeyAndOrderFront_(nil);
app.run();
}

Pretty cool, though as is, the entire interface is unsafe, missing the hopeful goal of the experiment. This approach could still be interesting when writing the application core code in Rust, and then packaging it using Cocoa bindings.

Wrapping Cocoa APIs in “safety”

Given those caveats, couldn’t we create Rust wrappers for Objective-C classes? Of course! After some trial and error, I had a base trait to use for wrapping and interacting with Objective-C objects:

-- CODE language-rust --
use objc::runtime::Object;
pub type Id = *mut Object;
pub trait ObjCClass: Sized {
/// Returns pointer to underlying objc object
fn ptr(&self) -> Id;
/// Creates an instance from an objc object pointer, failing
/// if the pointer is not an instance of the wrapped class
fn from_ptr(ptr: Id) -> Option<self>;</self>
/// The printed name of the class
fn class_name() -> &'static str;
/// Type-safe reference to an instance with a nil pointer
fn nil() -> Self;
/// Performs an `isKindOfClass` check to whether a particular
/// pointer is an instance of the wrapped class
fn ptr_is_class(ptr: Id) -> bool;
/// Change an instance of one class into another, failing if
/// the pointer is not an instance of the preferred class.
/// Useful for converting between inherited classes e.g.
/// NSDictionary to NSMutableDictionary.
fn coerce<t: objcclass='>(&self) -> Option<t> {</t></t:>
T::from_ptr(self.ptr())
}
/// Designate this instance as suitable for being released
/// once it is out of scope
fn autorelease(&self) -> Self;
/// Drop the Objective-C reference. The object is then invalid
fn release(&mut self);
}

Note that this creates a Rust object with a reference to an Objective-C object, but the overall effect is minimal as most interaction still happens in Objective-C runtime land.

Using this trait was most easily done creating a handy macro named [CODE]impl_objc_class[/CODE], and then wrapping the average class became easy! Here’s an example which wraps a few methods on [CODE]NSString[/CODE].

-- CODE language-rust --
const UTF8_ENCODING: NSUInteger = 4;
impl_objc_class!(NSString);
impl NSString {
/// Creates an `NSString` from a `str`.
pub fn from(content: &str) -> Self {
let ptr: *mut Object = unsafe {
let string: *mut Object = msg_send![class!('NSString'), alloc];
msg_send![string, initWithBytes:content.as_ptr()
length:content.len()
encoding:UTF8_ENCODING]
};
NSString { ptr: ptr }
}
/// The length of the string as measured in UTF-8 code points
pub fn len(&self) -> usize {
unsafe { msg_send![self.ptr, lengthOfBytesUsingEncoding:UTF8_ENCODING] }
}
}

The class can now be used directly, and without [CODE]unsafe[/CODE]:

-- CODE language-rust --
let greeting = NSString::from('hello');
assert_eq!(greeting.len(), 5);

Resources still need to be released (or auto-released, if applicable) when they are no longer needed, but classes became much easier to use. I explored some options such as implementing a [CODE]Drop[/CODE] trait to automatically discard Objective-C objects once the Rust reference goes out of scope, but this behavior is not always desirable, especially when working with references to applications and windows which are expected to stay for the life time of the application, or at least longer than the current scope.

Rust Away Mac Os Update

Packaging Rust into an app

While we can use the snippets of the cocoa crate to run an executable, the executable is not packaged as an app bundle, which would be suitable for having an app icon, putting an app in the dock, or being registered as a default application (like being the mail client used for [CODE]mailto:[/CODE] links, for example). For that, we’d need to package the executable into an app bundle.

An easy way to create an app bundle which launches Rust code is to create a Cocoa app with a Rust and dependent app target. This requires a few steps in Xcode:

  • Create a new app using the Cocoa app template
  • Add a second “External build system” target to the application which creates the Rust binary
  • Add the second target to the default app target as a dependency
  • Add the rust executable as a bundled resource of the app target
  • Replace the default AppDelegate with a script to launch the Rust binary, something like this bit of Swift:
-- CODE language-rust --
let task = Process()
task.launchPath = Bundle.main.path(forResource: 'my-rust-program', ofType: nil)
task.launch()
task.waitUntilExit()

I’ve created an example which shows all of these parts in action, adds an app icon, and pipes output from the Rust executable to the system console.

Conclusions

The initial results were less than ergonomic when using the existing Cocoa crate since the interface did not add additional safety, and perhaps removed some because the generic object type conformed to every Cocoa class trait. I could (and did) call the wrong methods on Cocoa class instances.

Writing my own layer of classes on top of [CODE]objc[/CODE] improved the latter, though it was more initial overhead to write wrappers before using classes, and still felt clumsy when converting between values in class clusters for example. There is potential for a “Rustier” crate for interfacing with Objective-C, or a generator which makes ergonomic method names. Despite this, I mapped a number of Objective-C classes by hand, and while my stylistic choices probably aren’t suitable for a general use library, Rust+Cocoa became very fast to use and iterate on ideas. The approach could be worth a try if you have reusable components in Rust to share with a Cocoa application, and have constructs unsuitable for use with the foreign function interface.

Rust Away Mac Os Catalina

There’s more I could cover here about the experience, like how to declare your own Objective-C classes in Rust and implementing protocols, but that should be the topic of a later post.

Rust Away Mac Os X

I’ve made some longer examples demonstrating the snippets in this post as well as a general template usable for packaging a mac app, which is available on GitHub.

Thanks for reading!