The compiler tells me to add an explicit lifetime bound, but I can’t figure out how I’m supposed to that.
error[E0309]: the parameter type `E` may not live long enough
--> src/main.rs:39:9
|
34 | impl<S: Into<juniper::Value>, E: Into<juniper::FieldError>> Registrable for FieldInfo<S,E>
| -- help: consider adding an explicit lifetime bound...: `E: 'a +`
...
39 | Box::pin(to_graphql((self.resolver)(executor)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds
--> src/main.rs:39:9
|
39 | Box::pin(to_graphql((self.resolver)(executor)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
server_1
error[E0309]: the parameter type `S` may not live long enough
--> src/main.rs:39:9
|
34 | impl<S: Into<juniper::Value>, E: Into<juniper::FieldError>> Registrable for FieldInfo<S,E>
| -- help: consider adding an explicit lifetime bound...: `S: 'a +`
...
39 | Box::pin(to_graphql((self.resolver)(executor)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds
--> src/main.rs:39:9
|
39 | Box::pin(to_graphql((self.resolver)(executor)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the minimal code I came up with reproducing the error. It was working when ReturnType1
was not generic over V
and E
but I’m now trying to make it work with multiple types:
use juniper;
#[derive(Clone)]
struct Context {}
impl juniper::Context for Context {}
type ReturnType1<'a, V: Into<juniper::Value>, E: Into<juniper::FieldError>> = juniper::BoxFuture<'a, Result<V,E>>;
type InnerReturnType = juniper::ExecutionResult<juniper::DefaultScalarValue>;
type ReturnType<'a> = juniper::BoxFuture<'a, InnerReturnType>;
type Resolver<V: Into<juniper::Value>, E: Into<juniper::FieldError>> = for<'a> fn(
&'a juniper::Executor<Context, juniper::DefaultScalarValue>
) -> ReturnType1<'a, V, E>;
async fn to_graphql<'a, V: Into<juniper::Value>, E: Into<juniper::FieldError>>(f: ReturnType1<'a, V, E>) -> InnerReturnType {
f.await
.map(|scalar| scalar.into())
.map_err(|err| err.into())
}
trait Registrable: Send + Sync
{
fn resolve<'a>(self: &Self, executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>) -> ReturnType<'a>;
}
struct FieldInfo<S, E>
where S: Into<juniper::Value>,
E: Into<juniper::FieldError>
{
resolver: Resolver<S,E>
}
impl<S: Into<juniper::Value>, E: Into<juniper::FieldError>> Registrable for FieldInfo<S,E>
where S: juniper::GraphQLType<TypeInfo=()> + Send + Sync
{
fn resolve<'a>(self: &Self, executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>) -> ReturnType<'a>
{
Box::pin(to_graphql((self.resolver)(executor)))
}
}
fn main() {}
Cargo.toml:
[package]
name = "pgql"
version = "0.1.0"
authors = ["Mathieu Rochette <[email protected]>"]
edition = "2018"
[dependencies]
juniper = { git = "https://github.com/graphql-rust/juniper", branch = "master" }
What’s bothering me is that the generic type is declared on the impl
block but the issue seems to be on the fn
within, so adding a lifetime at the impl level seems the wrong way to go.
If I try to add a where S: 'a, E: 'a
clause on the fn resolve()
:
impl<S: Into<juniper::Value>, E: Into<juniper::FieldError>> Registrable for FieldInfo<S,E>
where S: juniper::GraphQLType<TypeInfo=()> + Send + Sync
{
fn resolve<'a>(self: &Self, executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>) -> ReturnType<'a>
where S: 'a, E: 'a {
Box::pin(to_graphql((self.resolver)(executor)))
}
}
it says the method does not match the trait declaration:
error[E0195]: lifetime parameters or bounds on method `resolve` do not match the trait declaration
--> src/main.rs:37:15
|
24 | fn resolve<'a>(self: &Self, executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>) -> ReturnType<'a>;
| ---- lifetimes in impl do not match this method in trait
...
37 | fn resolve<'a>(self: &Self, executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>) -> ReturnType<'a>
| ^^^^ lifetimes do not match method in trait
but I can’t add it on the trait since it does not know about S
& E
...
you can see the changes I’m trying to make in this PR : https://github.com/mathroc/pgql-rs/pull/1/files
So, the await
in to_graphql
might resolve any time in the lifetime of f
—and when that happens the ensuing into()
calls obviously require data in the respective types to be valid. Ergo, to_graphql
's type parameters V
and E
must outlive its lifetime parameter 'a
. The borrow checker enforces this implicitly, as documented under async
Lifetimes:
Unlike traditional functions,
async fn
s which take references or other non-'static
arguments return aFuture
which is bounded by the lifetime of the arguments:
You want to specify that your implementation block's generic type parameters S
and E
have such a lifetime, but as you have noted you cannot add such a constraint when it does not appear in the trait definition.
One possibility is to add a lifetime parameter to the Registrable
trait itself, and on the resolve
function specify that this new trait lifetime parameter is a subtype of (i.e. outlives) the function's lifetime parameter 'a
:
trait Registrable<'b>: Send + Sync
{
fn resolve<'a>(
&self,
executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>,
) -> ReturnType<'a>
where 'b: 'a;
}
Then on your impl
you can relate that lifetime parameter to your generic type parameters S
and E
:
impl<'b, S, E> Registrable<'b> for FieldInfo<S, E>
where
S: juniper::GraphQLType<TypeInfo=()>,
S: Send,
S: Sync,
S: Into<juniper::Value>,
E: Into<juniper::FieldError>,
S: 'b,
E: 'b,
{
fn resolve<'a>(
&self,
executor: &'a juniper::Executor<Context, juniper::DefaultScalarValue>,
) -> ReturnType<'a>
where 'b: 'a
{
Box::pin(to_graphql((self.resolver)(executor)))
}
}
This is an interesting one. because we are implementing a trait, we can't add extra lifetime information to the function definition.
The issue at hand is that S
and E
are generic types, and therefore may contain references and hence be constrained by the lifetimes of those references. However they are generic and the compiler knows nothing about any constraints it may have.
At the same time, we are returning another generic type that depends on something with lifetime 'a
and S
and E
. 'a
is entirely unrelated to S
and E
, so the compiler looks and says: I can't tell if they live long enough because they are unrelated.
Now I haven't found the perfect way to describe this fully, however, we can make this work in a limited way with an easy code change - we can limit S
and E
to have static lifetimes, (which means they contain no references or only static references). This is usually sufficient in practice.
The code change in question is on FieldInfo
:
struct FieldInfo<S, E>
where
S: Into<juniper::Value> + 'static,
E: Into<juniper::FieldError> + 'static,
{
resolver: Resolver<S, E>,
}
I feel that there should be a way of expressing the lifetime relationships better, but I have yet to find it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With