I am learning rust now. Today I facing a lifetime issue. I want to implement an api response in rust rocket, I want the http response to use my custom entity called ApiResponse
. I will build it to replace the default rocket Response. this is my code:
use rocket::serde::Deserialize;
use rocket::serde::Serialize;
use rocket::response::Responder;
use rocket::{Request, Response};
use rocket::http::{Status, ContentType};
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
#[allow(non_snake_case)]
pub struct ApiResponse<T> {
pub body: T,
pub statusCode: String,
pub resultCode: String
}
impl<'r,T> Responder<'r,'r> for ApiResponse<T> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Response::build_from(self.respond_to(req).unwrap())
.header(ContentType::JSON)
.ok()
}
}
impl<T> Default for ApiResponse<T> where T : Default{
fn default() -> Self {
ApiResponse{
body: T::default(),
statusCode: "200".to_string(),
resultCode: "200".to_string()
}
}
}
when I compile this code, shows error like this:
$ cargo build ‹ruby-2.7.2›
Compiling reddwarf_music v0.1.0 (/Users/dolphin/Documents/GitHub/reddwarf_music)
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'r` due to conflicting requirements
--> src/biz/music/../../model/response/api_response.rs:17:35
|
17 | Response::build_from(self.respond_to(req).unwrap())
| ^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 16:31...
--> src/biz/music/../../model/response/api_response.rs:16:31
|
16 | fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
| ^^^^^^^
note: ...so that the type `rocket::Request<'_>` is not borrowed for too long
--> src/biz/music/../../model/response/api_response.rs:17:46
|
17 | Response::build_from(self.respond_to(req).unwrap())
| ^^^
note: but, the lifetime must be valid for the lifetime `'r` as defined on the impl at 15:6...
--> src/biz/music/../../model/response/api_response.rs:15:6
|
15 | impl<'r,T> Responder<'r,'r> for ApiResponse<T> {
| ^^
note: ...so that the expression is assignable
--> src/biz/music/../../model/response/api_response.rs:17:9
|
17 | / Response::build_from(self.respond_to(req).unwrap())
18 | | .header(ContentType::JSON)
19 | | .ok()
| |_________________^
= note: expected `Result<rocket::Response<'r>, _>`
found `Result<rocket::Response<'_>, _>`
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'r` due to conflicting requirements
--> src/biz/user/../../model/response/api_response.rs:17:35
|
17 | Response::build_from(self.respond_to(req).unwrap())
| ^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 16:31...
--> src/biz/user/../../model/response/api_response.rs:16:31
|
16 | fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
| ^^^^^^^
note: ...so that the type `rocket::Request<'_>` is not borrowed for too long
--> src/biz/user/../../model/response/api_response.rs:17:46
|
17 | Response::build_from(self.respond_to(req).unwrap())
| ^^^
note: but, the lifetime must be valid for the lifetime `'r` as defined on the impl at 15:6...
--> src/biz/user/../../model/response/api_response.rs:15:6
|
15 | impl<'r,T> Responder<'r,'r> for ApiResponse<T> {
| ^^
note: ...so that the expression is assignable
--> src/biz/user/../../model/response/api_response.rs:17:9
|
17 | / Response::build_from(self.respond_to(req).unwrap())
18 | | .header(ContentType::JSON)
19 | | .ok()
| |_________________^
= note: expected `Result<rocket::Response<'r>, _>`
found `Result<rocket::Response<'_>, _>`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0495`.
error: could not compile `reddwarf_music`
(base)
I read the lifetime manual and tried to understand this issue. Sorry it seems too complex that I could not figure out where is my code going wrong. the compiler tell the detail message but I still have no clue to fix this problem. So why would this happen? what should I do to fix this issue? what should I learn from this issue? I have tried to build the default response:
let response = Response::new();
Response::build_from(response).header(ContentType::JSON).ok()
it works. But when I build from my own ApiResponse
:
Response::build_from(self.respond_to(req).unwrap())
.header(ContentType::JSON)
.ok()
not works.
you have more documentation at https://api.rocket.rs/master/rocket/response/trait.Responder.html
This is trait signature
pub trait Responder<'r, 'o: 'r> {
fn respond_to(self, request: &'r Request<'_>) -> Result<'o>;
}
And documentation say
// If the response contains no borrowed data.
impl<'r> Responder<'r, 'static> for A {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
todo!()
}
}
// If the response borrows from the request.
impl<'r> Responder<'r, 'r> for B<'r> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
todo!()
}
}
// If the response is or wraps a borrow that may outlive the request.
impl<'r, 'o: 'r> Responder<'r, 'o> for &'o C {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
todo!()
}
}
// If the response wraps an existing responder.
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for D<R> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
todo!()
}
}
In your case it could be
impl<'r, T> Responder<'r,'static> for ApiResponse<T> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
todo!()
}
}
Now, in general the implementation of Responder doesn't make sense in your case. This implementation is for making requests and ApiResponse obviously aims to store responses.
See the String implementation
impl<'r> Responder<'r, 'static> for String {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build()
.header(ContentType::Plain)
.sized_body(self.len(), Cursor::new(self))
.ok()
}
}
or doc proposition
use std::io::Cursor;
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::ContentType;
struct Person {
name: String,
age: u16
}
impl<'r> Responder<'r, 'static> for Person {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let string = format!("{}:{}", self.name, self.age);
//--------------------------------^----------^-------
Response::build_from(string.respond_to(req)?)
.raw_header("X-Person-Name", self.name)
.raw_header("X-Person-Age", self.age.to_string())
.header(ContentType::new("application", "x-person"))
.ok()
}
}