Building native macOS applications with Rust
blog.bugsnag.com
blog.bugsnag.com
Cool to see its possible to do objective c without everything being unsafe though!
Is it anywhere near as easy as doing this in Javascript? Or does it require such things as manual memory management?
Doesn't seem too crazy, as long as you don't want to mutate the variables the function closed over.
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.
(See other files in that repo for more examples.)
It's not quite as low-friction as JS, but it's awfully close.
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.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.
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!)
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).
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
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.
Exactly. If those are goals you share use Swift. If not, it may not be a good match.
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.
Edit: But I do use Xcode to build, run, and submit iOS apps. Never tried to do it any other way for that platform.
/// 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.0: https://github.com/kattrali/webkitten/blob/6ac4ced187b44fe91...
How is type safety handled? It looks like msg_send itself isn't type safe, but there are hand-written bindings… is that right?
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...