Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding UDL async support #1834

Merged
merged 1 commit into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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