Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can derive be smarter about lifetime parameters? #27950

Closed
solson opened this issue Aug 23, 2015 · 9 comments
Closed

Can derive be smarter about lifetime parameters? #27950

solson opened this issue Aug 23, 2015 · 9 comments
Labels
A-syntaxext Area: Syntax extensions

Comments

@solson
Copy link
Member

solson commented Aug 23, 2015

briansmith on IRC ran into trouble using #[derive(PartialEq)] on a struct with a lifetime parameter. See the code at http://is.gd/R6Jx2x which fails with:

<anon>:8:59: 8:65 error: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
<anon>:8 fn doesnt_work_with_derived(x: Input, y: Input) -> bool { x == y }
                                                                   ^~~~~~
<anon>:8:1: 8:67 help: consider using an explicit lifetime parameter as shown: fn doesnt_work_with_derived<'a>(x: Input<'a>, y: Input<'a>) -> bool
<anon>:8 fn doesnt_work_with_derived(x: Input, y: Input) -> bool { x == y }
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Even though a similar function working directly with byte slices is fine with the lifetime parameters differing. Following the error message's suggestion works, but forces explicit lifetimes where one would expect elision to work (as it does with the byte slices).

The issue is shown in the expanded code from derive pasted below. It's because the Rhs type for the impl defaults to Self, which is Input<'a>, which strictly requires the exact same lifetime.

Below that, I wrote a modified version of the derived impl showing what we wanted to be generated. It introduces a new lifetime parameter on the impl for each lifetime parameter on the original struct and sets Rhs to be the same type as Self except with those lifetime parameters substituted. (In this case, just 'a replaced with 'b.) This actually works: http://is.gd/1GjWDD

So, can derive do this (seemingly) simple transformation for types with lifetime parameters? Can someone with more experience chip in and explain why this would go horribly wrong? :P

@eefriedman
Copy link
Contributor

Your suggested transformation could very easily go wrong: in some cases, the derived implementation wouldn't compile. Very simple example: you write Input2<'a>, which contains a field Input<'a>, and Input's PartialEq is implemented like impl <'a> PartialEq for Input<'a>.

@solson
Copy link
Member Author

solson commented Aug 23, 2015

@eefriedman Can you expand on your example? impl <'a> PartialEq for Input<'a> is what the current derive code generates.

@eefriedman
Copy link
Contributor

use std::cmp::PartialEq;

struct Input<'a> {
    bytes: &'a [u8],
}

impl <'a> PartialEq for Input<'a> {
    #[inline]
    fn eq(&self, _other: &Input<'a>) -> bool {
        panic!()
    }
}

struct Input2<'a> {
    i: Input<'a>
}

impl<'a, 'b> PartialEq<Input2<'b>> for Input2<'a> {
    #[inline]
    fn eq(&self, other: &Input2<'b>) -> bool {
        self.i == other.i // error here
    }
}

fn main() {}

@eddyb
Copy link
Member

eddyb commented Aug 23, 2015

@eefriedman You're missing the where clauses that the compiler is supposed to emit (but I guess that change hasn't happened yet).
Specifically, where Input<'a>: PartialEq<Input<'b>> which should automatically introduce 'a == 'b in this specific case.

@eefriedman
Copy link
Contributor

@eddyb The compiler currently rejects where clauses which don't involve a type parameter... is there an RFC somewhere to allow it?

@eefriedman
Copy link
Contributor

Created pull request #27972.

@steveklabnik steveklabnik added the A-codegen Area: Code generation label Mar 11, 2016
@solson
Copy link
Member Author

solson commented Sep 16, 2016

So, it's one year later. I took @eefriedman's example with the error, added @eddyb's where clause, and it compiles just fine: https://is.gd/m3phSn

Is it time we could change the derive code to work this way now?

@solson
Copy link
Member Author

solson commented Mar 10, 2017

I was just talking with ajeffrey on IRC about the most generalized version of derive(PartialEq) and I think it would work like this:

#[derive(PartialEq)]
struct Foo<'a, A, B> {
    a: &'a [A],
    b: Vec<B>,
    c: String,
}

// generated
impl<'a1, 'a2, A1, A2, B1, B2> PartialEq<Foo<'a2, A2, B2>> for Foo<'a1, A1, B1>
where
    &'a1 [A1]: PartialEq<&'a2 [A2]>, // for self.a == other.a
    Vec<B1>: PartialEq<Vec<B2>>,     // for self.b == other.b
    String: PartialEq<String>,       // for self.c == other.c. Obviously redundant, but not a real problem and the macro can't be type-directed.
{
    ...
}

That is, with a fully separately generalized right-hand side type and constraints based on the actual field types you will be comparing, not on the type parameters. Note that it can be the case that T: PartialEq<U> doesn't hold but Bar<T>: PartialEq<Bar<U>> does hold, depending on how Bar uses the type parameter. The current derive is overly restrictive in this respect as well.

This formulation should handle cases like in the OP and a lot more besides.

@Mark-Simulacrum Mark-Simulacrum added A-syntaxext Area: Syntax extensions and removed A-codegen Area: Code generation labels Jun 12, 2017
@Mark-Simulacrum
Copy link
Member

I'm going to close this in favor of #26925. That's the specific issue this is tracking (derive being not as intelligent as perhaps we'd want); I don't think separate issues are useful in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-syntaxext Area: Syntax extensions
Projects
None yet
Development

No branches or pull requests

5 participants