This article was written using Rust 1.13 nightly (the latest as of September 2016) with unstable features. Certain unstable features may have been changed or removed since.

Have you ever noticed how similar Rust and Swift are? I don’t mean just syntactically either– the languages have a lot of similar design philosophies:

Both languages have pretty good static type systems that prevent many errors at compile time, like NULL reference errors

reference errors Both languages often encourage using simple struct and enum values with traits/protocols where possible, over using object-oriented class hierarchies

Both languages provide very nice high-level constructs (things like map , or pattern matching) that get compiled and optimized into a static, native (and fast!) binary

Really, you can’t go wrong with using either language: both are really compelling for building things in a wide range of different domains!

That said, I personally prefer Rust, so learning about Swift inspired me to sit down and think, “what can Swift do that Rust can’t, and why?” To me, the most glaring thing was that Swift is really good for app development, while Rust in this regard is… lacking.

For now, let’s just talk about building Cocoa apps for macOS. Sure, you can build a native Cocoa app in Rust right now using the cocoa crate, but it’s very far removed from writing a Cocoa app in Swift: notice the very procedural nature of their “hello world” example app.

So, how can we improve on the ergonomics of the cocoa crate?

Well, let’s first start with a very simple Swift app: all this app does is open an empty window and print some messages to stdout , then the app quits when the window is closed:

1 import Cocoa 2

3 class AppDelegate : NSObject , NSApplicationDelegate { 4 let app : NSApplication 5 let controller : NSWindowController 6

7 init ( app : NSApplication ) { 8 self . app = app 9 self . controller = WindowController () 10 } 11

12 func applicationDidFinishLaunching ( _ : NSNotification ) { 13 controller . showWindow ( nil ) 14 app . activateIgnoringOtherApps ( true ) 15

16 print ( "Application launched!" ) 17 } 18

19 func applicationWillTerminate ( _ : NSNotification ) { 20 print ( "Application terminated!" ) 21 } 22

23 func applicationShouldTerminateAfterLastWindowClosed ( _ : NSApplication ) -> Bool { 24 return true 25 } 26 } 27

28 class WindowController : NSWindowController { 29 required init ( coder : NSCoder ) { 30 fatalError ( "Not implemented" ) 31 } 32

33 init () { 34 let rect = NSMakeRect ( 0 , 0 , 480 , 320 ) 35 let style = NSTitledWindowMask 36 | NSClosableWindowMask 37 | NSResizableWindowMask 38 let window = NSWindow ( contentRect : rect , 39 styleMask : style , 40 backing : . Buffered , 41 defer : false ) 42 window . title = "App" 43 super . init ( window : window ) 44 } 45 } 46

47 let app = NSApplication . sharedApplication () 48 let delegate = AppDelegate ( app : app ) 49 app . delegate = delegate 50

51 app . setActivationPolicy ( . Regular ) 52 app . run ()

(Credit for the above to lucamarrocoo via a GitHub Gist)

Now, let’s try translating this app to use our new “ideal” Rust crate:

1 extern crate new_cocoa as cocoa ; // Our new library! 2

3 struct AppDelegate { 4 super_ : cocoa :: NSObject , // (1) 5 app : cocoa :: NSApplication , 6 controller : cocoa :: NSWindowController 7 } 8

9 // (2) 10 impl cocoa :: Object for AppDelegate { 11 type Super = cocoa :: NSObject ; 12

13 fn super_ref ( & self ) -> & Self :: Super { 14 & self .super_ 15 } 16 } 17

18 impl cocoa :: AppDelegate { 19 fn new_with_app ( app : cocoa :: NSApplication ) -> Self { 20 AppDelegate { 21 super_ : cocoa :: NSObject :: new (), 22 app : app .clone (), 23 controller : WindowController :: new () 24 } 25 } 26 } 27

28 // (3) 29 impl cocoa :: IsNSApplicationDelegate for AppDelegate { 30 fn application_did_finish_launching ( & self , _ : & cocoa :: NSNotificaton ) { 31 self .show_window ( None ); 32 self .activate_ignoring_other_apps ( true ); 33

34 println! ( "Application launched!" ); 35 } 36

37 fn application_will_terminate ( & self , _ : & cocoa :: NSNotification ) { 38 println! ( "Application terminated!" ); 39 } 40

41 fn application_should_terminate_after_last_window_closed ( _ : & cocoa :: NSApplication ) 42 -> bool 43 { 44 true 45 } 46 } 47

