Hacker News new | past | comments | ask | show | jobs | submit login
Building native macOS applications with Rust (bugsnag.com)
223 points by ingve on Nov 30, 2016 | hide | past | favorite | 45 comments



Given my experience with rust I imagine a rust specific framework would probably be the best. I imagine something like react/flux would play nicely with ownership and borrowing.

Cool to see its possible to do objective c without everything being unsafe though!


How easy is it to write closures for event handling in Rust?

Is it anywhere near as easy as doing this in Javascript? Or does it require such things as manual memory management?


Here's an example from gtk-rs: https://github.com/gtk-rs/examples/blob/master/src/text_view...

(See other files in that repo for more examples.)

It's not quite as low-friction as JS, but it's awfully close.


It's not JavaScript easy, but it's not too hard once you wrap your head around Cell, RefCell, Rc, and friends.

Here's an example using GTK that does something vaguely useful, I have no idea if it's considered idiomatic, but it works as intended: https://github.com/seaneshbaugh/image_viewer/blob/master/src...

The closures themselves are trivial, it's manipulating the stuff outside the closure that's tricky (for a Rust neophyte like me).

The relevant definitions are

  let area = Rc::new(RefCell::new(DrawingArea::new()));
and

  let scale = Rc::new(Cell::new(1.0));
Rather than just define things as usual they need to be wrapped in a Cell (for value types) or RefCell (for reference types) and then wrapped in an Rc so we can keep track of reference counts at run time.

  {
      let scale = scale.clone();

      let area = area.clone();

      window.connect_key_release_event(move |_, key_event| {
          let mut s = scale.get();

          let key = key_event.get_keyval() as i32;

          if key == gdk_sys::GDK_KEY_plus {
              s += 0.1;
          } else {
              if key == gdk_sys::GDK_KEY_minus {
                  s -= 0.1;
              }
          }

          scale.set(s);

          area.borrow().queue_draw();

          Inhibit(false)
      });
  }
Inside the new scope (which is very important) scale and area are cloned which increments their count, then inside the close scale.get() is used to get a copy of scale (which is possible since it's a float) and then after doing stuff to it based on the key that is pressed it's set using scale.set(). For area it's a bit trickier, it has to first be borrow()'ed before anything can be done with it.


A bit of bikeshedding on the code you linked:

Why are you commenting out code? If you delete it, it will still be in your git history. If you have some reason to leave it in, document why and wrap it with a `if false {}` or `if debug {}` instead. That way autoformatters will not destroy your code and the compiler makes sure your code keeps compiling.

Leaving a line empty between statements helps structuring code. Putting an empty line after each statement makes the code as unstructured as without.


My personal reason for commenting out code for a few revisions, instead of deleting right away, is to keep a reminder of the work in progress. When I'm satisfied that the old code's really done for, I delete it and leave it to the history.

Until then, it's easier to review the commented-out bits than to go back through version history. (And plenty easy to delete it when it's ready to be deleted!)


Your VCS should really make it quite easy to go back through version history. If it's not doing that, that's a separate problem.

Maybe it's just me, but I find it an eyesore to have large blobs of commented out code laying around even for a short period (not to mention the odds of me remembering to delete them are close to nil).


How do you remember which commit to diff against? How do you easily pick out the relevant parts of that diff once many changes have occurred - months or even years later?


> How do you remember which commit to diff against?

Ideally your commit message would indicate this. If you're always consistent in the language you use to describe similar changes, you can just search through your git log to find it.

> How do you easily pick out the relevant parts of that diff once many changes have occurred - months or even years later?

You can use `git show` to just look at that one commit where you deleted the code


Seems fairly comparable to the way it's done in C++11 lambdas.


C++ lambdas and Rust closures are very similar as far as I know, the only major difference being that they use explicit capture lists and we don't.


On the other hand, Rust closures do compute the capture list, and then let the type system reason about the safety of those captures, which C++ lambdas (or blocks with the clang blocks extension) don't.


Ah okay, so rust will only ever capture the minimal set of what's necessarily used in the lambda? Different from using [&] or similar in C++ where you capture everything.


Yeah, in C++ you can sort of get away without explicit capture of all variables too by doing [=] or [&]. Though I suppose it's still explicit in a way, just saves the need to list everything.


Rust doesn't have manual memory management -- it just requires you to be very explicit so that the compiler can manage memory for you ;)


Being very explicit is what I call manual :)


I would say there's still a pretty big difference between using malloc to allocate space for an array of structs which you have to remember to free later and letting variable definitions and scopes handle that for you, like in many managed languages.


I would call memory in Rust manually managed, but automatically accounted. Vs C which would be both manually managed and accounted.


I'd say the other way around: Rust is manually accounted (you have to take it into consideration when coding) while the compiler automatically manages it for you.


I think we agree on the fundamental concept, but have a varying way of how one interprets the 'accounting' term: is it the tracking of the memory in various categories (which I think we agree that Rust does automatically in a way that few other languages do), or is it marking of given chunk of memory to be tracked in a certain categoric way (which is what the Rust programmer decides in code). Is there a less ambiguous set of terms to apply for these cases?


The usual way to do event handling in Rust is to either expose an event loop directly, or use an async toolkit where you have multiple components, each owning their own subset of data and processing events related to it, talking to each other over channels, futures, or some other abstraction.

