DEV Community

Cover image for How we built a Swift app that uses Rust
Mrinal Wadhwa for Ockam

Posted on • Updated on • Originally published at github.com

How we built a Swift app that uses Rust

Yesterday our team released Portals, an open source Mac app built in Swift. It uses the Ockam Rust library to privately share TCP or HTTP services from your Mac with your friends over End-to-End Encrypted Ockam Portals. A shared service appears on their localhost!

In this post, I'll dig into how the SwiftUI macOS app interacts with Rust code.

If you're curious about Portals for Mac. You can learn more about it in this article and install is using Homebrew as follows:

brew install build-trust/ockam/portals
Enter fullscreen mode Exit fullscreen mode

Here's a 2 minute video of the application in action:

Swift <> Rust

The Portals functionality was already implemented in the Ockam Rust library. We set out to create a great macOS-native experience.

Our first attempt at building the app was using Tauri. This made sense as we wanted to use the Ockam rust library and most people on our team are comfortable building things in Rust. This first version was easy to build and had all the basic functions we wanted. However, the experience of using the app wasn't great. Tauri only gave us minimal control over how the menu was rendered and what happened when a user interacts with the menu. This version of the app felt like it belonged in a 10 year old version of macOS when compared to super easy to use menubar items built into macOS Sonoma.

We realized that to have the rich experience we want, we must build the app using SwiftUI.

Unfortunately, we couldn't find an off-the-shelf solution, to integrate Swift and Rust, that would give us the best of both worlds; the safety of Rust, and the rich macOS-native experience of SwiftUI. After some more digging we realized we can connect the two using C-89. Rust is compatible with the C calling convention, and Swift is interoperable with Objective-C, which is a superset of C-89.

Image description

We wrote the Rust data structures that needed to be visible to Swift twice. One version is idiomatic in Rust and easy to use. The other version is C compatible using pointers and memory that is manually allocated with malloc. We also exposed some C-compatible APIs that use raw-pointers in unsafe rust to convert the idiomatic data structures to their C-compatible versions. Finally we automatically generated a C header with the help of the cbindgen library.

On the Swift side, we could have called the C APIs directly, but C data structures are not first class citizens in Swift. This makes them harder to use idiomatically within SwiftUI code. Instead, we chose to duplicate the data structures in Swift and convert between C and Swift. This may seem burdensome, but practically, the shared state doesn't change very often. The ability to quickly build components in SwiftUI using constructs like if let .., ForEach, enum etc. is super helpful and worth the tradeoff.

Here's an example of the same structure in its 4 forms:

// Rust idiomatic structure
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct LocalService {
    pub name: String,
    pub address: String,
    pub port: u16,
    pub shared_with: Vec<Invitee>,
    pub available: bool,
}

// Rust C-compatible structure
#[repr(C)]
pub struct LocalService {
    pub(super) name: *const c_char,
    pub(super) address: *const c_char,
    pub(super) port: u16,
    pub(super) shared_with: *const *const Invitee,
    pub(super) available: u8,
}

// Generated C header structure
typedef struct C_LocalService {
  const char *name;
  const char *address;
  uint16_t port;
  const struct C_Invitee *const *shared_with;
  uint8_t available;
} C_LocalService;

// Swift idiomatic structure
class LocalService {
    let name: String
    @Published var address: String?
    @Published var port: UInt16
    @Published var sharedWith: [Invitee]
    @Published var available: Bool
}
Enter fullscreen mode Exit fullscreen mode

The Swift app is statically linked to our Rust lib at compile time. The data flow is simple: UI interactions are sent from Swift to Rust as actions by calling C APIs, change events are emitted only by Rust, and Swift is notified using callbacks that lead to updates to the UI.

Most code in the SwiftUI views looks just like any other SwiftUI application.

