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

core: reduce monomorphization of the futures code #2406

Merged
merged 1 commit into from
Mar 3, 2025
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
39 changes: 14 additions & 25 deletions uniffi_core/src/ffi/rustfuture/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html

use std::{
future::Future,
marker::PhantomData,
ops::Deref,
panic,
Expand All @@ -87,29 +88,27 @@ use std::{
use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler, UniffiCompatibleFuture};
use crate::{rust_call_with_out_status, FfiDefault, LiftArgsError, LowerReturn, RustCallStatus};

type BoxedFuture<T> = Pin<Box<dyn UniffiCompatibleFuture<Result<T, LiftArgsError>>>>;

/// Wraps the actual future we're polling
struct WrappedFuture<F, T, UT>
struct WrappedFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
// Note: this could be a single enum, but that would make it easy to mess up the future pinning
// guarantee. For example you might want to call `std::mem::take()` to try to get the result,
// but if the future happened to be stored that would move and break all internal references.
future: Option<F>,
future: Option<BoxedFuture<T>>,
result: Option<Result<T::ReturnType, RustCallStatus>>,
}

impl<F, T, UT> WrappedFuture<F, T, UT>
impl<T, UT> WrappedFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
fn new(future: F) -> Self {
fn new(future: BoxedFuture<T>) -> Self {
Self {
future: Some(future),
result: None,
Expand Down Expand Up @@ -182,40 +181,34 @@ where
//
// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising
// that we will treat the raw pointer properly, for example by not returning it twice.
unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT>
unsafe impl<T, UT> Send for WrappedFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
}

/// Future that the foreign code is awaiting
pub(super) struct RustFuture<F, T, UT>
pub(super) struct RustFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
// This Mutex should never block if our code is working correctly, since there should not be
// multiple threads calling [Self::poll] and/or [Self::complete] at the same time.
future: Mutex<WrappedFuture<F, T, UT>>,
future: Mutex<WrappedFuture<T, UT>>,
scheduler: Mutex<Scheduler>,
// UT is used as the generic parameter for [LowerReturn].
// Let's model this with PhantomData as a function that inputs a UT value.
_phantom: PhantomData<fn(UT) -> ()>,
}

impl<F, T, UT> RustFuture<F, T, UT>
impl<T, UT> RustFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
pub(super) fn new(future: F, _tag: UT) -> Arc<Self> {
pub(super) fn new(future: BoxedFuture<T>, _tag: UT) -> Arc<Self> {
Arc::new(Self {
future: Mutex::new(WrappedFuture::new(future)),
scheduler: Mutex::new(Scheduler::new()),
Expand Down Expand Up @@ -263,10 +256,8 @@ where
}
}

impl<F, T, UT> Wake for RustFuture<F, T, UT>
impl<T, UT> Wake for RustFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
Expand Down Expand Up @@ -298,10 +289,8 @@ pub trait RustFutureFfi<ReturnType>: Send + Sync {
fn ffi_free(self: Arc<Self>);
}

impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT>
impl<T, UT> RustFutureFfi<T::ReturnType> for RustFuture<T, UT>
where
// See rust_future_new for an explanation of these trait bounds
F: UniffiCompatibleFuture<Result<T, LiftArgsError>> + 'static,
T: LowerReturn<UT> + Send + 'static,
UT: Send + 'static,
{
Expand Down
4 changes: 2 additions & 2 deletions uniffi_core/src/ffi/rustfuture/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ where
// Needed to allocate a handle
dyn RustFutureFfi<T::ReturnType>: HandleAlloc<UT>,
{
let handle = <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle(
RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>,
let handle = HandleAlloc::new_handle(
RustFuture::new(Box::pin(future), tag) as Arc<dyn RustFutureFfi<T::ReturnType>>
);
trace!("rust_future_new: {handle:?}");
handle
Expand Down
5 changes: 3 additions & 2 deletions uniffi_core/src/ffi/rustfuture/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) {
result: None,
waker: None,
}));
let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag);
let rust_future = RustFuture::new(Box::pin(Receiver(channel.clone())), crate::UniFfiTag);
(Sender(channel), rust_future)
}

Expand Down Expand Up @@ -246,7 +246,8 @@ fn test_wake_during_poll() {
Poll::Ready(Ok("All done".to_owned()))
}
});
let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = RustFuture::new(future, crate::UniFfiTag);
let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> =
RustFuture::new(Box::pin(future), crate::UniFfiTag);
let continuation_result = poll(&rust_future);
// The continuation function should called immediately
assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
Expand Down