48 struct WindowController { 49 super_ : cocoa :: NSWindowController // (1) 50 } 51

52 // (2) 53 impl cocoa :: Object for WindowController { 54 type Super = cocoa :: NSWindowController ; 55

56 fn super_ref ( & self ) -> & Self :: Super { 57 & self .super_ 58 } 59 } 60

61 impl WindowController { 62 fn new () -> Self { 63 let rect = cocoa :: NSMakeRect ( 0.0 , 0.0 , 480.0 , 320.0 ); 64 let style = cocoa :: NSTitledWindowMask 65 | cocoa :: NSClosableWindowMask 66 | cocoa :: NSResizableWindowMask ; 67 let backing = cocoa :: NSBackingStoreType :: Buffered ; 68 let window = cocoa :: NSWindow :: new ( rect , style , backing , false ); 69 window .set_title ( "App" ) ; // (4) 70

71 WindowController { 72 super_ : NSWindowController :: new_with_window ( window ) 73 } 74 } 75 } 76

77 // (3) 78 impl cocoa :: IsNSWindowController for WindowController { 79 fn new_with_coder ( coder : cocoa :: NSCoder ) -> Self { 80 panic! ( "Not implemented" ); 81 } 82 } 83

84 fn main () { 85 let app = NSApplication :: shared_application (); 86 let delegate = AppDelegate :: new ( app ); 87 app .set_delegate ( delegate ) ; // (4) 88 }

I think the above translation is pretty good! It’s not perfect (as we’ll see), but it’s a pretty good jumping-off point. That said, there’s a few things about the translation I wanted to point out:

The super_ fields (lines 4 and 49). Because Rust doesn’t have object-oriented-style inheritance, we need to explicitly add a field to hold our base class (which is roughly how inheritance works under-the-hood in some other languages, we’re just doing it manually). You can also see how this affects our constructors on lines 20-24 and 71-73. The Object trait (lines 10 and 53). There’s nothing special about the aforementioned super_ field, so we use the Object trait to signal that we want to use it for “inheritance”. We’re going to go more in-depth on how this works later :) IsNSApplicationDelegate (line 29) and IsNSWindowController (line 78). We’ll talk about this more later, but the core idea is that we separate a class’s “instance type” (named like NSThing ) from it’s trait “interface” (named like IsNSThing ). Explicit getters and setters (e.g. lines 69 and 87). Rust doesn’t have any form of overriding getters and setters, so we just use plain ol’ methods instead!

So, we have a clear objective: to build this nice Cocoa library for Rust. Now, the real question is: how would we go about building it? To be clear, I’m not going to walk through building the whole example to completion; instead, I’m going to focus on some of the higher-level design questions, as well as some of the hurdles I faced while building it to completion. If you’re impatient and just want to dive in with the resulting library, feel free to skip to the epilogue (where I also discuss where this project might be headed).

With that out of the way, let’s dive in! But, before getting our hands dirty, we need to talk about…

The Objective-C runtime!

The Objective-C runtime is going to be our gateway into the world of Cocoa, so we should at least have a cursory understanding of how it works.

First, let’s dissect a very simple piece of Swift code that uses Cocoa:

1 import Cocoa 2

3 let menu = NSMenu ( title : "Hello!" ) 4 let app = NSApplication . sharedApplication () 5 app . mainMenu = menu 6 app . setActivationPolicy ( . Regular ) 7 app . run ()

This code seems pretty innocuous, right? All it does is create a main menu for our app with the title, “Hello!”, then it runs the app1. What does the “runtime” even do here? Well, let’s break it down, line by line:

line 3: Create a new NSMenu by sending the message alloc to the NSMenu class. Then, instantiate it by sending the initWithTitle: message (which sets the class up appropriately).

by sending the message to the class. Then, instantiate it by sending the message (which sets the class up appropriately). line 4: Get a reference to the current app by sending the sharedApplication message to the NSApplication class.

message to the class. line 5: Set the app’s main menu by sending the setMainMenu: message to the app object

message to the object line 6: Send the setActivationPolicy: message to the app object

message to the object line 7: Run the app by sending the run message to the app object

Sending messages with the objc crate

So, as you may have gathered, sending message is pretty important in the Objective-C runtime! But, how do you even “send a message” using Rust, then? Fortunately, the Objective-C runtime has a pretty simple C API. Double fortunately, there’s already an awesome crate for working with the Objective-C runtime in Rust! We can translate the above example to Rust using the objc crate like this:

1 #[macro_use] extern crate objc ; 2 use std :: ffi :: CString ; 3 use objc :: runtime ::{ Object , Class }; 4

5 // Link to Cocoa 6 #[link(name = "Cocoa" , kind = "framework" )] 7 extern { } 8

