on
Bridging Rust to Swift, pt. 5: Swift Packages, the final wrap
In our last post, we built a
Swift package that made distributing and using our .xcframework
a lot easier.
We will now tackle making our interaction with the C library more in line with
Swift principles.
The major sticking points we found while using our C API in the last post were
- Structs and their associated functions were decoupled.
- Working with
OpaquePointer
s was a bit cumbersome. - We had to manually manage memory.
One class to rule them all
All the aforementioned issues can be tackled by writing a single Swift class
SwiftPerson
. Our Swift package already has a file SwiftPerson.swift
with the
necessary boilerplate code. By default, the generated SwiftPerson
declaration
will be of type struct
- we need to change that to class
. We make this
change as we can’t have a deinitializer in a struct
.
We will have the class manage a single OpaquePointer
to a C struct Person
.
When the class is deinited in Swift, it will automatically free the memory
allocated in C. Finally, it will encapsulate all the C functions that interact
with the struct.
public class SwiftPerson {
var person_op: OpaquePointer
public init(age: UInt32) {
person_op = create_person(age)
}
deinit {
withUnsafePointer(to: person) { unsafe_person in
free_person(unsafe_person.pointee)
}
}
}
Let’s break this code down
- We first declare a variable to reference our
OpaquePointer
. - In our init, we create a C struct
Person
and assign the returnedOpaquePointer
toperson_op
. - Finally, in the
deinit
, we make sure that we always free the memory that was allocated in C duringinit
.
To complete our API encapsulation, we need a way to access the age on Person
.
We can do this by either an associated function in Swift or a computed property.
Computed properties feel a bit more natural in this instance.
var age: UInt32 {
withUnsafePointer(to: person) { unsafe_person in
return get_person_age(unsafe_person.pointee)
}
}
The computed property hides the mechanics behind the working of the
OpaquePointer
. We now have a perfectly usable Swift class SwiftPerson
that
encapsulates our C struct Person
. Our final class definition reads as
public class SwiftPerson {
var person: OpaquePointer
public init(age: UInt32) {
person = create_person(age)
}
deinit {
withUnsafePointer(to: person) { unsafe_person in
free_person(unsafe_person.pointee)
}
}
var age: UInt32 {
withUnsafePointer(to: person) { unsafe_person in
return get_person_age(unsafe_person.pointee)
}
}
}
The C API (and it’s existence) is completely hidden from the users of our
package SwiftPerson
, who can now do
let swifty_frank = SwiftPerson(age: 40)
let age = swifty_frank.age
Much simpler (and safer). This concludes our simplistic introduction to bridging rust/C to swift. However, once we get down to it, there are other topics that would require us to put our thinking caps back on - essentially around handling more complicated data structures and strings. Perhaps, another post for another day…