Opaque Types for UniFFI
Tags: rust, ffi, python, uniffi
Published: 2024-03-07 06:44:41

---
title: Opaque Types for UniFFI
published: true
description: How to create opaque types for UniFFI interfaces
tags: rust, ffi, python, uniffi
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sexmylqnfcj5v18283ey.png
---

I'm going to show you how to use slab to send handles from rust to python and back to rust using an arena allocator called [`slab`](https://github.com/tokio-rs/slab).

## tl;dr

With `slab`, we allocate types into a chunk of memory that has been pre-allocated and get an id back. It's really just an index into the chunk of memory, indexed by the size of the type we are using.

We have to have a "global" (static in some way) chuck of this memory to handle the FFI nature of it.

The example code from this article is available here: https://github.com/graysonarts/uniffi-slab-example

## Background
On my youtube series ["Growing up Rust"](https://www.youtube.com/playlist?list=PLdDXTuzb-Fa8uoXnUGz9GCcVM2mCnbapu), I'm building a personal CRM in Rust with a Swift frontend. I'm using [`CQRS`](https://martinfowler.com/bliki/CQRS.html) and an event-driven architecture with the least amount of swift as possible. I'm using [`UniFFI`](https://mozilla.github.io/uniffi-rs/) to generate the bindings for swift (and in this example python)

To accomplish this, I need the ability to create commands and then submit the command to the core rust model for processing.

I'm handling this part of it by an enum called `PersonCommand` which handles all the different types of changes that can happen to a person. I want to treat the `PersonCommand` as completely opaque to the swift code. It just needs to know how to create the necessary commands and submit them to the run queue.

Because I want to batch up commands at the dialog boundary, I need swift to be able to hold onto the Command before it's processed, but it doesn't need access to anything inside of the command, so I want it to be opaque. `UniFFI` doesn't support this out of the box (that I could find, let me know if I'm wrong!).

## The key pieces.
I'm not going to cover setting up `UniFFI` so make sure you check their documentation if you need help there.

### The global slab

In order for slab to be global, we need to make it a static variable. There are other patterns you could use for this, but this is the simplest.

```rust
static COMMAND_SLAB: Lazy<Mutex<Slab<OpaqueType>>>
   = Lazy::new(|| Mutex::new(Slab::new()));
```

`Lazy` is from the `once_cell` crate. I tried to use the `std::cell::LazyCell` but I couldn't get it working and is not a standard feature yet, so I'd rather bring in `once_cell` which does work well.

We need the `Mutex` because the slab may be accessed from multiple threads. Other kinds of locks would work like a `RwLock` but again, `Mutex` is simple and straightforward. Your mileage may vary depending on your use case and workload. In a UI application, all commands are generated as a result of user actions, so  we should be okay.

### The handle type alias
I decided to use a type alias for code clarity, but it's just a `u32`. Technically, slab returns a `usize` but that can't be marshalled across the FFI, so we cast it to a u32. Adjust as necessary. It might be safer to serialize and deserialize to a string using `serde` or some other crate.

```rust
type OpaqueHandle = u32;
```

### The factory method

Because it's an opaque type to our FFI, we need to have a function that returns the handle for specific instance of the type.

```rust
fn ffi_make_opaque(number: u32) -> OpaqueHandle {
    COMMAND_SLAB.lock().unwrap().insert(OpaqueType {
        ...
    }) as OpaqueHandle
}
```

### Accepting the handle

Now if we need to send the handle back into rust, we would do something like this

```rust
    let slab = COMMAND_SLAB.lock().unwrap();
    let command = slab.get(handle as usize).unwrap();
    println!("Processing handle: {:?}", command));
```

In this case, we need to bind the slab to a local variable to control the lifetime, and then we get the command from the slab and do something with it.

Ideally, this function would get the command and then call it's execute function.

## Alternative Approaches

If we wanted, we _could_ get the raw pointer to the type and pass that around as a value between the different languages, but I found that it was a messier and more error prone way to handle it than to use `slab`.