swift-bridge icon indicating copy to clipboard operation
swift-bridge copied to clipboard

Support Rust `char` and Swift `Character`

Open chinedufn opened this issue 1 year ago • 6 comments

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"))

chinedufn avatar Mar 06 '23 11:03 chinedufn

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.

NiwakaDev avatar Mar 07 '23 03:03 NiwakaDev

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.

chinedufn avatar Mar 07 '23 12:03 chinedufn

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?

NiwakaDev avatar Mar 30 '23 03:03 NiwakaDev

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)

chinedufn avatar Mar 30 '23 16:03 chinedufn

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!!

NiwakaDev avatar Apr 01 '23 01:04 NiwakaDev

Sure!

chinedufn avatar Apr 01 '23 01:04 chinedufn