9 fn main () { 10 unsafe { 11 // Convert "Hello!" to an `NSString` 12 let title = CString :: new ( "Hello!" ) .unwrap (); 13 let title = title .as_ptr (); 14

15 let NSString = Class :: get ( "NSString" ) .unwrap (); 16 let title : * mut Object = msg_send! [ NSString , stringWithUTF8String : title ]; 17

18 // `title` is now an `NSString` that holds 19 // the string "Hello!", so proceed as normal 20

21 let NSMenu = Class :: get ( "NSMenu" ) .unwrap (); 22 let menu : * mut Object = msg_send! [ NSMenu , alloc ]; 23 let menu : * mut Object = msg_send! [ menu , initWithTitle : title ]; 24

25 let NSApplication = Class :: get ( "NSApplication" ) .unwrap (); 26 let app : * mut Object = msg_send! [ NSApplication , sharedApplication ]; 27

28 let _ : () = msg_send! [ app , setMainMenu : menu ]; 29

30 let _ : () = msg_send! [ app , setActivationPolicy : 0 /* .Regular */ ]; 31

32 let _ : () = msg_send! [ app , run ]; 33 } 34 }

The very first thing we do is… add an empty extern { } with a #[link(...)] attribute. It’s kind of weird, but it basically just tells rustc to link to Cocoa (you can do the same thing by running cargo rustc -- -l framework=Cocoa ). If you omit it, the code will still compile, but the program will panic at runtime because none of the classes could be found.

The next thing we need to do is to convert our “Hello!” string to an NSString . To do so, we first convert it to a *const c_char by using the CString type. Then, we convert it to an NSString with NSString ‘s stringWithUTF8String static method.

Once that’s done, the rest of the code is pretty straightforward! As you may have gathered, the syntax msg_send![foo, bar:baz] means “send the bar: message to foo ” (which was heavily influenced by the Objective-C syntax for sending messages).

There are two curious things that came up in our example, however:

The (useless-looking) let statements. We use these to annotate the return type of a method, which msg_send! needs to know to call the correct version of objc_msgSend . The Class::get(...) calls. As mentioned previously, classes can receive messages in the Objective-C runtime too, which is important for class methods (like when we send alloc to create a new instance of a class, or when we call sharedApplication to get our NSApplication ).

There’s also two additional important details in the above that I should mention:

All objects are of type *mut Object (a.k.a id if you’re writing Objective-C, and AnyObject if you’re writing Swift)! This will come up when we talk about polymorphism, so just keep it in the back of your mind for now! Classes are objects too! We can get a class object using the Class::get(...) method, then we can send messages to it using msg_send! . As we’ll also see later, you can even make new classes at runtime!

Traits and classes

So, now we understand enough of the Objective-C runtime to actually dive into our library! But there’s still a pretty large unanswered question for our library design: how do we even make “classes” in Rust? Well, Rust doesn’t have classes or inheritance, but it does have structs and traits! So here’s an example of the NSWindowController class represented with a struct and a trait, with an overridable windowDidLoad method:

1 #[macro_use] extern crate objc ; 2 use objc :: runtime :: Object as AnyObject ; 3

4 // Link to Cocoa 5 #[link(name = "Cocoa" , kind = "framework" )] 6 extern { } 7

8 struct NSWindowController { 9 id : * mut AnyObject 10 } 11

