public object FfiConverterString: FfiConverter { // Note: we don't inherit from FfiConverterRustBuffer, because we use a // special encoding when lowering/lifting. We can use `RustBuffer.len` to // store our length and avoid writing it out to the buffer. override fun lift(value: RustBuffer.ByValue): String { try { val byteArr = ByteArray(value.len.toInt()) value.asByteBuffer()!!.get(byteArr) return byteArr.toString(Charsets.UTF_8) } finally { RustBuffer.free(value) } } override fun read(buf: ByteBuffer): String { val len = buf.getInt() val byteArr = ByteArray(len) buf.get(byteArr) return byteArr.toString(Charsets.UTF_8) } fun toUtf8(value: String): ByteBuffer { // Make sure we don't have invalid UTF-16, check for lone surrogates. return Charsets.UTF_8.newEncoder().run { onMalformedInput(CodingErrorAction.REPORT) encode(CharBuffer.wrap(value)) } } override fun lower(value: String): RustBuffer.ByValue { val byteBuf = toUtf8(value) // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) rbuf.asByteBuffer()!!.put(byteBuf) return rbuf } // We aren't sure exactly how many bytes our string will be once it's UTF-8 // encoded. Allocate 3 bytes per UTF-16 code unit which will always be // enough. override fun allocationSize(value: String): ULong { val sizeForLength = 4UL val sizeForString = value.length.toULong() * 3UL return sizeForLength + sizeForString } override fun write(value: String, buf: ByteBuffer) { val byteBuf = toUtf8(value) buf.putInt(byteBuf.limit()) buf.put(byteBuf) } }