Skip to content

Commit

Permalink
Adding UDL async support
Browse files Browse the repository at this point in the history
  • Loading branch information
bendk committed Nov 6, 2023
1 parent 9db16c5 commit 6a25f90
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 18 deletions.
10 changes: 8 additions & 2 deletions docs/manual/src/futures.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ UniFFI supports exposing async Rust functions over the FFI. It can convert a Rus

Check out the [examples](https://github.com/mozilla/uniffi-rs/tree/main/examples/futures) or the more terse and thorough [fixtures](https://github.com/mozilla/uniffi-rs/tree/main/fixtures/futures).

Note that currently async functions are only supported by proc-macros, UDL support is being planned in https://github.com/mozilla/uniffi-rs/issues/1716.

## Example

This is a short "async sleep()" example:
Expand Down Expand Up @@ -34,6 +32,14 @@ if __name__ == '__main__':
asyncio.run(main())
```

Async functions can also be defined in UDL:
```idl
namespace example {
[Async]
string say_after(u64 ms, string who);
}
```

This code uses `asyncio` to drive the future to completion, while our exposed function is used with `await`.

In Rust `Future` terminology this means the foreign bindings supply the "executor" - think event-loop, or async runtime. In this example it's `asyncio`. There's no requirement for a Rust event loop.
Expand Down
13 changes: 13 additions & 0 deletions docs/manual/src/udl/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,16 @@ fun helloName(name: String = "world" ): String {
// ...
}
```

## Async

Async functions can be exposed using the `[Async]` attribute:

```idl
namespace Example {
[Async]
string async_hello();
}
```

See the [Async/Future support section](../futures.md) for details.
5 changes: 4 additions & 1 deletion fixtures/futures/src/futures.udl
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
namespace futures {};
namespace futures {
[Async]
boolean always_ready();
};
5 changes: 3 additions & 2 deletions fixtures/futures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ pub fn greet(who: String) -> String {
format!("Hello, {who}")
}

/// Async function that is immediatly ready.
#[uniffi::export]
/// Async function that is immediately ready.
///
/// (This one is defined in the UDL to test UDL support)
pub async fn always_ready() -> bool {
true
}
Expand Down
1 change: 0 additions & 1 deletion fixtures/futures/src/uniffi_futures.udl

This file was deleted.

4 changes: 2 additions & 2 deletions uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#[::uniffi::export_for_udl]
pub trait r#{{ obj.name() }} {
{%- for meth in obj.methods() %}
fn {{ meth.name() }}(
fn {% if meth.is_async() %}async {% endif %}{{ meth.name() }}(
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
Expand Down Expand Up @@ -68,7 +68,7 @@ impl {{ obj.rust_name() }} {
{%- for meth in obj.methods() %}
#[::uniffi::export_for_udl]
impl {{ obj.rust_name() }} {
pub fn r#{{ meth.name() }}(
pub {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}(
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[::uniffi::export_for_udl]
pub fn r#{{ func.name() }}(
pub {% if func.is_async() %}async {% endif %}fn r#{{ func.name() }}(
{%- for arg in func.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}
Expand Down
42 changes: 36 additions & 6 deletions uniffi_udl/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub(super) enum Attribute {
Custom,
// The interface described is implemented as a trait.
Trait,
Async,
}

impl Attribute {
Expand All @@ -67,6 +68,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute {
"Error" => Ok(Attribute::Error),
"Custom" => Ok(Attribute::Custom),
"Trait" => Ok(Attribute::Trait),
"Async" => Ok(Attribute::Async),
_ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0),
},
// Matches assignment-style attributes like ["Throws=Error"]
Expand Down Expand Up @@ -196,8 +198,9 @@ impl<T: TryInto<EnumAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for E

/// Represents UDL attributes that might appear on a function.
///
/// This supports the `[Throws=ErrorName]` attribute for functions that
/// can produce an error.
/// This supports:
/// * `[Throws=ErrorName]` attribute for functions that can produce an error.
/// * `[Async] for async functions
#[derive(Debug, Clone, Checksum, Default)]
pub(super) struct FunctionAttributes(Vec<Attribute>);

Expand All @@ -210,6 +213,10 @@ impl FunctionAttributes {
_ => None,
})
}

pub(super) fn is_async(&self) -> bool {
self.0.iter().any(|attr| matches!(attr, Attribute::Async))
}
}

impl FromIterator<Attribute> for FunctionAttributes {
Expand All @@ -224,7 +231,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttribut
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
Attribute::Throws(_) => Ok(()),
Attribute::Throws(_) | Attribute::Async => Ok(()),
_ => bail!(format!("{attr:?} not supported for functions")),
})?;
Ok(Self(attrs))
Expand Down Expand Up @@ -406,6 +413,10 @@ impl MethodAttributes {
})
}

