Sharpiro
Sharpiro

Reputation: 2424

Why does Rust "From" trait implementation give lifetime error, but custom trait does not?

The following code can work when I use a custom trait. But I don't understand why it fails when trying to use the builtin "From" trait.

struct A {
    x: isize,
}

enum SimpleEnum {
    A(A),
    B(u8),
}

trait FromCustom {
    fn from_custom(value: &SimpleEnum) -> &A;
}

impl FromCustom for &SimpleEnum {
    fn from_custom(value: &SimpleEnum) -> &A {
        if let SimpleEnum::A(value) = value {
            value // NO error
        } else {
            panic!();
        }
    }
}

impl From<&SimpleEnum> for &A {
    fn from(value: &SimpleEnum) -> Self {
        if let SimpleEnum::A(value) = value {
            value // error
        } else {
            panic!();
        }
    }
}

Error:

error: lifetime may not live long enough
  --> rust_pst_bin/src/main.rs:55:13
   |
53 |     fn from(value: &SimpleEnum) -> Self {
   |                    -               ---- return type is &'2 A
   |                    |
   |                    let's call the lifetime of this reference `'1`
54 |         if let SimpleEnum::A(value) = value {
55 |             value // error
   |             ^^^^^ associated function was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`


Upvotes: 1

Views: 142

Answers (2)

EvilTak
EvilTak

Reputation: 7579

Since you do not provide any lifetimes to your From<&SimpleEnum> impl, the compiler assigns to &SimpleEnum (and hence the parameter value) a different lifetime than the lifetime of Self (or &A). Desugared, the impl would be as follows:

impl<'a, 'b> From<&'a SimpleEnum> for &'b A {
    fn from(value: &'a SimpleEnum) -> Self {
        if let SimpleEnum::A(value) = value {
            value
        } else {
            panic!();
        }
    }
}

This would not work as Self has a different lifetime 'b compared to value's lifetime 'a. You have to explicitly tell the compiler that the two lifetimes are the same:

impl<'a> From<&'a SimpleEnum> for &'a A {
    fn from(value: &'a SimpleEnum) -> Self {
        if let SimpleEnum::A(value) = value {
            value
        } else {
            panic!();
        }
    }
}

Playground

Upvotes: 3

kmdreko
kmdreko

Reputation: 60522

The difference has to do with inferred lifetimes.

Your custom trait has references in both the parameter and return type; due to lifetime elision rules, your trait is equivalent to the following:

trait FromCustom {
    fn from_custom<'a>(value: &'a SimpleEnum) -> &'a A;
}

So by the signature alone, the return type is bound to the lifetime of the parameter.

In contrast, the From trait is written as such:

pub trait From<T>: Sized {
    fn from(value: T) -> Self;
}

There is no implicit linking of lifetimes between T and Self since there are no obvious lifetimes in use. And thus when you write your implementation, the &SimpleEnum and &A have distinct lifetimes, as if written like so:

impl<'a, 'b> From<&'b SimpleEnum> for &'a A {
    fn from(value: &'b SimpleEnum) -> Self {
        if let SimpleEnum::A(value) = value {
            value // error
        } else {
            panic!();
        }
    }
}

What is inferred is that a &SimpleEnum of any lifetime can be used to create a &A with any lifetime, which is untrue. You instead need to communicate that a &SimpleEnum with a particular lifetime can be used to create a &A with that lifetime. You can do that by linking the lifetimes together yourself:

impl<'a> From<&'a SimpleEnum> for &'a A {
    fn from(value: &'a SimpleEnum) -> Self {
        if let SimpleEnum::A(value) = value {
            value
        } else {
            panic!();
        }
    }
}

Upvotes: 2

Related Questions