Hacker News new | past | comments | ask | show | jobs | submit login
Objective-Rust (belkadan.com)
206 points by ingve on Aug 28, 2020 | hide | past | favorite | 33 comments

Funny enough, this is exactly what my objrs crate is meant for: https://gitlab.com/objrs/objrs

You can implement new Objective-C classes directly in Rust. Or use Objective-C classes/methods from Rust. objrs is mostly a collection of macros that transforms the Rust code to be ABI-compatible with normal Objective-C code. There isn't any runtime work done by objrs. It's all done at compile time.

Here's an example of creating an new class:

  #[objrs(class, super = NSObject)]
  pub struct StringCollection {
    strings: Vec<String>,

  impl StringCollection {
    #[objrs(selector = "new", no_impl)]
    pub fn new() -> objrs::Strong<StringCollection> {}

    #[objrs(selector = "printStrings")]
    pub fn print_strings(&self) {
      for string in self.strings.iter() {
        println!("String: {}", string);
I'm still slowly rewriting it. It requires nightly Rust, and unfortunately doesn't compile with the latest nightly. And it doesn't currently build from HEAD. But it's been a fun hobby project.

This is reminding me that I'd love to see experiments with macro's for implementing Swift's ABI, not for Swift interop but to explore the space of a more native, unsafe-free SO/DLL ABI for Rust than what C offers.


tries to offer #[repr(Swift_5)]

My plan has always been to branch out and try to support Swift once objrs is more mature. But I haven't explored the Swift ABI at all so I might only be able to realistically support a small subset of Swift, if anything at all.

> I'm still slowly rewriting it. It requires nightly Rust, and unfortunately doesn't compile with the latest nightly. And it doesn't currently build from HEAD. But it's been a fun hobby project.

I hear this story a lot about Rust, and it makes me wary of the language. Code that's written needs to keep working. What made you decide to use nightly in the first place?

> I hear this story a lot about Rust, and it makes me wary of the language.

Why wary? The whole point of experimental nightly is that you can try out new exciting features which are not stabilised yet. Sometimes they will be changed before a full release and you have to deal with it.

But if you stay with stable releases, code keeps working.

It’s the bit where it has to be a nightly but not a too new nightly that gives heebiejeebies.

Kind of reminds of some godawful government websites which had very finicky requirements in which their ActiveX components would run: precise Internet Explorer builds, with these updates but NOT WITH THESE updates, and god forbid you update your Windows XP past SP1.

Nightly is totally unstable. Features get added, and might get removed if the team decides they're not a good idea. So programs that want to use such an experimental feature might only work for a certain few versions of Nightly, because Nightly only guarantees the stability of features that have been released to Stable.

That's why it's strongly recommended not to use Nightly to write your programs: they'll almost certainly break, and since Nightly gets released every 24 hours they might break daily. If you don't have the time to fix things every day, you shouldn't be using the unstable features!

I've looked at Rust a few times, and every time I installed the stable release, but as soon as I wanted to actually do something, I realise that the library or feature I wanted to use was not actually available in stable.

At least at the time (a year or so ago perhaps) it was not possible to actually use stable for regular development. Has this changed now?

In our 2018 survey, 56% of people used nightly. In 2019, it was down to 30%.

This question is always multiple choice, so people who use Rust a lot may choose both nightly and stable. The vast majority of my projects target stable, with my operating system ones being the exception that use nightly.

Rewriting it has nothing to do with it being in Rust. The reality is that I don't have a lot of free time, but I really wanted to get the project into a functioning state (even if it sucked). So I took some shortcuts a couple years ago in order to get it fully functioning (more or less) in a short amount of time. I would have taken those same shortcuts in any language.

Cool! Does it support laying out ivars too?

Yeah, it supports ivars just fine. Computing byte offsets for each ivar was a pain the butt. By default it will use the field's name as the ivar name. You can rename the ivar by using #[objrs(ivar, name = "whatever")] on the ivar. You can also pass an expression to be used to construct the ivar and give it a default value.

Documentation for the various macros is at [1], which includes a number of small examples. There's a larger demo[2] that uses Metal to render an interactive image. There are also framework bindings for some common frameworks (I want to add bindings for all of Apple's frameworks, but I need finish the rewrite first). Those bindings include some uses of ivars on extern classes too[3].

[1]: https://gitlab.com/objrs/objrs/blob/master/DOCUMENTATION.md

[2]: https://gitlab.com/objrs/objrs/-/tree/master/demo

[3]: https://gitlab.com/objrs/objrs/-/blob/master/frameworks/foun...

I hope Rust / Objective-C interop gets some more love soon. There are a number of pain points, including inconsistent wrapping styles (the "cocoa" crate implements things like NSView as traits on id), autorelease pools always being a pain, and a fair amount of bloat from the msg_send! macro (a lot of which is boilerplate error handling).

Not exactly obj-C, but there are also problems two inconsistent ways of wrapping other macOS types: some (like the Metal crate) use foreign_types, while others use the TCFType mechanism. Among other things, these have two different meanings for "Ref" types, which can be very confusing.

So it's possible to get stuff done, but it doesn't feel nice. By contrast, the Rust/Windows world pretty well organized, with Peter Atashian's winapi providing a solid foundation, the wio crate being a good way to get ComPtr and other utilities, and Microsoft's com-rs coming soon as a standard way to do Rust-side implementation of COM interfaces.

I experimented with this in cacao: https://github.com/ryanmcgrath/cacao

I just need to get around to "finishing" it. I think most of the other approaches miss what people would realistically need out of ObjC/Rust interop, so... well, that's my hat in the ring.

Yup, that is a promising direction. I think a lot of my concern is around building a consensus and getting people on the same page.

Seeing the headline gave me an immediate rush, mixing horror and fascination.

The article did not disappoint.

I actually think this could be developed into something very useful. Rust is a lot cleaner than Swift, and proper Objective-C interop could make it a viable alternative for app development.

(I'd avoid trying to replicate ObjC's syntax, though, since it's the worst part of that language.)

Swift and Objective-C share a memory model, though, which Rust does not. I suspect that any Rust code that interoperates with Objective-C will never be "cleaner" than the corresponding Swift code.

I mean, it depends on the Swift code you write - basic example which could be done fairly closely: https://github.com/ryanmcgrath/cacao/blob/trunk/examples/aut...

Having written my fair share of both languages, I don't think they're as far apart as people think... yet, anyway.

I doubt my response will age well.

I’ve had some pretty good luck integrating Common Lisp and the Objective-C runtime: the difference of memory models isn’t a huge problem, as long as there’s a nice way to map between them.

Swift selectively shares the objc memory model.

Apps don't need to be in Rust. And maybe they even shouldn't be programmed in Rust.

If your application or backend or whatever can tolerate GC, I would strongly recommend that you use it. While Rust has made manual memory management easier than either C++ or C, it still requires more work than a GC'd language.

Rust has a lot of things going for it, but this obsession with writing everything in Rust that has taken HN by storm is a little confusing.

Swift is unequivocally a higher-level language than Rust and definitely should be used for iOS development unless you have a very good reason to use Rust.

Swift does not do cycle-collecting GC, either. Not really any "higher-level" than Rust, when you look at a comprehensive view of the problem space. And obligate ARC ala Swift has all kinds of performance pitfalls, to the point where it can be even worse than obligate tracing GC.

Yes. It might help to can think of it this way:

Rust, with the borrow-checker, strong-arms the developer into using memory patterns that the compiler manage handle without the overhead of reference counting or other runtime mechanism.

Those same memory patterns are available in Swift as well, which the compiler can then optimize as well (in theory -- as far as I'm aware there are various unimplemented optimization opportunities in Swift).

So in a sense, in Swift you can opt-in or out of rust-level memory management.

Of course, a big difference is that Rust tells you up-front when you've got it right, but in Swift it's not at all clear. You need to profile. Swift does have a clear delineation between reference types (which use ARC) and values types, so you can get on the right path by choosing value types. Value types in Swift are very rich and powerful so it's not a hard path to follow.

ARC is GC, and anyone who says otherwise is crazy.

Usually on Apple’s platforms ARC and GC are treated separately because Objective-C has shipped with a conventional garbage collector as well as reference counting and it’s difficult to tell them apart otherwise.

Swift language by definition is at the same level as Rust. "Higher Level" means that developer is order of magnitude more productive in language X than in language Y. This is not a case for Swift vs Rust.

Swift does implicit allocations, doesn't have explicit monomorphisation, requires a non-trivial runtime. Language functionality like ARC and ivars compiles to a lot of "magic" (compared to Rust's focus on zero-cost abstractions). Swift's standard library is full of dynamic dispatch (e.g. Swift strings are a collection of classes with abstracted-away and variable in-memory representation. Rust's strings are dumb pointer+length).

Swift is much higher-level than Rust. It's at best at Go's level. You'd expect Swift's performance to be good, but in low-level tasks Swift is actually closer to JS/TypeScript than anything else: https://github.com/ixy-languages/ixy-languages

I'd be careful about drawing general conclusions from https://github.com/ixy-languages/ixy-languages

It's using some non-optimal patterns that you would not normally use in Swift for code if you wanted it to perform well. (There's a nice performance analysis if you click in, showing that it's using ARC in an inner loop, which is taking 3/4th of the time. That's not something you need to do.)

D has had native interoperability support for Objective-C for a couple of years now: https://dlang.org/spec/objc_interface.html.

I tried something similar in Nim. While fun, ultimately I was faced with the reality of a lack of proper typing and other tooling support, which would be a mountain of work to add on top.

Sometimes I’m truly amazed with what fellow programmers are able to do.

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