Need help with closure lifetimes


#1

Trying to store a function reference, then re-use that function reference later on. enough talk, code :point_down:



pub trait ArcService: Send + Sync {
	fn call (&self, req: Request, res: Response) -> FutureResponse;
}

pub type FutureResponse = Box<Future<Item = Response, Error = Error>>;

impl<B, H, A> ArcService for (B, H, A)
where
		B: MiddleWare + Sync + Send,
		H: ArcService + Sync + Send,
		A: 'static + Fn(Response) -> Response + Sync + Send,
{
	fn call(&self, req: Request, res: Response) -> FutureResponse {
		let request = match self.0.call(req) {
			result::Ok(request) => request,
			result::response(res) => {
				return box Ok(res).into_future()
			}
			result::error(e) => {
				return box Ok(e.into()).into_future()
			}
		};
		let response = (self.1).call(request, res);
		return box response.map(|response| (self.2)(response))
	}
}

apparently the closure in the return statement is illegal, but the error message doesn’t make much sense to me. can anyone please help out?


arc-reactor on  redesign [!] via 𝗥 v1.25.0 
➜ cargo build
   Compiling async-server v0.1.0 (file:///home/seun/Projects/arc-reactor)
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/ArcProto/service.rs:29:10
   |
29 |         return box response.map(|response| (self.2)(response))
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 18:2...
  --> src/ArcProto/service.rs:18:2
   |
18 |       fn call(&self, req: Request, res: Response) -> FutureResponse {
   |  _____^
19 | |         let request = match self.0.call(req) {
20 | |             result::Ok(request) => request,
21 | |             result::response(res) => {
...  |
29 | |         return box response.map(|response| (self.2)(response))
30 | |     }
   | |_____^
note: ...so that the type `futures::Map<std::boxed::Box<futures::Future<Error=hyper::Error, Item=ArcCore::response::Response>>, [closure@src/ArcProto/service.rs:29:27: 29:56 self:&&(B, H, A)]>` will meet its required lifetime bounds
  --> src/ArcProto/service.rs:29:10
   |
29 |         return box response.map(|response| (self.2)(response))
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that types are compatible (expected std::boxed::Box<futures::Future<Error=hyper::Error, Item=ArcCore::response::Response> + 'static>, found std::boxed::Box<futures::Future<Error=hyper::Error, Item=ArcCore::response::Response>>)
  --> src/ArcProto/service.rs:18:64
   |
18 |       fn call(&self, req: Request, res: Response) -> FutureResponse {
   |  ___________________________________________________________________^
19 | |         let request = match self.0.call(req) {
20 | |             result::Ok(request) => request,
21 | |             result::response(res) => {
...  |
29 | |         return box response.map(|response| (self.2)(response))
30 | |     }
   | |_____^

error: aborting due to previous error

error: Could not compile `async-server`.

To learn more, run the command again with --verbose.

arc-reactor on  redesign [!] via 𝗥 v1.25.0 
➜ 

#2
pub type FutureResponse = Box<Future<Item = Response, Error = Error>>;

This expands to pub type FutureResponse = Box<Future<Item = Response, Error = Error> + 'static>; (note the 'static lifetime) because that’s the default trait object bound.

You can try doing:

pub type FutureResponse<'a> = Box<Future<Item = Response, Error = Error> + 'a>;

That will associate the same lifetime on the response trait object as &self. Whether this will work or not will depend on how the rest of the code is setup.


#3

it breaks the rest of my codebase. :joy:


#4

sites that call ArcService::call() suddenly have lifetime issues too, saying the return type must be valid for a static lifetime

arc-reactor on  redesign [$!?] via 𝗥 v1.25.0 
➜ cargo build
   Compiling impl-service v0.1.0 (file:///home/seun/Projects/arc-reactor/impl-service)
   Compiling async-server v0.1.0 (file:///home/seun/Projects/arc-reactor)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/ArcRouting/router.rs:23:34
   |
23 |         if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
   |                                        ^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 22:2...
  --> src/ArcRouting/router.rs:22:2
   |
22 |       fn call(&self, req: Request) -> Self::Future {
   |  _____^
23 | |         if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
24 | |             let mut request: ArcRequest = req.into();
25 | |             request.paramsMap.insert(routeMatch.params);
...  |
45 | |         ).into_future()
46 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/ArcRouting/router.rs:23:29
   |
23 |         if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
   |                                   ^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that types are compatible (expected std::boxed::Box<futures::Future<Error=hyper::Error, Item=hyper::Response> + 'static>, found std::boxed::Box<futures::Future<Error=hyper::Error, Item=hyper::Response>>)
  --> src/ArcRouting/router.rs:22:47
   |
22 |       fn call(&self, req: Request) -> Self::Future {
   |  __________________________________________________^
23 | |         if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
24 | |             let mut request: ArcRequest = req.into();
25 | |             request.paramsMap.insert(routeMatch.params);
...  |
45 | |         ).into_future()
46 | |     }
   | |_____^

error: aborting due to previous error

error: Could not compile `async-server`.

To learn more, run the command again with --verbose.

arc-reactor on  redesign [$!?] via 𝗥 v1.25.0 
➜ 

router.rs


impl 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) -> Self::Future {
		if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
			let mut request: ArcRequest = req.into();
			request.paramsMap.insert(routeMatch.params);

			let modifiedRequest = match self.middleware {
				Some(ref middleware) => match middleware.call(request) {
					ArcResult::Ok(req) => req,
					ArcResult::response(response) => return box Ok(response.into()).into_future(),
					// TODO: well, obviously its a hack, still haven't figured errors out.
					ArcResult::error(_) => return box Err(hyper::Error::Timeout).into_future()
				},
				None => request
			};

			let responseFuture =  routeMatch.handler.call(modifiedRequest, ArcResponse::new());

			return box responseFuture.map(|res| res.into());
		}

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

#5
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

Yeah, same issue here except there’s no lifetime param to associate with the Box. At this point, I’d say it’ll be easier if you put your state that you want to use across futures into an Rc and move clones of it around the futures. I think hyper will ultimately require a static lifetime anyway for the service.


#6

trying this out.


#7

I ended up using Arc and it worked beautifully. But i’ve run into similar problems, i understand the error. Not sure how to fix it.


fn call(&self, req: Request) -> Self::Future {
		if let Some(routeMatch) = self.matchRoute(req.path(), req.method()) {
			let mut request: ArcRequest = req.into();
			request.paramsMap.insert(routeMatch.params);

			let modifiedRequest = match self.middleware {
				Some(ref middleware) => {
					return box middleware.call(request)
						.and_then(|req| routeMatch.handler.call(req, ArcResponse::new()))
						.map(|res| res.into())
				},
				None => request
			};

			let responseFuture =  routeMatch.handler.call(modifiedRequest, ArcResponse::new());
			return box responseFuture.map(|res| res.into());
		}

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

I get that, the closure might outlive the self reference because futures have a 'static lifetime, a solution would be to somehow tell the compiler that self will live as long as the boxed future i’m returning (dunno if this is possible?)


#8

The way you express that is fn call<'a>(&'a self, ...) -> Box<Future<...> + 'a>. However, given that you’ll need a 'static future eventually, this won’t work because your self won’t be static.

The typical way to allow yourself to call methods from future chains expressed as combinators is to make Self Clone and then move a clone of self into the closure. To make Self cloneable, stick its guts into an Arc/Rc or you can wrap just the pieces you want to access from futures into Arc/Rc.