swift-bridge
swift-bridge copied to clipboard
Support Rust `char` and Swift `Character`
For example, the following should be possible:
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
fn reflect_char(character: char) -> char;
}
}
// Swift
let char: Character = reflect_char(Character("a"))
As far as I read Character
implementation, Character
seems to be represented as String
. So, we might need to have something like RustString
on the Swift Side.
The reason that we convert Rust std::string::String
to a Swift RustString
is to avoid needing to copy the entire string since we don't know how many bytes of heap memory that the string is using https://github.com/chinedufn/swift-bridge/issues/5#issuecomment-1003241697 .
A unicode character has a maximum size of 4 bytes, so we can create a small C FFI representation to handle bridging characters.
Something along the lines of:
// in src/std_bridge/character.rs
#[repr(C)]
struct CharacterFfiRepr {
byte_1: u8,
byte_2: u8,
byte_3: u8,
byte_4: u8,
length: u8
}
Then when passing a character between languages we would construct this character FFI representation and set the length to the number of bytes in the unicode character (1, 2, 3 or 4).
Then on the other side we could match (Rust)
or switch or if/else if/else (Swift)
on the length and use that to know how many of the 4 bytes to use to construct the Rust char/Swift Character
.
Could probably use something like this to get the character's bytes on the Swift side https://developer.apple.com/documentation/swift/character/utf8 .
So yeah, just some high-level ideas. There might be a better way than what I laid out, but I think it should work.
Hi, @chinedufn.
According to Rust char representation,
char is always four bytes in size.
Unlike Rust's String
, I guess that Rust char
's representation doesn't get encoded as utf8
.
So, we might need to have:
//We don't need to have a length field.
#[repr(C)]
struct CharacterFfiRepr(u32);
What do you think about this?
I don't know whether or not Rust's memory layout for characters is stable. If it is, then sure.
Otherwise, it might be better to use something like https://doc.rust-lang.org/std/primitive.char.html#method.encode_utf8 to encode it to into a byte slice.
Then use Swift's String(data: Data(bytes: ..))
on the other side to decode the slice into a character.
Something like:
// Rust
let mut bytes = [0; 4];
'a'.encode_utf8(&mut bytes);
u32::from_be_bytes(bytes) // <- FFI repr
// Swift
let charUInt32: UInt32 = ...; // <- the char that came from Rust
// I copied this from https://stackoverflow.com/a/29970559
var bigEndian = charUInt32.bigEndian
let count = MemoryLayout<UInt32>.size
let bytePtr = withUnsafePointer(to: &bigEndian) {
$0.withMemoryRebound(to: UInt8.self, capacity: count) {
UnsafeBufferPointer(start: $0, count: count)
}
}
let byteArray = Array(bytePtr)
let charString = String(data: Data(bytes: byteArray))
let char = Character(charString)
Otherwise, it might be better to use something like https://doc.rust-lang.org/std/primitive.char.html#method.encode_utf8 to encode it to into a byte slice.
I see.
I'd like to try to implement this feature!!
Sure!