Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.9k views
in Technique[技术] by (71.8m points)

rust - How to return a future combinator with `&self`

I have this piece of code using futures v0.1:

impl ArcService for (Box<MiddleWare<Request>>, Box<ArcService>) {
    fn call(&self, req: Request, res: Response) -> Box<Future<Item = Response, Error = Error>> {
        box self.0.call(req).and_then(move |req| self.1.call(req, res))
    }
}

pub trait ArcService: Send + Sync {
    fn call(&self, req: Request, res: Response) -> Box<Future<Item = Response, Error = Error>>;
}

pub trait MiddleWare<T>: Sync + Send {
    fn call<'a>(&'a self, param: T) -> Box<Future<Item = T, Error = Error> + 'a>;
}

type MiddleWareFuture<'a, I> = Box<Future<Item = I, Error = Error> + 'a>;

impl MiddleWare<Request> for Vec<Box<MiddleWare<Request>>> {
    fn call(&self, request: Request) -> MiddleWareFuture<Request> {
        self.iter()
            .fold(box Ok(request).into_future(), |request, middleware| {
                box request.and_then(move |req| middleware.call(req))
            })
    }
}

pub struct ArcRouter {
    routes: HashMap<Method, Box<ArcService>>,
}

// Service implementation
impl hyper::Server::Service for ArcRouter {
    type Response = Response;
    type Request = Request;
    type Error = hyper::Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, req: Request) -> Box<Future<Item = Self::Response, Error = Self::Error>> {
        if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
            let mut request: ArcRequest = req.into();
            request.paramsMap.insert(routeMatch.params);
            let response = routeMatch.handler //handler is ArcService
                    .call(request, ArcResponse::new())
                    .map(|res| res.into());
            return box response;
        }

        // TODO: this should be handled by a user defined 404 handler
        return box Ok(Response::new().with_status(StatusCode::NotFound)).into_future();
    }
}

Note the lifetime parameter on Middleware — it is used to avoid lifetime issues.

This does not compile because Box<Future<Item = Response, Error = Error>> is implicitly 'static and therefore causes lifetime issues. hyper::Server::Service requires a 'static Future

Here is an example that aptly describes my problem:

extern crate futures; // v0.1 (old)

use futures::{future, Future};

struct Example {
    age: i32,
}

// trait is defined in an external crate. You can't change it's definition
trait MakeFuture {
    fn make_a_future(&self) -> Box<Future<Item = i32, Error = ()>>;
}

impl MakeFuture for Example {
    fn make_a_future(&self) -> Box<Future<Item = i32, Error = ()>> {
        let f = future::ok(self).map(|ex| ex.age + 1);
        Box::new(f)
    }
}

playground link

which gives the lifetime error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:16:28
   |
16 |         let f = future::ok(self).map(|ex| ex.age + 1);
   |                            ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 15:5...
  --> src/main.rs:15:5
   |
15 | /     fn make_a_future(&self) -> Box<Future<Item = i32, Error = ()>> {
16 | |         let f = future::ok(self).map(|ex| ex.age + 1);
17 | |         Box::new(f)
18 | |     }
   | |_____^
note: ...so that expression is assignable (expected &Example, found &Example)
  --> src/main.rs:16:28
   |
16 |         let f = future::ok(self).map(|ex| ex.age + 1);
   |                            ^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that expression is assignable (expected std::boxed::Box<futures::Future<Item=i32, Error=()> + 'static>, found std::boxed::Box<futures::Future<Item=i32, Error=()>>)
  --> src/main.rs:17:9
   |
17 |         Box::new(f)
   |         ^^^^^^^^^^^

Is there a way to get around this? I'm building with hyper::Service and using Rust v1.25.0 (nightly)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

How to return a future combinator with &self

You return a future that refers to self like this:

use futures::future::{self, FutureResult}; // 0.1.28

struct Example {
    age: i32,
}

impl Example {
    fn make_a_future(&self) -> FutureResult<&Example, ()> {
        future::ok(self)
    }
}

As discussed in the Tokio documentation on returning futures, the easiest stable solution to returning a complicated future is a impl Trait. Note that we assign an explicit lifetime to self and use that in the returned value (via + 'a):

use futures::{future, Future}; // 0.1.28

struct Example {
    age: i32,
}

impl Example {
    fn make_a_future<'a>(&'a self) -> impl Future<Item = i32, Error = ()> + 'a {
        future::ok(self).map(|ex| ex.age + 1)
    }
}

Your real question is "how can I lie to the compiler and attempt to introduce memory unsafety into my program?"

Box<SomeTrait + 'static> (or Box<SomeTrait> by itself) means that the trait object must not contain any references that do not last for the entire program. By definition, your Example struct has a shorter lifetime than that.

This has nothing to do with futures. This is a fundamental Rust concept.

There are many questions that ask the same thing in regards to threads, which have similar restrictions. A small sampling:

Like in those cases, you are attempting to share a reference to a variable with something that may exist after the variable is destroyed. Languages such as C or C++ would let you do this, only to have your program crash at a seemingly random point in time in the future when that variable is accessed after being freed. The crash is the good case, by the way; information leaks or code execution is also possible.

Like the case for threads, you have to ensure that this doesn't happen. The easiest way is to move the variable into the future, not sharing it at all. Another option is to use something like an Arc around your variable, clone the Arc and hand the clone to the future.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...