VStack(alignment: .leading, spacing: 0) {
    Text(service.sourceName).lineLimit(1)

    HStack(spacing: 0) {
        Image(systemName: "circle.fill")
            .font(.system(size: 7))
            .foregroundColor( service.enabled ? (service.available ? .green : .red) : .orange)

        if !service.enabled {
            Text(verbatim: "Not connected")
        } else {
            if service.available {
                Text(verbatim: service.address.unsafelyUnwrapped + ":" + String(service.port))
            } else {
                Text(verbatim: "Connecting")
            }
        }
    }
...
Enter fullscreen mode Exit fullscreen mode

If you're curious to learn more, checkout the code for the ockam_app_lib crate and the Portals app in Swift. The Makefile in the swift folder is also a good place to explore how everything is built and linked together.

If you're interested in contributing to the app's Swift or Rust code, we add new good first issues every week and love helping new contributors. Join us on the contributors discord.

Learn more about Portals for Mac

GitHub logo build-trust / ockam

Orchestrate end-to-end encryption, cryptographic identities, mutual authentication, and authorization policies between distributed applications – at massive scale.

🚀 Portals for Mac – A macOS app built in Swift that uses the Ockam Rust library to privately share a service on your Mac with anyone, anywhere. The service is shared securely over an end-to-end encrypted and mutually authenticated Ockam Portal. Your friends will have access to it on their localhost! This app is a great example of the kinds of things you can build with Ockam 👉


Discord

Trust for Data-in-Motion

Ockam is a suite of open source programming libraries and command line tools to orchestrate end-to-end encryption, mutual authentication, key management, credential management, and authorization policy enforcement – at massive scale.

Modern applications are distributed and have an unwieldy number of interconnections that must trustfully exchange data. To trust data-in-motion applications need end-to-end guarantees of data authenticity, integrity, and confidentiality. To be private and secure by-design, applications must have granular control over every trust and access decision.…

Top comments (14)

Collapse
 
drumm profile image
Sam J.

Thanks for the article!!

Just to mention that this link is not working because of the braces 😉: dev.to/build-trust/(https://github...

Collapse
 
mrinal profile image
Mrinal Wadhwa

Thank you so much for pointing that out. Just fixed it!

Collapse
 
drumm profile image
Sam J.

It’s great to see such an app being established with one of the 2 best programming languages available (some would say 3), unfortunate in 2023 we still have to rely on C for interoperability, imagine this would become Rust! 🤯

Collapse
 
cemcneill profile image
CEMcNEill

I know you guys have been working on this quietly for a while now @mrinal. So exciting to see it finally released.

Collapse
 
mrinal profile image
Mrinal Wadhwa

Thank you @cemcneill, so glad you like it!

Collapse
 
proteusiq profile image
Prayson Wilfred Daniel

Mind blowing 🤯! Thank you for documenting your journey.

Collapse
 
mrinal profile image
Mrinal Wadhwa

Thank you @proteusiq. So happy to hear that you liked it!

Collapse
 
mattgreg profile image
Matthew Gregory

Portals is a cool app. @mrinal can you elaborate on the differences between Portals and ngrok?

Collapse
 
mrinal profile image
Mrinal Wadhwa • Edited

@mattgreg Great question. Here's the key difference in behavior:

When you use ngrok your local service is exposed to the Internet which mean attackers on the Internet can try different ways to attack it and break into your computer. ngrok servers also terminate TLS and see your unencrypted application data.

When you use Portals, your service is not available on the Internet, it is ONLY exposed to your invited friends. Attackers from the Internet have no way of attacking it. Ockam servers cannot see or manipulate your application's data since it is end-to-end encrypted between your friend's computer and your computer.

Collapse
 
etorreborre profile image
Eric Torreborre

Could you say a bit more about the memory management side since Swift is garbage collected and Rust is not? At the end of the day how is memory reclaimed?

Collapse
 
davidebaldo profile image
Davide Baldo

Sure!
First, during swift toward rust interactions, we need to manually consider the scope of the validity of parameters: they will be valid only until the functions returns. Adding the async context on the rust side, we need to copy the parameters before returning the function to make sure we don't incur in any memory violation or use-after-free.
When it's rust calling swift instead, we make a similar consideration: parameters will be valid only until the function returns, subsequently, the allocated memory of structures needs to manually be freed.

Collapse
 
dev99 profile image
kalyan swaroop

What if you had used protobuf to generate the structures on the two sides ?
Would that have reduced some of your structure translation work (and made it safer perhaps)?

Collapse
 
mrinal profile image
Mrinal Wadhwa

Hi @dev99 good question. I don't think protobuf is helpful here since there's no need to serialize the data. We can simply access them in-memory.

Collapse
 
matthiasg profile image
matthiasg

So you used Tauri v2 and found it not good ? v2 is getting close to beta now, so do you think they will get better or is it of fundamental nature ?