Skip to content

Commit 6a25f90

Browse files
committed
Adding UDL async support
1 parent 9db16c5 commit 6a25f90

File tree

9 files changed

+73
-18
lines changed

9 files changed

+73
-18
lines changed

docs/manual/src/futures.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ UniFFI supports exposing async Rust functions over the FFI. It can convert a Rus
44

55
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).
66

7-
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.
8-
97
## Example
108

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

35+
Async functions can also be defined in UDL:
36+
```idl
37+
namespace example {
38+
[Async]
39+
string say_after(u64 ms, string who);
40+
}
41+
```
42+
3743
This code uses `asyncio` to drive the future to completion, while our exposed function is used with `await`.
3844

3945
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.

docs/manual/src/udl/functions.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,16 @@ fun helloName(name: String = "world" ): String {
4747
// ...
4848
}
4949
```
50+
51+
## Async
52+
53+
Async functions can be exposed using the `[Async]` attribute:
54+
55+
```idl
56+
namespace Example {
57+
[Async]
58+
string async_hello();
59+
}
60+
```
61+
62+
See the [Async/Future support section](../futures.md) for details.

fixtures/futures/src/futures.udl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
namespace futures {};
1+
namespace futures {
2+
[Async]
3+
boolean always_ready();
4+
};

fixtures/futures/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ pub fn greet(who: String) -> String {
6767
format!("Hello, {who}")
6868
}
6969

70-
/// Async function that is immediatly ready.
71-
#[uniffi::export]
70+
/// Async function that is immediately ready.
71+
///
72+
/// (This one is defined in the UDL to test UDL support)
7273
pub async fn always_ready() -> bool {
7374
true
7475
}

fixtures/futures/src/uniffi_futures.udl

Lines changed: 0 additions & 1 deletion
This file was deleted.

uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#[::uniffi::export_for_udl]
1616
pub trait r#{{ obj.name() }} {
1717
{%- for meth in obj.methods() %}
18-
fn {{ meth.name() }}(
18+
fn {% if meth.is_async() %}async {% endif %}{{ meth.name() }}(
1919
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
2020
{%- for arg in meth.arguments() %}
2121
{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
@@ -68,7 +68,7 @@ impl {{ obj.rust_name() }} {
6868
{%- for meth in obj.methods() %}
6969
#[::uniffi::export_for_udl]
7070
impl {{ obj.rust_name() }} {
71-
pub fn r#{{ meth.name() }}(
71+
pub {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}(
7272
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
7373
{%- for arg in meth.arguments() %}
7474
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},

uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#[::uniffi::export_for_udl]
2-
pub fn r#{{ func.name() }}(
2+
pub {% if func.is_async() %}async {% endif %}fn r#{{ func.name() }}(
33
{%- for arg in func.arguments() %}
44
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
55
{%- endfor %}

uniffi_udl/src/attributes.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub(super) enum Attribute {
4141
Custom,
4242
// The interface described is implemented as a trait.
4343
Trait,
44+
Async,
4445
}
4546

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

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

@@ -210,6 +213,10 @@ impl FunctionAttributes {
210213
_ => None,
211214
})
212215
}
216+
217+
pub(super) fn is_async(&self) -> bool {
218+
self.0.iter().any(|attr| matches!(attr, Attribute::Async))
219+
}
213220
}
214221

215222
impl FromIterator<Attribute> for FunctionAttributes {
@@ -224,7 +231,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttribut
224231
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
225232
) -> Result<Self, Self::Error> {
226233
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
227-
Attribute::Throws(_) => Ok(()),
234+
Attribute::Throws(_) | Attribute::Async => Ok(()),
228235
_ => bail!(format!("{attr:?} not supported for functions")),
229236
})?;
230237
Ok(Self(attrs))
@@ -406,6 +413,10 @@ impl MethodAttributes {
406413
})
407414
}
408415

416+
pub(super) fn is_async(&self) -> bool {
417+
self.0.iter().any(|attr| matches!(attr, Attribute::Async))
418+
}
419+
409420
pub(super) fn get_self_by_arc(&self) -> bool {
410421
self.0
411422
.iter()
@@ -425,8 +436,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for MethodAttributes
425436
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
426437
) -> Result<Self, Self::Error> {
427438
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
428-
Attribute::SelfType(_) => Ok(()),
429-
Attribute::Throws(_) => Ok(()),
439+
Attribute::SelfType(_) | Attribute::Throws(_) | Attribute::Async => Ok(()),
430440
_ => bail!(format!("{attr:?} not supported for methods")),
431441
})?;
432442
Ok(Self(attrs))
@@ -641,14 +651,22 @@ mod test {
641651
}
642652

643653
#[test]
644-
fn test_throws_attribute() {
654+
fn test_function_attributes() {
645655
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap();
646656
let attrs = FunctionAttributes::try_from(&node).unwrap();
647657
assert!(matches!(attrs.get_throws_err(), Some("Error")));
658+
assert!(!attrs.is_async());
648659

649660
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
650661
let attrs = FunctionAttributes::try_from(&node).unwrap();
651662
assert!(attrs.get_throws_err().is_none());
663+
assert!(!attrs.is_async());
664+
665+
let (_, node) =
666+
weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Async]").unwrap();
667+
let attrs = FunctionAttributes::try_from(&node).unwrap();
668+
assert!(matches!(attrs.get_throws_err(), Some("Error")));
669+
assert!(attrs.is_async());
652670
}
653671

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

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

682702
let (_, node) =
683703
weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error]").unwrap();
684704
let attrs = MethodAttributes::try_from(&node).unwrap();
685705
assert!(attrs.get_self_by_arc());
686706
assert!(attrs.get_throws_err().is_some());
707+
assert!(!attrs.is_async());
708+
709+
let (_, node) =
710+
weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error, Async]")
711+
.unwrap();
712+
let attrs = MethodAttributes::try_from(&node).unwrap();
713+
assert!(attrs.get_self_by_arc());
714+
assert!(attrs.get_throws_err().is_some());
715+
assert!(attrs.is_async());
687716

688717
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc]").unwrap();
689718
let attrs = MethodAttributes::try_from(&node).unwrap();
690719
assert!(attrs.get_self_by_arc());
691720
assert!(attrs.get_throws_err().is_none());
721+
assert!(!attrs.is_async());
692722
}
693723

694724
#[test]

uniffi_udl/src/converters/callables.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_
8989
Some(id) => id.0.to_string(),
9090
};
9191
let attrs = FunctionAttributes::try_from(self.attributes.as_ref())?;
92+
let is_async = attrs.is_async();
9293
let throws = match attrs.get_throws_err() {
9394
None => None,
9495
Some(name) => match ci.get_type(name) {
@@ -99,7 +100,7 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_
99100
Ok(FnMetadata {
100101
module_path: ci.module_path(),
101102
name,
102-
is_async: false,
103+
is_async,
103104
return_type,
104105
inputs: self.args.body.list.convert(ci)?,
105106
throws,
@@ -140,6 +141,7 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe
140141
}
141142
let return_type = ci.resolve_return_type_expression(&self.return_type)?;
142143
let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
144+
let is_async = attributes.is_async();
143145

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

188191
let throws = match attributes.get_throws_err() {
189192
Some(name) => match ci.get_type(name) {
@@ -208,7 +211,7 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface
208211
name
209212
}
210213
},
211-
is_async: false, // not supported in udl
214+
is_async,
212215
inputs: self.args.body.list.convert(ci)?,
213216
return_type,
214217
throws,

0 commit comments

Comments
 (0)