Using different lifetimes in struct implementations

I am trying to get a better understanding of Rust lifetimes.

struct Name<'a> {
    arg: &'a u8,
}
impl<'a> Name<'a> {
    fn new1(arg: &'a u8) -> Name<'a> {
        Name { arg }
    }
    fn new2<'b>(arg: &'b u8) -> Name<'b> {
        Name { arg }
    }
}

Is there any difference between functions new1 and new2? I am assuming it would matter if arg was &self? Is there any case where one if preferred over the other?

The latter is like doing impl Foo { fn new() -> Bar { Bar } } -- not wrong, but weird because it looks like a constructor but isn't returning Self.

Try changing them to -> Self and you'll see lots of errors from new2.

2 Likes

Yes thats true. A simple 'b: 'a would fix the issue when the return is Self. Even though I am not understanding what it means. How does the lifetime defined on the struct affect the method implementations?

What would be the difference between

fn new1(arg: &'a self)

and

 fn new2<'b>(arg: &'b self)

how different would these functions behave? When should one opt one over the other?

impl<'a> Name<'a> {
    fn new1(arg: &'a self) { ... }
    fn new1<'b>(arg: &'b self) { ... }
}

The first one is almost always wrong. Consider these to be more like the following:

fn func1<'a>(arg: &'a Name<'a>)
fn func2<'a, 'b>(arg: &'b Name<'a>)

The first one constrains the lifetime of the borrow to match the lifetime of the struct. Now, due to covariance, this isn't a big deal; when this function is called, the lifetime of the struct can easily be shortened to match the lifetime of the borrow.

But let's consider the same scenario with &mut:

fn func1_mut<'a>(arg: &'a mut Name<'a>)
fn func2_mut<'a, 'b>(arg: &'b mut Name<'a>)

&mut T is invariant in T, so the compiler can't shrink this lifetime when you call this function. As a result, any attempt to call new1_mut will force the Name to be mutably borrowed for the entire rest of the time that it exists. (making it impossible to borrow the Name again)

fn main() {
    let mut name = Name { arg: b"hello" };
    func1_mut(&mut name);
    func2(&name); // ERROR: already mutably borrowed.
}

By the way, that's incorrect usage because you're saying arg: &'a self while what you meant was arg: &'a Self. Self is this type (As in the type you're implementing a trait on or implementing methods on) while self is sugar for saying "this is called with an owned argument of type Self and is named self and is therefore a method"

For example:

fn foo(&self) {}
//Becomes
fn foo(self: &Self) {}

//And
fn foo(self) {}
//becomes
fn foo(self: Self) {}

It can still cause problems as the unnecessary shortening can cause otherwise valid code to be rejected.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.