Skip to content

Commit

Permalink
New custom type system
Browse files Browse the repository at this point in the history
Implemented the custom type system descibed in the new docs.  This is
working for single crates, but multi-crate setup is currently broken.
It should start working again once we change how UDL generation handles
UniFfiTag.

Removed the nested-module-import fixture.  The custom type code will no
longer test it, since it's not using `UniffiCustomTypeConverter`.  I
couldn't think of a way to make it work again.
  • Loading branch information
bendk committed May 4, 2024
1 parent 480f7ab commit f9e1a98
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 330 deletions.
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ members = [
"fixtures/regressions/swift-callbacks-omit-labels",
"fixtures/regressions/swift-dictionary-nesting",
"fixtures/regressions/unary-result-alias",
"fixtures/regressions/nested-module-import",
"fixtures/regressions/wrong-lower-check",
"fixtures/trait-methods",
"fixtures/uitests",
Expand Down
110 changes: 42 additions & 68 deletions examples/custom-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,72 @@
use url::Url;

// A custom guid defined via a proc-macro (ie, not referenced in the UDL)
// By far the easiest way to define custom types.
pub struct ExampleCustomType(String);

// custom_newtype! is the easiest way to define custom types.
uniffi::custom_newtype!(ExampleCustomType, String);

// Custom Handle type which trivially wraps an i64.
pub struct Handle(pub i64);

// Custom TimeIntervalMs type which trivially wraps an i64.
pub struct TimeIntervalMs(pub i64);

// Custom TimeIntervalSecDbl type which trivially wraps an f64.
pub struct TimeIntervalSecDbl(pub f64);
// This one could also use custom_newtype!, but let's use Into and TryFrom instead
uniffi::custom_type!(Handle, i64);

// Custom TimeIntervalSecFlt type which trivially wraps an f32.
pub struct TimeIntervalSecFlt(pub f32);
// Defining `From<Handle> for i64` also gives us `Into<i64> for Handle`
impl From<Handle> for i64 {
fn from(val: Handle) -> Self {
val.0
}
}

// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side
impl UniffiCustomTypeConverter for Handle {
// The `Builtin` type will be used to marshall values across the FFI
type Builtin = i64;
impl TryFrom<i64> for Handle {
type Error = std::convert::Infallible;

// Convert Builtin to our custom type
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
fn try_from(val: i64) -> Result<Handle, Self::Error> {
Ok(Handle(val))
}

// Convert our custom type to Builtin
fn from_custom(obj: Self) -> Self::Builtin {
obj.0
}
}

// Use `url::Url` as a custom type, with `String` as the Builtin
impl UniffiCustomTypeConverter for Url {
type Builtin = String;
// Custom TimeIntervalMs type which trivially wraps an i64.
pub struct TimeIntervalMs(pub i64);

fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
Ok(Url::parse(&val)?)
}
// Another custom type, this time we will define an infallible conversion back to Rust.
uniffi::custom_type!(TimeIntervalMs, i64);

fn from_custom(obj: Self) -> Self::Builtin {
obj.into()
impl From<TimeIntervalMs> for i64 {
fn from(val: TimeIntervalMs) -> Self {
val.0
}
}

// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side
impl UniffiCustomTypeConverter for TimeIntervalMs {
// The `Builtin` type will be used to marshall values across the FFI
type Builtin = i64;

// Convert Builtin to our custom type
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
Ok(TimeIntervalMs(val))
}

// Convert our custom type to Builtin
fn from_custom(obj: Self) -> Self::Builtin {
obj.0
// Defining `From<i64> for Handle` also gives us `Into<Handle> for i64`
impl From<i64> for TimeIntervalMs {
fn from(val: i64) -> TimeIntervalMs {
TimeIntervalMs(val)
}
}

// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side
impl UniffiCustomTypeConverter for TimeIntervalSecDbl {
// The `Builtin` type will be used to marshall values across the FFI
type Builtin = f64;
// Custom TimeIntervalSecDbl type which trivially wraps an f64.
pub struct TimeIntervalSecDbl(pub f64);

// Convert Builtin to our custom type
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
Ok(TimeIntervalSecDbl(val))
}
// custom_type! can take an additional parameter with closures to control the conversions
uniffi::custom_type!(TimeIntervalSecDbl, f64, {
from_custom: |time_interval| time_interval.0,
try_into_custom: |val| Ok(TimeIntervalSecDbl(val)),
});

// Convert our custom type to Builtin
fn from_custom(obj: Self) -> Self::Builtin {
obj.0
}
}
// Custom TimeIntervalSecFlt type which trivially wraps an f32.
pub struct TimeIntervalSecFlt(pub f32);

// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side
impl UniffiCustomTypeConverter for TimeIntervalSecFlt {
// The `Builtin` type will be used to marshall values across the FFI
type Builtin = f32;
// Let's go back to custom_newtype for this one.
uniffi::custom_newtype!(TimeIntervalSecFlt, f32);

// Convert Builtin to our custom type
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
Ok(TimeIntervalSecFlt(val))
}

// Convert our custom type to Builtin
fn from_custom(obj: Self) -> Self::Builtin {
obj.0
}
}
// `Url` gets converted to a `String` to pass across the FFI.
// Use the `remote` param when types are defined in a different crate
uniffi::custom_type!(Url, String, {
try_into_custom: |val| Ok(Url::parse(&val)?),
from_custom: |obj| obj.into(),
remote,
});

// And a little struct and function that ties them together.
pub struct CustomTypesDemo {
Expand Down
39 changes: 11 additions & 28 deletions fixtures/ext-types/custom-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn get_guid(guid: Option<Guid>) -> Guid {
Some(guid) => {
assert!(
!guid.0.is_empty(),
"our UniffiCustomTypeConverter already checked!"
"our custom type converter already checked!"
);
guid
}
Expand All @@ -38,13 +38,13 @@ pub fn get_guid(guid: Option<Guid>) -> Guid {

fn try_get_guid(guid: Option<Guid>) -> std::result::Result<Guid, GuidError> {
// This function itself always returns Ok - but it's declared as a Result
// because the UniffiCustomTypeConverter might return the Err as part of
// because the custom type converter might return the Err as part of
// turning the string into the Guid.
Ok(match guid {
Some(guid) => {
assert!(
!guid.0.is_empty(),
"our UniffiCustomTypeConverter failed to check for an empty GUID"
"our custom type converter failed to check for an empty GUID"
);
guid
}
Expand Down Expand Up @@ -85,12 +85,10 @@ pub fn run_callback(callback: Box<dyn GuidCallback>) -> Guid {
callback.run(Guid("callback-test-payload".into()))
}

impl UniffiCustomTypeConverter for Guid {
type Builtin = String;

// This is a "fixture" rather than an "example", so we are free to do things that don't really
// make sense for real apps.
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
uniffi::custom_type!(Guid, String, {
try_into_custom: |val| {
// This is a "fixture" rather than an "example", so we are free to do things that don't really
// make sense for real apps.
if val.is_empty() {
Err(GuidError::TooShort.into())
} else if val == "unexpected" {
Expand All @@ -100,28 +98,13 @@ impl UniffiCustomTypeConverter for Guid {
} else {
Ok(Guid(val))
}
}

fn from_custom(obj: Self) -> Self::Builtin {
obj.0
}
}
},
from_custom: |obj| obj.0,
});

pub struct ANestedGuid(pub Guid);

impl UniffiCustomTypeConverter for ANestedGuid {
type Builtin = Guid;

// This is a "fixture" rather than an "example", so we are free to do things that don't really
// make sense for real apps.
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
Ok(ANestedGuid(val))
}

fn from_custom(obj: Self) -> Self::Builtin {
obj.0
}
}
uniffi::custom_newtype!(ANestedGuid, Guid);

#[uniffi::export]
fn get_nested_guid(nguid: Option<ANestedGuid>) -> ANestedGuid {
Expand Down
34 changes: 15 additions & 19 deletions fixtures/ext-types/http-headermap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,28 @@ pub struct HttpHeader {
pub(crate) val: String,
}

/// Expose `http::HeaderMap` to Uniffi.
impl crate::UniffiCustomTypeConverter for http::HeaderMap {
/// http::HeaderMap is a multimap so there may be multiple values
/// per key. We represent this as a vector of `HttpHeader` (AKA
/// `key` & `val`) where `key` may repeat.
type Builtin = Vec<HttpHeader>;

fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
// Expose `http::HeaderMap` to Uniffi.
uniffi::custom_type!(HeaderMap, Vec<HttpHeader>, {
from_custom: |obj| {
obj.iter()
.map(|(k, v)| HttpHeader {
key: k.as_str().to_string(),
val: v.to_str().unwrap().to_string(),
})
.collect()
},
try_into_custom: |val| {
Ok(http::HeaderMap::from_iter(val.into_iter().filter_map(
|h| {
let n = http::HeaderName::from_str(&h.key).ok()?;
let v = http::HeaderValue::from_str(&h.val).ok()?;
Some((n, v))
},
)))
}

fn from_custom(obj: Self) -> Self::Builtin {
obj.iter()
.map(|(k, v)| HttpHeader {
key: k.as_str().to_string(),
val: v.to_str().unwrap().to_string(),
})
.collect()
}
}
},
// Must specify remote since http::HeaderMap is defined in a different crate.
remote,
});

pub fn get_headermap(v: String) -> HeaderMap {
let n = http::HeaderName::from_str("test-header").unwrap();
Expand Down
21 changes: 5 additions & 16 deletions fixtures/ext-types/proc-macro-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,22 +168,11 @@ pub struct Uuid {
val: String,
}

// Tell UniFfi we want to use am UniffiCustomTypeConverter to go to and
// from a String.
// Note this could be done even if the above `struct` defn was external.
uniffi::custom_type!(Uuid, String);

impl UniffiCustomTypeConverter for Uuid {
type Builtin = String;

fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
Ok(Uuid { val })
}

fn from_custom(obj: Self) -> Self::Builtin {
obj.val
}
}
// Define a custom type exactly like we would for UDL
uniffi::custom_type!(Uuid, String, {
from_custom: |uuid| uuid.val,
try_into_custom: |s| Ok(Uuid { val: s}),
});

mod submodule {
// A custom type using the "newtype" idiom.
Expand Down
17 changes: 0 additions & 17 deletions fixtures/regressions/nested-module-import/Cargo.toml

This file was deleted.

7 changes: 0 additions & 7 deletions fixtures/regressions/nested-module-import/README.md

This file was deleted.

7 changes: 0 additions & 7 deletions fixtures/regressions/nested-module-import/build.rs

This file was deleted.

31 changes: 0 additions & 31 deletions fixtures/regressions/nested-module-import/src/lib.rs

This file was deleted.

10 changes: 0 additions & 10 deletions fixtures/regressions/nested-module-import/src/test.udl

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,3 @@
{%- endmatch %}
{% endif %}
{%- endfor %}

// We generate support for each Custom Type and the builtin type it uses.
{%- for (name, builtin) in ci.iter_custom_types() %}
::uniffi::custom_type!(r#{{ name }}, {{builtin|type_rs}});
{%- endfor -%}
Loading

0 comments on commit f9e1a98

Please sign in to comment.