Trait + impl + lifetime = nightmare

//rust 1.30.0 


struct X<'x> {
  x: &'x str
}

trait TX {
  fn new(x: X) -> Self;
}

struct S<'x> {
  x: X<'x>
}

impl<'x> TX for S<'x> {
  fn new(x: X) -> S<'x> {
    S {
      x
    }
  }
}

fn main() {
  println!("Hello, Dcoder!")
}

Could someone help me understand what I'm doing wrong with this code. I believe the problem is the elided lifetime, I have no idea how to "explain" to the compiler that this lifetime '_ is the same as the' x.

I would also like to understand this error message
, which seems to me to be encrypted. In particular, the "expected X<'> found X<'>".


error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'x` due to conflicting requirements
  --> source_file.rs:18:5
   |
18 |     S {
   |     ^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 17:3...
  --> source_file.rs:17:3
   |
17 | /   fn new(x: X) -> S<'x> {
18 | |     S {
19 | |       x
20 | |     }
21 | |   }
   | |___^
   = note: ...so that the expression is assignable:
           expected X<'_>
              found X<'_>
note: but, the lifetime must be valid for the lifetime 'x as defined on the impl at 16:6...
  --> source_file.rs:16:6
   |
16 | impl<'x> TX for S<'x> {
   |      ^^
   = note: ...so that the expression is assignable:
           expected S<'x>
              found S<'_>

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.


The direct answer is that you forgot an <'x> on the X in your new function, but the underlying reality is probably that you should be using String instead of &str and avoid the lifetimes entirely.

Edit: I guess you can't actually put a lifetime on that X. That would require the trait to be generic too.

3 Likes

TX::new has some hidden lifetimes, let's see what they are

trait TX {
  fn new<'x>(x: X<'x>) -> Self;
}

Interesting, let's take a look at the hidden lifetimes in impl TX for S<'_>

impl<'s> TX for S<'s> {
  fn new<'x>(x: X<'x>) -> S<'s> {
    S {
      x
    }
  }
}

As you can see the lifetimes don't actually match. You can fix this like @alice suggested by using String, or by lifting the lifetime to the trait, like so:

trait TX<'x> {
  fn new(x: X<'x>) -> Self;
}

impl<'x> TX<'x> for S<'x> {
  fn new(x: X<'x>) -> S<'x> {
    S {
      x
    }
  }
}

If you are just starting Rust or are only a few months in, I would suggest you avoid lifetimes inside your own types. It gets complex very quickly, stick to owned types like String and Vec<_> instead of &str or &[_].

10 Likes

Are you some kind of god ??? It worked and I understood the problem clearly

3 Likes

Yeah, that's not a great diagnostic. I wonder whether this is worth a compiler bug?

3 Likes

Yeah, that diagnostic should be better

Not out loud! I'm undercover :laughing:.

5 Likes

I don't think I will be able to avoid this, this is just an example code but the actual code uses a Chars<'chars> so I think there is no way to avoid using lifetime in this case.

Also:

That's a pretty old version of the compiler (latest stable is 1.49.0) -- did you install Rust using a distro package manager? Those packaged versions tend to be rather out of date, I'd suggest using rustup instead. Compiler errors and other things are improving with every new release.

2 Likes

I'm at work, so I'm using Dcoder on my Android to practice. On my computer I use the current stable version.

1 Like

Two tips:

  • put #![warn(2018_idioms)] at the root of the crate to get rust warnings about missing "explicit lifetime placeholders" (e.g., when you use X instead of X<'_> in a function signature);

    • once you see the lifetime holes / placeholders, you have to be able to apply the rules of lifetime elision by yourself. The TL,DR of those rules are:

      1. there is an independent / distinct lifetime parameter for each "hole" / placeholder,

      2. except those in the return type of a function, which connect to / use the same parameter as the "main lifetime parameter in argument position". This "main lifetime in argument position" (if any), is:

        • either the only lifetime parameter / hole among all the function parameters' types;

        • or, when dealing with &self (sugar for self: &'_ Self) or &mut self (sugar for self: &'_ mut Self) methods, the lifetime of that borrow of Self.

      Bonus tip: you can write let () = function_name; inside the body of a function to trigger an error message where Rust will print a function signature with named lifetime parameters.

  • Know that nested borrows (e.g. & & Something, or & SomethingElse<'_>) ought to feature distinct lifetimes. That is, if you see &'a Something<'a> or &'x X<'x>, then that is an antipattern that quickly causes many compilation errors. Instead, you should use distinct lifetime parameter names, which is easily achieved by using placeholders, as per the lifetime elision rules mentioned above: &'_ X<'x>.

4 Likes

It's not great for writing code on mobile but you can paste stuff into play.rust-lang.org to see the output from an up-to-date toolchain.

1 Like

If you have not yet found a solution, then post the actual code with Chars, and I'll see what I can do.

1 Like
4 Likes

Here is the code that originated the post:
Parser code on GitLab

Here's where is Chars<'chars>:
Lexer code on GitLab

Those links don't work for me, gitlab says they don't exist. Do you have the repository set to private?

1 Like

Ops, now the repository is public

Two comments:

  1. You typically do not put constructors in traits.
  2. Do you really need the trait at all? Can you just make it ordinary struct methods?
2 Likes

I come from C / C ++ and it's been kind of hard to let go of those habits. I can't say if the trait is necessary but I wanted a contract that ensures that possible additions are consistent with the API.

I would not use a trait if there is only one implementer. That just makes your life harder.

1 Like

The constructor in the trait was to ensure that the object is only constructed with valid input, as in std::fs::File