pub(super) fn is_async(&self) -> bool {
self.0.iter().any(|attr| matches!(attr, Attribute::Async))
}

pub(super) fn get_self_by_arc(&self) -> bool {
self.0
.iter()
Expand All @@ -425,8 +436,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for MethodAttributes
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
Attribute::SelfType(_) => Ok(()),
Attribute::Throws(_) => Ok(()),
Attribute::SelfType(_) | Attribute::Throws(_) | Attribute::Async => Ok(()),
_ => bail!(format!("{attr:?} not supported for methods")),
})?;
Ok(Self(attrs))
Expand Down Expand Up @@ -641,14 +651,22 @@ mod test {
}

#[test]
fn test_throws_attribute() {
fn test_function_attributes() {
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap();
let attrs = FunctionAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.get_throws_err(), Some("Error")));
assert!(!attrs.is_async());

let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
let attrs = FunctionAttributes::try_from(&node).unwrap();
assert!(attrs.get_throws_err().is_none());
assert!(!attrs.is_async());

let (_, node) =
weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Async]").unwrap();
let attrs = FunctionAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.get_throws_err(), Some("Error")));
assert!(attrs.is_async());
}

#[test]
Expand All @@ -673,22 +691,34 @@ mod test {
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(!attrs.get_self_by_arc());
assert!(matches!(attrs.get_throws_err(), Some("Error")));
assert!(!attrs.is_async());

let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(!attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_none());
assert!(!attrs.is_async());

let (_, node) =
weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error]").unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_some());
assert!(!attrs.is_async());

let (_, node) =
weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error, Async]")
.unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_some());
assert!(attrs.is_async());

let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc]").unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_none());
assert!(!attrs.is_async());
}

#[test]
Expand Down
9 changes: 6 additions & 3 deletions uniffi_udl/src/converters/callables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_
Some(id) => id.0.to_string(),
};
let attrs = FunctionAttributes::try_from(self.attributes.as_ref())?;
let is_async = attrs.is_async();
let throws = match attrs.get_throws_err() {
None => None,
Some(name) => match ci.get_type(name) {
Expand All @@ -99,7 +100,7 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_
Ok(FnMetadata {
module_path: ci.module_path(),
name,
is_async: false,
is_async,
return_type,
inputs: self.args.body.list.convert(ci)?,
throws,
Expand Down Expand Up @@ -140,6 +141,7 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe
}
let return_type = ci.resolve_return_type_expression(&self.return_type)?;
let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
let is_async = attributes.is_async();

let throws = match attributes.get_throws_err() {
Some(name) => match ci.get_type(name) {
Expand All @@ -164,7 +166,7 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe
},
// We don't know the name of the containing `Object` at this point, fill it in later.
self_name: Default::default(),
is_async: false, // not supported in UDL
is_async,
inputs: self.args.body.list.convert(ci)?,
return_type,
throws,
Expand All @@ -184,6 +186,7 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface
}
let return_type = ci.resolve_return_type_expression(&self.return_type)?;
let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
let is_async = attributes.is_async();

let throws = match attributes.get_throws_err() {
Some(name) => match ci.get_type(name) {
Expand All @@ -208,7 +211,7 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface
name
}
},
is_async: false, // not supported in udl
is_async,
inputs: self.args.body.list.convert(ci)?,
return_type,
throws,
Expand Down

0 comments on commit 6a25f90

Please sign in to comment.