Closures are difficult to use for this due to Rust's concept of values having one owner, making it fiddly to share values over multiple closures without e.g. wrapping them in Rc<RefCell<T>>.


> Closures are difficult to use for this due to Rust's concept of values having one owner, making it fiddly to share values over multiple closures without e.g. wrapping them in Rc<RefCell<T>>.

But note that most UI toolkits already effectively expose all their objects as reference-counted aliasable-mutable—the same as Rc<RefCell<T>>, but more ergonomic.

I wonder if the right solution for this for these frameworks to allow programmers to easily create their own classes in the native widget class hierarchy (COM, Objective-C Foundation, GObject, QObject, etc.), with some combination of macros and other boilerplate generation. Then the rule would effectively be "instead of using Rc<RefCell<T>>, create a native widget class to hold your data if you need to multiply close over it". Not only would this be more ergonomic than the pure-Rust solution, but it'd also interoperate better with the underlying widget system: for example, on macOS and iOS Objective-C introspection APIs would work with such objects.


The issue is less the UI toolkit data and more all the other data in your app.


The solution I proposed is for all that other data.


https://doc.rust-lang.org/book/closures.html#closures-and-th...

Doesn't seem too crazy, as long as you don't want to mutate the variables the function closed over.


Mutating isn't hard either, and if it does get hard you can stick things in a Cell or RefCell.


It' not hard, it's just a bit more code than in JavaScript :)


I see Rust mentioned exponentially more here on HN lately and it has definitely peaked my interest as a 2nd year CS student. As soon as I get some free time, I'll give rust a try!


I'm a little torn by this, I love Rust and think it has the potential to be a great language. So in that regard this is really cool and neat. On the other hand I also really like swift and I have a hard time understand what benefit Rust would give over swift since Apple had put a large amount of work into the overall safety of swift interop with Obj-C, though it's far from perfect.


> I have a hard time understand what benefit Rust would give over swift

Rust is a drastically better-designed language, with substantially improved safety mechanisms, more advanced memory management (via lifetimes), generally better performance, and more powerful (generic) ADTs, to name a few things. That said, I'm not convinced it's better for the UI-centric development that Swift is targeting.


While I agree with you, to be fair to Swift it has other goals, and one primary one is to seamlessly work with existing objc, which it's excelled at.


> to be fair to Swift it has other goals

Exactly. If those are goals you share use Swift. If not, it may not be a good match.


So the question is valid: what benefit does one get using Rush over Swift on developing for Mac? Rust may be a better language overall, while still be an inferior choice in this context of Mac OS X Native app development.


I listed some of the benefits earlier in the thread. What I obviously can't do is prove it's better for native app development, because that depends on the developer.


Swift is a great language. I think the benfit comes in when you want to share the logic with another application, personally. I'd make the Apple version Swift + Rust.


Wouldn't it make sense for the magical macro to RC-ify the objects, and handle retain/release calls that way?


I assume they are using XCode to package the bundle because of signing so the app is able to be packaged for the app store?

If you just need to create an .app bundle you can manually create the folder structure and .plist files which allows you to run the rust binary directly while looking the same to a user as an XCode bundled app.


You don't even need to use Xcode proper to code-sign a Mac app. Just use the codesign command-line utility. I've developed a Mac app using only the command-line tools that come with Xcode (via a build system, of course), not Xcode itself. To submit to the Mac App Store, I use the Application Loader app that comes with Xcode.

Edit: But I do use Xcode to build, run, and submit iOS apps. Never tried to do it any other way for that platform.


Thats really good to know, and quite useful. Thanks!


    /// Designate this instance as suitable for being released
    /// once it is out of scope
    fn autorelease(&self) -> Self;
Note, this isn't quite what autorelease means. Often the time between the instance going out of scope and when it's actually released is significant.


What about UI? Are you forced to do layout in code, or are you able to load in Xibs or Storyboards?


You can take either approach. I opted to do it in code[0] as a personal preference since it reduced the amount of time spent jumping between Xcode and Vim, but creating a xib or storyboard and referencing it from Rust using NSBundle resources is just as possible. Maybe I should add examples of using resources to the code samples in the blog post. Thanks!

0: https://github.com/kattrali/webkitten/blob/6ac4ced187b44fe91...


That would be nice. I myself am someone who can't stand doing UI work in code, and prefer to lay it out visually if possible.


How do you subclass an ObjC class from Rust — say, to create a delegate for a UI element?

How is type safety handled? It looks like msg_send itself isn't type safe, but there are hand-written bindings… is that right?


> How do you subclass an ObjC class from Rust — say, to create a delegate for a UI element?

The objc::declare module includes ClassDecl, which enables registering a new Objective-C class.

objc::declare documentation: http://sasheldon.com/rust-objc/objc/declare/index.html

Example usage: https://github.com/kattrali/rust-mac-app-examples/blob/maste...

> How is type safety handled? It looks like msg_send itself isn't type safe, but there are hand-written bindings… is that right?

Correct. msg_send operates on objc::runtime::Object references. The handwritten bindings use the impl_objc_class macro referenced in the post to define separate types for different Objective-C classes, and make messaging the wrong type of object a compile-time rather than a runtime error.

The impl_objc_class macro implements the ObjCClass and PartialEq traits for a new type: https://github.com/kattrali/rust-mac-app-examples/blob/maste...




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: