Is there benefit to having two lifetimes in this From<&'a str> implementation?

Due to my ignorance on lifetime subtyping and reborrows, I am finding myself writing non-idiomatic code "out of fear" of the code not being general enough. For example:

struct Foo<'a> {
    value: &'a str,
impl<'a: 'b, 'b> From<&'a str> for Foo<'b> {
    fn from(value: &'a str) -> Self {
        Self { value }

I think removing 'b will lead to equivalent code, but I am unsure. For example one often sees PartialEq implemented like below:

impl<'a, 'b> PartialEq<Foo<'a>> for Foo<'b> {
    fn eq(&self, other: &Foo<'a>) -> bool {
        self.value == other.value

or with lifetime elision:

impl PartialEq<Foo<'_>> for Foo<'_> {
    fn eq(&self, other: &Foo<'_>) -> bool {
        self.value == other.value

One, can someone show me code that will not compile if the PartialEq were implemented using the same lifetime parameter like below?

impl<'a> PartialEq<Self> for Foo<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value

Two, assuming one can be answered, what makes it important to use different lifetimes in the PartialEq implementation but not the From one?

I can come up with not entirely unreasonable code that would rely on such a From implementation:

Using a function like this

// supports Error types that can be created from &'static str
fn custom_fun_with_callback<E>(callback: impl FnOnce(i32) -> Result<(), E>) -> Result<(), E>
    E: From<&'static str>,

which is generic over error types (passing them along from a callback) but requires them to be creatable with a From impl, too.

Now if we do

fn use_case(foo: Foo<'_>) -> Result<(), Foo<'_>> {
    custom_fun_with_callback(|n| Err(foo))?;

this works fine with the generic implementation, but fails with one that equates the lifetimes 'a and 'b

error: lifetime may not live long enough
  --> src/
21 | fn use_case(foo: Foo<'_>) -> Result<(), Foo<'_>> {
   |             --- has type `Foo<'1>`
22 |     custom_fun_with_callback(|n| Err(foo))?;
   |                                  ^^^^^^^^ returning this value requires that `'1` must outlive `'static`

I think, it should generally never be a problem to have the less general implementations for the purpose of calling the relevant method (from or ==/!=) directly, but it can make a difference when they are used to meet explicit trait bounds, such as in the example above where the From<&'static str> bound was solved, not just <…>::from("foo") was called with a &'static str constant "foo". In the latter case, subtyping coercions could always still shorten the lifetime of the &str before the call to from.


For PartialEq, the same thing applies: PartialEq bounds might not be met. S: PartialEq<T> bounds are not too common, but occasionally come up in some other (generic) PartialEq implementations. Such heterogeneous (generic) implementations aren’t too common, but they do exist. Combined with a grain of invariance (so subtyping coercions cannot come save the day after all), I could for example find some implementations involving the most basic invariant types: &mut … types! Here, for example even the PartialEq implementation for &mut S == &mut T itself is generic/heterogeneous in such a way, or also the one between &S and &mut T (so both of these work for types S: PartialEq<T> so S and T can be different types)! With this implementation in mind, here would be code that can break with the less generic implementation:

fn execute(place: &mut Foo<'_>, error_value: &Foo<'_>) {
    if place == error_value {
        place.value = "something went wrong!";

Your comprehension of Rust is enviable. I aspire to have such fluency in the language. I cannot thank you enough. I spent some time trying to come up with counterexamples and failed each time. I tried relying on a couple trivial polymorphic functions with trait bounds, but they clearly were not clever enough. Again, thank you.


This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.