Skip to content

Commit

Permalink
Make RustBuffer growable on demand, grow it rather than pre-calculati…
Browse files Browse the repository at this point in the history
…ng final size.

Fixes mozilla#251, with some additional cleanups that kind of fell out along the way.

The Kotlin and Python bindings currently do an awkward pre-calculation of the serialized
size of a record, so that they can pre-allocate a `RustBuffer` into which to write it.
This commit changes things to use a grown-on-demand `RustBuffer`, which brings with it
some nice improvements.

The broad strokes of this commit:

* The `RustBuffer` struct now exposes the `capacity` field from Rust, and a new
  `reserve` method mirroring the behaviour of `Vec<u8>::reserve`. This allows
  the foreign-language code to grow rust buffers on demand.

* Since `RustBuffer` is now specific to how `Vec`s are implemented in rust, there
  is a new `ForeignBytes` struct that can be used for passing a buffer of bytes
  *in* to rust from the foreign language code

* When serializing complex data types, the Kotlin and Python bindings now grow
  the target buffer on demand rather than pre-calculating its final size.

* The swift bindings still serialize into a local byte array and then copy it
  over into a `RustBuffer` at the end, but this process is slightly simpler
  thanks to the new `ForeignBytes` struct.

* Since we can no longer use the `ffi_support::ByteBuffer` struct for buffers,
  we are no longer bound to its pre-canned destructor functions. This means we
  can make all FFI functions take an `&err` out param, simplifying some of the
  code gen logic.
  • Loading branch information
rfk committed Sep 12, 2020
1 parent a23fd65 commit c35e904
Show file tree
Hide file tree
Showing 39 changed files with 892 additions and 311 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ target
.cargo
.*.swp
*.jar
.vscode
2 changes: 1 addition & 1 deletion examples/rondpoint/tests/bindings/test_rondpoint.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ listOf(0.0F, 1.0F, -1.0F, Float.MIN_VALUE, Float.MAX_VALUE).affirmEnchaine(st::t

// Doubles
// MIN_VALUE is 4.9E-324. Accuracy and formatting get weird at small sizes.
listOf(0.0, 1.0, -1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmEnchaine(st::toStringDouble) { s, n -> s.toDouble() == n }
listOf(0.0, 1.0, -1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmEnchaine(st::toStringDouble) { s, n -> s.toDouble() == n }
2 changes: 1 addition & 1 deletion examples/rondpoint/tests/bindings/test_rondpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ extension Array where Element: Equatable {
assert(fn(v) == v, "identique_\(type(of:v))(\(v))")
}
}
}
}
118 changes: 118 additions & 0 deletions uniffi/src/ffi/foreignbytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::convert::TryInto;

/// Support for reading a slice of foreign-language-allocated bytes over the FFI.
///
/// Foreign language code can pass a slice of bytes by providing a data pointer
/// and length, and this struct provides a convenient wrapper for working with
/// that pair. Naturally, this can be tremendously unsafe! So here are the details:
///
/// * The foreign language code must ensure the provided buffer stays alive
/// and unchanged for the duration of the call to which the `ForeignBytes`
/// struct was provided.
///
/// To work with the bytes in Rust code, use `as_slice()` to view the data
/// as a `&[u8]`.
///
/// Implementation note: all the fields of this struct are private and it has no
/// constructors, so consuming crates cant create instances of it. If you've
/// got a `ForeignBytes`, then you received it over the FFI and are assuming that
/// the foreign language code is upholding the above invariants.
///
/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified
/// to give a read-only view of externally-provided bytes.
#[repr(C)]
pub struct ForeignBytes {
/// The length of the pointed-to data.
/// We use an `i32` for compatibility with JNA.
len: i32,
/// The pointer to the foreign-owned bytes.
data: *const u8,
}

impl ForeignBytes {
/// View the foreign bytes as a `&[u8]`.
///
/// # Panics
///
/// Panics if the provided struct has a null pointer but non-zero length.
/// Panics if the provided length is negative.
pub fn as_slice<'a>(&'a self) -> &'a [u8] {
if self.data.is_null() {
assert!(self.len == 0, "null ForeignBytes had non-zero length");
&[]
} else {
unsafe { std::slice::from_raw_parts(self.data, self.len()) }
}
}

/// Get the length of this slice of bytes.
///
/// # Panics
///
/// Panics if the provided length is negative.
pub fn len(&self) -> usize {
self.len
.try_into()
.expect("bytes length negative or overflowed")
}
}

#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_foreignbytes_access() {
let v = vec![1u8, 2, 3];
let fbuf = ForeignBytes {
len: 3,
data: v.as_ptr(),
};
assert_eq!(fbuf.len(), 3);
assert_eq!(fbuf.as_slice(), &[1u8, 2, 3]);
}

#[test]
fn test_foreignbytes_empty() {
let v = Vec::<u8>::new();
let fbuf = ForeignBytes {
len: 0,
data: v.as_ptr(),
};
assert_eq!(fbuf.len(), 0);
assert_eq!(fbuf.as_slice(), &[0u8; 0]);
}

#[test]
fn test_foreignbytes_null_means_empty() {
let fbuf = ForeignBytes {
len: 0,
data: std::ptr::null_mut(),
};
assert_eq!(fbuf.as_slice(), &[0u8; 0]);
}

#[test]
#[should_panic]
fn test_foreignbytes_null_must_have_zero_length() {
let fbuf = ForeignBytes {
len: 12,
data: std::ptr::null_mut(),
};
fbuf.as_slice();
}

#[test]
#[should_panic]
fn test_foreignbytes_provided_len_must_be_non_negative() {
let v = vec![0u8, 1, 2];
let fbuf = ForeignBytes {
len: -1,
data: v.as_ptr(),
};
fbuf.as_slice();
}
}
9 changes: 9 additions & 0 deletions uniffi/src/ffi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pub mod foreignbytes;
pub mod rustbuffer;

pub use foreignbytes::*;
pub use rustbuffer::*;
Loading

0 comments on commit c35e904

Please sign in to comment.