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!
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.
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
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.
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'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.
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.
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.
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.
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!
> 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.
Cool to see its possible to do objective c without everything being unsafe though!