I have been looking at how to create an iOS app recently, and more particularly, its backend. SwiftUI as a front-end framework these days is quite lovely, but I am not convinced that Swift ecosystem is really good enough to do backend stuff - either on the device, or especially outside it (although Apple is making baby steps with Embedded Swift).

UI on the other hand seems to be the best done with Swift (and notably SwiftUI now). It seems considerably better than Interface Builder based objective C was that I used last time around.

Due to that, I spent some time recently evaluating two contenders that seemed most promising to me.

The mission

The candidates I looked at hard were Go and Rust. I have written quite a bit of Go, and Rust toy program or two. Producing something that does network request in separate thread, and asynchronously returns it back in a callback was pretty easy to write in both languages (not particularly prettily, mind you, as this is only test code).

After that, interfacing it with Swift is what took longer. Here are some notes and code links from that exercise.

Language: Go

I used mobile module - golang.org/x/mobile - Go Packages and its gomobile command - golang.org/x/mobile/cmd/gomobile - Go Packages tool to create the wrapper modules for Swift.

Pros:

  • Go interface just out of the box was visible to Swift
  • Fast compilation, relatively painless to set up

Cons:

  • No customisation options for language bindings, and some of it was bit weird
    • e.g. given package Foo, everything exported by it had Foo prefix
  • Some types (notably, slices (essentially vectors in other languages)) were not supported

The actual code and more detail about its results: fingon/swift-go-test: Swift <> Go interoperability test app

Language: Rust

Rust seems to have multiple language binding mechanisms (like almost anything else, for that matter). Using Github stars as a measure of popularity (as well as recent release existing), I chose to go with mozilla/uniffi-rs: a multi-language bindings generator for rust.

Pros:

  • Highly customisable language bindings
  • Bit smaller binary than Go

Cons:

The actual code and more detail about its results: fingon/swift-rust-test: Swift <> Rust interoperation test app

Actual numbers about build times and size

Non-release Rust builds are quite slow (and large), so they were not considered. So we are comparing Go defaults (with or without symbol stripping) to different Rust release (with or without LTO).

Compilation time from scratch (.xcframework, 3 arches)

Incremental build with both is reasonably fast, but in e.g. CI we may want to do build-from-scratch and the speed of that does matter.

GoRust
16s (default)82s (release)
141s (release+LTO)

Disclaimer: The Rust build process COULD have been sped up to some extent by parallelising some parts of build.sh - it seems Rust pipeline in general is not particularly good at utilising cores unlike Go which uses all cores pretty much all the time.

Binary size (.xcframework, 3 arches)

GoRust
63MB (default)89MB (release)
31MB (stripped symbols)19MB (release+LTO)

Conclusion

For us, speed of development outweighs the somewhat smaller binaries we could get with fully optimised Rust choice. Simpler language is also a plus. So it seems we are going to go with Go.