0

I have an AdoptablePet trait that allows you to asynchronously adopt a pet via fn do_adoption(id: &Self::Id) -> BoxFuture<'static, Result<Self, Self::Error>>;.

I have a Dog trait that is adoptable (pub trait Dog: AdoptablePet) and takes an associated AdoptingPerson and an adoption_policy before allowing you to actually adopt the pet. The adoption_policy is just a function that returns an array of boxed futures returning Results.

When I go to create a Pitbull, which implements both Dog and an AdoptablePet, everything works, but as soon as I try to make a default implementation of adoption_policy (as it will be the same for all Pitbulls) I can't get the references right between all the joining of the boxed futures.

When I try to join_all the adoption_policy Vec, it contains references to the boxed futures rather than the boxed futures themselves. When I try to map and dereference them, I get a borrow checker error (see [EXAMPLE B] in the code):

error[E0277]: the trait bound `&std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<(), AdoptionError>> + std::marker::Send>>: core::future::future::Future` is not satisfied
  --> src/lib.rs:70:13
   |
70 |             join_all(Self::adoption_policy(adopter, id).iter()).then(|policy_results| {
   |             ^^^^^^^^ the trait `core::future::future::Future` is not implemented for `&std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<(), AdoptionError>> + std::marker::Send>>`
   |
   = help: the following implementations were found:
             <std::pin::Pin<P> as core::future::future::Future>
   = note: required by `futures_util::future::join_all::join_all`

error[E0599]: no method named `then` found for type `futures_util::future::join_all::JoinAll<&std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<(), AdoptionError>> + std::marker::Send>>>` in the current scope
  --> src/lib.rs:70:65
   |
70 |             join_all(Self::adoption_policy(adopter, id).iter()).then(|policy_results| {
   |                                                                 ^^^^

error[E0277]: the trait bound `&std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<(), AdoptionError>> + std::marker::Send>>: core::future::future::Future` is not satisfied
  --> src/lib.rs:70:13
   |
70 |             join_all(Self::adoption_policy(adopter, id).iter()).then(|policy_results| {
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `core::future::future::Future` is not implemented for `&std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<(), AdoptionError>> + std::marker::Send>>`
   |
   = help: the following implementations were found:
             <std::pin::Pin<P> as core::future::future::Future>
   = note: required by `futures_util::future::join_all::JoinAll`

I'm a bit lost. If I don't join_all in adopt (and merely return Self::do_adoption(id) everything works (see [EXAMPLE A] in the code). What's going on?

The code (also available in a git repo):

#![feature(async_await)]
use futures::future::{self, join_all, BoxFuture};

#[derive(Debug)]
pub struct AdoptionError;

pub trait AdoptablePet
where
    Self: Sized,
{
    /// The id of the pet to adopt.
    type Id;

    /// Adopt the pet.
    fn do_adoption(id: &Self::Id) -> BoxFuture<'static, Result<Self, AdoptionError>>;
}

pub trait Dog: AdoptablePet
where
    // XXX: Are these all needed?
    Self: Sized + Send,
    <Self as AdoptablePet>::Id: Sync,
    Self: 'static,
    Self::AdoptingPerson: Sync,
{
    /// The Person adopting a dog.
    type AdoptingPerson;

    /// The policy to check when a person is adopting a particular dog.
    fn adoption_policy(
        adopter: &Self::AdoptingPerson,
        id: &Self::Id,
    ) -> Vec<BoxFuture<'static, Result<(), AdoptionError>>>;

    /// Policy-aware adoption.
    fn adopt(
        adopter: &Self::AdoptingPerson,
        id: &Self::Id,
    ) -> BoxFuture<'static, Result<Self, AdoptionError>> {
        // [EXAMPLE A]
        // Doing the following works...
        /*
        if true {
            Self::do_adoption(id)
        } else {
            Box::pin(future::ready(Err(AdoptionError{})))
        }
        */

        /* [EXAMPLE B]
           But this is what I want to do. This is the error:

            --> src/lib.rs:71:13
             |
          71 | /             join_all(
          72 | |
          73 | |
          74 | |                 --> src/lib.rs:65:13
          ...  |
          86 | |                 Self::adoption_policy(adopter, id).iter(),
          87 | |             )
             | |_____________^ the trait `core::future::future::Future` is not implemented for `&std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = std::result::Result<(), AdoptionError>> + std::marker::Send>>`
             |
             = help: the following implementations were found:
                       <std::pin::Pin<P> as core::future::future::Future>
             = note: required by `futures_util::future::join_all::JoinAll`
        */
        Box::pin(
            // Check all the adoption rules in the policy.
            join_all(Self::adoption_policy(adopter, id).iter()).then(|policy_results| {
                // Depending on the result, do the (async/long-running)
                // adoption or return an error.
                let has_policy_failure = policy_results.any(|x| x.is_err());
                if !has_policy_failure {
                    Self::do_adoption(id)
                } else {
                    Box::pin(future::ready(Err(AdoptionError {})))
                }
            }),
        )
    }
}

/// Implementation.

#[derive(Debug, Clone, PartialEq)]
pub struct DogId(pub String);

pub struct Pitbull {
    pub id: DogId,
}

impl AdoptablePet for Pitbull {
    type Id = DogId;

    fn do_adoption(id: &Self::Id) -> BoxFuture<'static, Result<Self, AdoptionError>> {
        Box::pin(future::ready(Ok(Pitbull { id: id.clone() })))
    }
}

impl Dog for Pitbull {
    type AdoptingPerson = Person;
    fn adoption_policy(
        _adopter: &Self::AdoptingPerson,
        _id: &Self::Id,
    ) -> Vec<BoxFuture<'static, Result<(), AdoptionError>>> {
        vec![
            // 1. Check if they have had their shots.
            // 2. Check if the adopter has children and if the breed is good with children.
            // etc.
        ]
    }
}

pub struct Person {
    name: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        futures::executor::block_on(async {
            let id = DogId("fluffy123".to_string());
            let adopter = Person {
                name: "Fred".to_string(),
            };
            let _ = Pitbull::adopt(&adopter, &id).await.unwrap();
        });
    }
}

I'm using futures-preview version 0.3.0-alpha.16.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Chris
  • 75
  • 5

1 Answers1

2

Here is the working version:

fn adopt(
    adopter: &Self::AdoptingPerson,
    id: &'static Self::Id,
) -> BoxFuture<'static, Result<Self, AdoptionError>> {
    Box::pin(
        join_all(Self::adoption_policy(adopter, id)).then(move |policy_results| {
            let has_policy_failure = policy_results.iter().any(|x| x.is_err());
            if !has_policy_failure {
                Self::do_adoption(id)
            } else {
                Box::pin(future::ready(Err(AdoptionError {})))
            }
        }),
    )
}

Changes:

  1. join_all needs to take ownership of the futures, not references to them: join_all(Self::adoption_policy(adopter, id)).
  2. futures::future::FutureExt must be imported to gain access to FutureExt::then.
  3. any is a method on Iterator, not Vec: policy_results.iter().any(/* ... */)
  4. id needs to be 'static due to your bounds: id: &'static Self::Id
  5. id needs to be moved into the closure to prevent a borrow: move |policy_results| { /* ... */ }.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366