12 trait IsNSWindowController { 13 // [self windowDidLoad] 14 fn window_did_load ( & self ) { 15 // `windowDidLoad` does nothing unless a subclass overrides it 16 } 17

18 // ... every other selector `NSWindowController` responds to ... 19 } 20

21 impl IsNSWindowController for NSWindowController { 22 fn window_did_load ( & self ) { 23 unsafe { 24 // Send `windowDidLoad` to the inner Objective-C object 25 msg_send! [ self .id , windowDidLoad ]; 26 } 27 } 28

29 // ... 30 }

In the above, NSWindowController is just a newtype around an Objective-C object (remember, all objects in the Objective-C runtime are *mut Object / *mut AnyObject / id ). If we write a function that returns an NSWindowController , it’s just an assertion that the id field is some object that conforms to the NSWindowController interface (i.e. it responds to windowDidLoad ), which means we can call any NSWindowController methods on it safely (so, we can write controller.windowDidLoad() ). The important point is that, when returning an NSWindowController , the object in the id field doesn’t necessarily have to have the class NSWindowController (it could be a subclass or a surrogate object, for instance).

Inheritance hierarchy

So why move all of the methods into a separate trait, rather than putting them in a normal impl NSWindowController block? Well, let’s add a second class into the mix and see how our code changes; we’ll add NSWindowController ’s superclass, NSResponder :

1 #[macro_use] extern crate objc ; 2 use objc :: runtime :: Object as AnyObject ; 3 use objc :: runtime ::{ BOOL , YES }; 4

5 // Link to Cocoa 6 #[link(name = "Cocoa" , kind = "framework" )] 7 extern { } 8

9 struct NSResponder { 10 id : * mut AnyObject 11 } 12

13 trait IsNSResponder { 14 // [self becomeFirstResponder] 15 fn become_first_responder ( & self ) -> bool { 16 // Default implementation returns true 17 true 18 } 19

20 // ... every other selector `NSResponder` responds to ... 21 } 22

23 impl IsNSResponder for NSResponder { 24 fn become_first_responder ( & self ) -> bool { 25 unsafe { 26 // Send `becomeFirstResponder` to the object and 27 // convert the result to a `bool` 28 let result : BOOL = msg_send! [ self .id , becomeFirstResponder ]; 29 result == YES 30 } 31 } 32

33 // ... 34 } 35

36 struct NSWindowController { 37 id : * mut AnyObject 38 } 39

40 trait IsNSWindowController : IsNSResponder { 41 // [self windowDidLoad] 42 fn window_did_load ( & self ) { 43 // default windowDidLoad impl does nothing 44 } 45

46 // ... every other selector `NSWindowController` responds to ... 47 } 48

49 impl IsNSResponder for NSWindowController { 50 fn become_first_responder ( & self ) -> bool { 51 unsafe { 52 // Send `becomeFirstResponder` to the object and 53 // convert the result to a `bool` 54 let result : BOOL = msg_send! [ self .id , becomeFirstResponder ]; 55 result == YES 56 } 57 } 58

59 // ... 60 } 61

62 impl IsNSWindowController for NSWindowController { 63 fn window_did_load ( & self ) { 64 unsafe { 65 // Send `windowDidLoad` to the inner Objective-C object 66 msg_send! [ self .id , windowDidLoad ]; 67 } 68 } 69

70 // ... 71 }

The NSResponder struct and IsNSResponder trait look roughly like you’d probably expect, mirroring exactly how we had NSWindowController and IsNSWindowController before. But, we added two curious things: the IsNSWindowController: IsNSResponder , and the impl IsNSResponder for NSWindowController .

The IsNSWindowController: IsNSResponder line says, “the IsNSWindowController trait inherits the IsNSResponder trait”. It doesn’t quite work like object-oriented inheritance works, though: instead, what is says is “all implementors of IsNSWindowController must also implement IsNSResponder ”.

This explains the second strange addition: since NSWindowController implements IsNSWindowController , that means it must also implement IsNSResponder . Combined, that means that any time we have an object that implements IsNSWindowController (i.e. an object who’s class is NSWindowController or a subclass), we can call any NSResponder methods on it! This should make intuitive sense to those familiar with an understanding of object-oriented programming as well: every NSWindowController is also an NSResponder , so that means we can call any NSResponder method for any given NSWindowController .

Don’t repeat yourself!

Eagle-eyed readers may have noticed something fishy in the above: lines 24-33 are exactly the same as lines 50-59! We can clean this up a bit by changing the code to the following:

1 struct NSWindowController { 2 id : NSResponder 3 } 4 impl IsNSResponder for NSWindowController { 5 fn become_first_responder ( & self ) -> bool { 6 // Forward along the call... 7 self .id .become_first_responder () 8 } 9

10 // ... 11 } 12

13 impl IsNSWindowController for NSWindowController { 14 fn window_did_load ( & self ) { 15 unsafe { 16 // Send `windowDidLoad` to the inner Objective-C object 17 msg_send! [ self .id .id , windowDidLoad ]; 18 } 19 } 20

21 // ... 22 }

So, by changing our NSWindowController to hold an NSResponder instead of a *mut AnyObject , we can just forward the become_first_responder call to NSResponder directly! Note that this still allows for calling subclass overrides, because it’s still sending a message via the Objective-C runtime, since that’s what NSResponder::become_first_responder does! Because of this change though, we did also needed to adjust our window_did_load method.

Still, this is a great win in reducing duplication! So, all done, time to go home, right? Wrong, we can reduce duplication even further:

1 #[macro_use] extern crate objc ; 2 use objc :: runtime ::{ BOOL , YES }; 3 use objc :: runtime :: Object as AnyObject ; 4

5 // Link to Cocoa 6 #[link(name = "Cocoa" , kind = "framework" )] 7 extern { } 8

9 // A trait used to indicate that a type is a "class" 10 trait Object { 11 // The "superclass" this type inherits from 12 type Super ; 13

14 // Get a pointer to the superclass 15 fn super_ref ( & self ) -> & Self :: Super ; 16 } 17

18 struct NSResponder { 19 super_ : * mut AnyObject 20 } 21

22 impl Object for NSResponder { 23 // `NSResponder` doesn't really have a superclass in our example, 24 // so we use `*mut AnyObject` as a sort of "base class". 25 type Super = * mut AnyObject ; 26

27 fn super_ref ( & self ) -> & Self :: Super { 28 & self .super_ 29 } 30 } 31

32 trait IsNSResponder { 33 // [self becomeFirstResponder] 34 fn become_first_responder ( & self ) -> bool { 35 // Default implementation returns true 36 true 37 } 38

39 // ... every other selector `NSResponder` responds to ... 40 } 41

42 // A trait that indicates that a type is a subclass 43 // of `NSResponder` specifically 44 trait SubNSResponder { 45 type SuperNSResponder : IsNSResponder ; 46

47 fn super_ns_responder ( & self ) -> & Self :: SuperNSResponder ; 48 } 49

50 // Automatically implement `SubNSResponder` 51 // for all `NSResponder` subclasses 52 impl < T > SubNSResponder for T 53 where T : Object , T :: Super : IsNSResponder 54 { 55 type SuperNSResponder = T :: Super ; 56

57 fn super_ns_responder ( & self ) -> & Self :: SuperNSResponder { 58 self .super_ref () 59 } 60 } 61

62 // The base impl of `IsNSResponder`, which all 63 // subclasses will fallback to 64 impl IsNSResponder for NSResponder { 65 fn become_first_responder ( & self ) -> bool { 66 unsafe { 67 // Send `becomeFirstResponder` to the object and 68 // convert the result to a `bool` 69 let result : BOOL = msg_send! [ self . super_ , becomeFirstResponder ]; 70 result == YES 71 } 72 } 73

74 // ... 75 } 76

77 // The default impl of `IsNSResponder` for all subclasses 78 impl < T > IsNSResponder for T 79 where T : SubNSResponder 80 { 81 fn become_first_responder ( & self ) -> bool { 82 // Forward `become_first_responder` to our superclass 83 self .super_ns_responder () .become_first_responder () 84 } 85 } 86

87 struct NSWindowController { 88 super_ : NSResponder 89 } 90

91 impl Object for NSWindowController { 92 type Super = NSResponder ; 93

94 fn super_ref ( & self ) -> & Self :: Super { 95 & self .super_ 96 } 97 } 98

99 trait IsNSWindowController : IsNSResponder { 100 // [self windowDidLoad] 101 fn window_did_load ( & self ) { 102 // default windowDidLoad impl does nothing 103 } 104

105 // ... every other selector `NSWindowController` responds to ... 106 } 107

108 // `IsNSResponder` is now automatically 109 // implemented for `NSWindowController`! 110

111 impl IsNSWindowController for NSWindowController { 112 fn window_did_load ( & self ) { 113 unsafe { 114 // Send `windowDidLoad` to the inner Objective-C object 115 msg_send! [ self .super_.super_ , windowDidLoad ]; 116 } 117 } 118

119 // ... 120 }

To summarize the above:

Object is a trait that indicates that a type “subclasses” another type. Object::Super is the superclass’s type.

is a trait that indicates that a type “subclasses” another type. is the superclass’s type. Object::super_ref is a method that “converts” a pointer from a subclass to its superclass (basically, it just returns a reference to a field that holds an instance of the superclass).

is a method that “converts” a pointer from a subclass to its superclass (basically, it just returns a reference to a field that holds an instance of the superclass). The extra trait, SubNSResponder , is a trait implemented for all subclasses of NSResponder (this is necessary because of this Rust issue).

, is a trait implemented for all subclasses of (this is necessary because of this Rust issue). We automatically implement IsNSResponder for all NSResponder subclasses by forwarding to the superclass’s impl s by default

Well, okay, we actually ended up with more code… But, on the plus side, we don’t need to add an impl for every ancestor in a class’s hierarchy–we now only need one Object impl, one Sub_ trait, one blanket Sub_ impl, and one Is_ blanket impl per class! This scales a lot better (and is a lot easier to write a macro for… *cough cough*)!

There is, however, one problem: we just took away the ability for a subclass to override a method from a superclass. We’ll come back to this problem later.

Polymorphism

So we have a (relatively) simple way of representing a Swift/Objective-C class hierarchy in Rust. Minus a couple of minor details (like how to represent constructors, or when we should write methods as taking &self , &mut self , or self ), we could basically extrapolate what we have to all of the classes from Foundation and Cocoa, and our library would be in pretty good shape! There is, however, one important detail we’ve missed, which we can see in this code snippet:

1 // ... 2

3 impl NSResponder { 4 fn new () -> Self { 5 // ... initialize a new NSResponder ... 6 unimplemented! (); 7 } 8 } 9

10 impl NSWindowController { 11 fn new () -> Self { 12 // ... initialize a new NSWindowController ... 13 unimplemented! (); 14 } 15 } 16

17 fn use_ns_responder ( responder : NSResponder ) { 18 // ... do something with an `NSResponder` ... 19 } 20

21 fn main () { 22 let controller = NSWindowController :: new (); 23 use_ns_responder ( controller ); 24 // ^^^^^^^^^^ 25 // expected struct `NSResponder` 26 // found struct `NSWindowController 27 // error: mismatched types 28 }

The problem is that an NSWindowController is-an NSResponder , so the equivalent code would work in Objective-C/Swift. Unfortunately, it’s impossible to get the code above to work as intended in Rust, so we have to make some tweaks.

Basically, we need a way to cast from a type that implements IsNSResponder (a.k.a subclasses of NSResponder ) to a real NSResponder in the Objective-C runtime.

Presenting… the Duck trait:

1 pub trait Duck < T > { 2 // Cast `self` to the subtype `T` 3 fn duck ( self ) -> T ; 4 } 5

6 impl Duck < NSResponder > for NSWindowController { 7 fn duck ( self ) -> NSResponder { 8 // Since `NSWindowController` is just a newtype around `NSResponder`, 9 // we can "convert" it by returning the inner `NSResponder` 10 self .super_ 11 } 12 }

Implementing Duck<Foo> for Bar says that Bar can be coerced into a Foo . Basically, Duck acts just like the Rust Into trait, but we’re using it specifically for casting from a superclass to a subclass.

Now, we can tweak our broken example code a bit to get it to compile:

1 // ... 2

3 impl NSResponder { 4 fn new () -> Self { 5 // ... initialize a new NSResponder ... 6 unimplemented! (); 7 } 8 } 9

10 impl NSWindowController { 11 fn new () -> Self { 12 // ... initialize a new NSWindowController ... 13 unimplemented! (); 14 } 15 } 16

17 fn use_ns_responder ( responder : NSResponder ) { 18 // ... do something with an `NSResponder` ... 19 } 20

21 fn main () { 22 let controller = NSWindowController :: new (); 23 use_ns_responder ( controller .duck () ); 24 // yay! no more errors! 25 }

Not impressed? Don’t worry, Duck will become really important… soon.

Passing Rust types into the Objective-C runtime

Alright, we have a formula for pulling in Objective-C classes into Rust, we can export methods, and we can support polymorphism pretty well! Library done, pack it up, throw it up on crates.io, call it a day!

Okay, not quite. There’s one large unanswered question: how do we export Rust types as classes for the Objective-C runtime? In the beginning, I alluded that something like the following would also work:

1 struct MyResponder { 2 super_ : NSResponder 3 } 4

5 impl MyResponder { 6 fn new () -> Self { 7 MyResponder { 8 super_ : NSResponder :: new () 9 } 10 } 11 } 12

13 // MyResponder inherits from NSResponder 14 impl Object for MyResponder { 15 type Super = NSResponder ; 16

17 fn super_ref ( & self ) -> & Self :: Super { 18 & self .super_ 19 } 20 } 21

22 // Override `NSResponder` methods! 23 impl IsNSResponder for MyResponder { 24 fn become_first_responder ( & self ) -> bool { 25 println! ( "Called MyResponder::become_first_responder" ); 26 false 27 } 28

29 // NOTE: We're only providing impls for the methods 30 // we actually want to override 31 } 32

33 fn use_ns_responder ( responder : NSResponder ) { 34 // ... do something with an `NSResponder` ... 35 } 36

37 fn main () { 38 let responder = MyResponder :: new (); 39 use_ns_responder ( responder ); 40 }

Just like before, we’d get type error when trying to call use_ns_responder with MyResponder (rather than an actual NSResponder ).

So, also like before, let’s add another .duck() call:

1 fn main () { 2 let responder = MyResponder :: new (); 3 use_ns_responder ( responder .duck () ); 4 }

Now, all we need to do is write a blanket impl to fulfill the requirement MyResponder: Duck<NSResponder> (so that MyResponder::duck() -> NSResponder ). Easy, right?

Well, the first thing we need to do is address the issue of a subclass overriding a superclass’s methods. To do this, we’re going to turn on a nightly feature, then we’re going to modify our current blanket IsNSResponder impl:

1 #![feature(specialization)] 2

3 #[macro_use] extern crate objc ; 4 use objc :: runtime :: Object as AnyObject ; 5 use objc :: runtime ::{ BOOL , YES }; 6

7 // Link to Cocoa 8 #[link(name = "Cocoa" , kind = "framework" )] 9 extern { } 10 // The default impl of `IsNSResponder` for all subclasses 11 impl < T > IsNSResponder for T 12 where T : SubNSResponder 13 { 14 // Allow every method to be specialized, 15 // using the `default` keyword 16 default fn become_first_responder ( & self ) -> bool { 17 // Forward `become_first_responder` to our superclass 18 self .super_ns_responder () .become_first_responder () 19 } 20 }

Normally in Rust, it’s considered an error for two impl s to overlap. But, with specialization, two or more impl s are allowed as long as:

One impl is strictly more specific than the others (so that there are no ambiguities for method dispatch) The conflicting items are marked as default in the more general impl

Now, subclasses of NSResponder can override methods by adding an impl NSResponder for _ { ... } , then adding methods as needed!

Now we can add our Duck impl for converting MyResponder to NSResponder . Here’s what our new duck() method is going to do:

Create a new Objective-C class called CustomResponder using the ClassDecl type from the objc crate (if the class hasn’t already been created). Add an instance variable (a.k.a an “ivar”, or “field”) to the class, which will hold a Box<IsNSResponder> trait object. In a nutshell, a trait object lets us hold any instance of a trait, which can even differ at runtime (in our example, our trait object will always end up holding our MyResponder type). Add Objective-C methods to our class that will just call the appropriate method from our trait object. Allocate a new CustomResponder with the normal alloc and init messages, then set our trait object instance variable to the object we want. Return an NSResponder with the id field containing our new CustomResponder instance.

And here it is translated to code:

1 use std :: mem ; 2 use std :: os :: raw :: c_void ; 3 use objc :: runtime ::{ Sel , Class , NO }; 4 use objc :: declare :: ClassDecl ; 5

6 // Our new blanket impl, which will enable us to 7 // call `.duck()` on our `MyResponder` class! 8 impl < T > Duck < NSResponder > for T 9 where T : IsNSResponder 10 { 11 // Must be marked default, so that 12 // NSWindowController's impl doesn't conflict 13 default fn duck ( self ) -> NSResponder { 14 // No easy tricks like last time... 15

16 // Try to find the "CustomResponder" class 17 let CustomResponder = match Class :: get ( "CustomResponder" ) { 18 Some ( CustomResponder ) => { 19 // The "CustomResponder" class already 20 // exists (which means we've already 21 // created it), so don't recreate it. 22 CustomResponder 23 } 24 None => { 25 // The "CustomResponder" class doesn't 26 // exist, so we need to create it... 27

28 // (1) 29 let NSObject = Class :: get ( "NSObject" ) .unwrap (); 30 let mut CustomResponder = 31 ClassDecl :: new ( "CustomResponder" , NSObject ) .unwrap (); 32

33 // First, we add an "instance variable" to our class. 34 // It will be of type `*mut c_void`, but 35 // it will hold a `*mut Box<IsNSResponder>` 36 // in practice. 37

38 // (2) 39 CustomResponder .add_ivar :: <* mut c_void > ( "_boxed" ); 40

41 // Next, we need add all of the methods our custom class 42 // will respond to. For each method, we need a function 43 // to call (like `impl_becomeFirstResponder`), and 44 // we need to register the method with `.add_method()`. 45

46 // Here is the function that will be called when our 47 // `CustomResponder` receives the `becomeFirstResponder` 48 // method 49

50 // (3) 51 extern "C" fn impl_becomeFirstResponder ( self_ : & mut AnyObject , 52 sel : Sel ) 53 -> BOOL 54 { 55 // Get a pointer to our "_boxed" instance variable 56 let boxed : & mut * mut c_void = unsafe { 57 self_ .get_mut_ivar ( "_boxed" ) 58 }; 59

60 // Cast it to it's actual type 61 let boxed : & mut * mut Box < IsNSResponder > = unsafe { 62 mem :: transmute ( boxed ) 63 }; 64

65 // Call the "real" `become_first_responder` method 66 let result = unsafe { 67 ( ** boxed ) .become_first_responder () 68 }; 69

70 // Cast the result to an Objective-C `BOOL` 71 match result { 72 true => YES , 73 false => NO 74 } 75 } 76

77 let f = impl_becomeFirstResponder as extern "C" fn ( & mut AnyObject , Sel ) -> BOOL ; 78 unsafe { 79 CustomResponder .add_method ( sel! ( becomeFirstResponder ), f ); 80 } 81

82 // Finally, register our new class with the 83 // Objective-C runtime! This returns the class, 84 // so we can send messages to it 85

86 CustomResponder .register () 87 } 88 }; 89

90 // Convert `self` to a `*mut Box<IsNSResponder>` 91 let boxed : Box < IsNSResponder > = Box :: new ( self ); 92 let boxed : Box < Box < IsNSResponder >> = Box :: new ( boxed ); 93 let boxed : * mut Box < IsNSResponder > = Box :: into_raw ( boxed ); 94

95 unsafe { 96 // To create an instance of "CustomResponder", 97 // we call `alloc`, then `init` 98 // (4) 99 let responder : * mut AnyObject = msg_send! [ CustomResponder , alloc ]; 100 let responder : * mut AnyObject = msg_send! [ responder , init ]; 101

102 // Check that `responder` is not `NULL` (and `panic!` if it is) 103 let responder : & mut AnyObject = responder .as_mut () .unwrap (); 104

105 // Convert our `*mut Box<IsNSResponder>` into a `*mut c_void` 106 let boxed : * mut c_void = mem :: transmute ( boxed ); 107

108 // Set our "_boxed" instance variable to our pointer 109 responder .set_ivar ( "_boxed" , boxed ); 110

111 // Finally, convert our `&mut AnyObject` into an `NSResponder`! 112 // (5) 113 NSResponder { 114 super_ : responder 115 } 116 } 117 } 118 }

Wow, that was a lot to take in! But, do you see what we just did? If not, here’s essentially the class we just created, written in a hybrid of Swift and Rust:

1 class CustomResponder : NSResponder { 2 var _boxed : Box < IsNSResponder > 3

4 init ( responder : IsNSResponder ) { 5 _boxed = Box . new ( responder ) 6 } 7

8 func becomeFirstResponder () { 9 _boxed . becomeFirstResponder () 10 } 11

12 // ... 13 }

Put another way, we just created a class to connect the worlds of Rust and Swift! The “magic” is that our class holds a boxed trait object, and that each message simply ends up dispatching to that boxed trait object!

Epilogue

So, what have we learned? Well, we covered wrapping Objective-C classes as structs and traits, modeling class hierarchies using specialization, and exporting Rust types as Objective-C types with our Duck trait. This post basically described the first phase of development of sorbet-cocoa , which, as you may have gathered, is the library I’m working on to make it as easy to write Cocoa apps in Rust as it is in Swift/Objective-C (or nearly as easy, anyway!)

There’s still a ton about sorbet-cocoa that I didn’t cover in this post, including:

Memory management with the Objective-C runtime

Wrapping Objective-C Protocols

Building macros to reduce duplication even more

Handling Rust/Objective-C type conversions (like NSString from/to &str )

from/to ) Working around Rust issue #36587 (it was harder than you’d think!)

…although there’s still a lot of experimentation going on. I still consider sorbet-cocoa a proof-of-concept at this point (there’s not even any documentation yet!), but if you’re curious about how far along it is, here’s the example app from the beginning of this article, ported to use sorbet-cocoa so it’ll actually run! That said, there aren’t many classes that have been exported yet, so it’d be hard to build anything beyond that example currently (at the time of writing).

Sorbet: beyond just Cocoa

I believe that most of the core ideas presented in this post aren’t necessarily specific to building Cocoa apps. Such a system could probably be written for other runtimes and GUI frameworks, like GTK+, Qt, or .NET/WinForms/WPF.

Xamarin (C#) and React Native (JavaScript) are two existing systems that are similar to what I’m imagining: a set of platform-specific libraries for writing native mobile and desktop apps with shared business logic. The overall project I’m going to call Sorbet. I believe Rust in this domain has several advantages over what already exists today (Xamarin, React Native, or even a shared C++ codebase):

Fast, low-level language with a really good cross-platform standard library

No built-in garbage-collector or other runtime features that would add any unnecessary overhead or latency

Fast and simple FFI to interop with other languages easily (as long as they expose a C API)

Awesome zero-cost abstractions!

In other words, a lot of the same reasons Rust is good for other things :)