Simplification reference life-time

Note that structs with lifetimes are often a bad idea. Not just because they clutter your code with lifetime annotations, but because they severely limit how you can use these types .

My recommendation is to think really carefully before adding a lifetime to a struct. When in doubt, use an owned type ( String instead of &str , Box<dyn Foo> instead of &dyn Foo , etc.).

It decrease struct usability only due to current syntax that require everywhere to write named life-time ...

This two pieces of code more readable and maintainable:

struct CompositeObject {
    obj: &'self SomeType,
}

struct BigObject {
    composite_obj: CompositeObject,
    count: i32,
}

struct Application {
   big_obj: BigObject,
}

fn make_app(&SomeType, i32) -> Application<'_>;

or

struct CompositeObject {
    obj: &'self SomeType,
}

struct BigObject {
    composite_obj: CompositeObject,
    count: i32,
}

struct Application {
   big_obj: BigObject,
}

fn make_app(&SomeType, i32) -> Application;

But it currently is not supported ... :frowning:

This suggestion was posted as https://github.com/rust-lang/rfcs/pull/2949 (with no significant changes AFAICT).

When used with multiple lifetimes 'self loses any special meaning it could have had. If we were to go in that direction (I'm not saying we shouldn't), I'd prefer to have something more related to introducing unnamed free variables lifetime parameters:

struct CompositeObject {
    obj: &'0 SomeType, // automagically prepends a `<'0>` parameter
}

Now, the code from the previous example:

would become:

struct CompositeObject {
    counter: &'0 usize,
    obj: &'1 SomeType,
}
struct BigObject {
    composite_obj: CompositeObject<'0, '1>,
    count: i32,
}
struct Application {
   big_obj: BigObject<'0, '1>,
}

which at least does not break the symmetry between the two lifetime parameters.

Also highly relevant:

In the struct definition

struct App<'a> {
    resource: Handle<'a>,
}

'a is not the lifetime of App<'a>. It is also not the lifetime of Handle<'a>. To say it is betrays a fundamental misunderstanding of lifetimes.

'a is an upper bound on how long you can hold a Handle<'a>. 'a is the lifetime of some borrow which Handle<'a> depends on. Handle<'a> must be dropped before the end of 'a, but 'a may extend far after Handle<'a> may be dropped (and before the Handle<'a> is created).

"Lifetime of self" is not a meaningful concept.

6 Likes

Wouldn’t this depend on variance though? I believe the idea of an “upper bound” only applies when Handle is covariant or invariant over 'a.

IIUC, these only impact how the lifetime may change (e.g. if you can interpret &Handle<'a> as &Handle<'b> where 'a > 'b). Also, the only contravariance we have in Rust is the T in fn(T) -> _. Contravariance is such a tiny detail that it's much easier to ignore it until it's actually a problem. Personally, I always make it obvious by doing for<'f: 'a> fn(&'f _) rather than fn(&'a _). Also, if 'a is unbound, it should be for<'a> anyway.

In any case, when talking about the drop timing of a value and the lifetime it captures, variance doesn't even really come in to the picture, as it's only the one interpretation of the value we care about.

You can think about it like this: it's always legal when you own a value to break it into its fields (modulo privacy and drop impls). This ends the region that App<'c> exists, but does nothing to the borrow region that App<'c> is contained within.

The lifetime 'c is (maximally) a name for the region from when the borrow was created to when the borrow is no longer valid. App<'c> must exist within this region, but nothing says it must exist for the entire region.

Variance allows you to use arbitrarily shorter regions within the defining region, but either that new name is bound to an actual defined region, or it "floats", having multiple possible end timings, of which typically the most convenient is chosen (until further restricted). Also, most lifetimes are floating, as they float as soon as it's in a non-invariant holder and passed between storage spaces (assigned to a new variable or passed to a function).

I have a library, generativity, that actually creates an instance of a type that "unique"ly defines a new invariant lifetime that is defined as from its creation to its drop. It's complicated how it does so, and it requires a decent amount of macro trickery to make sure it's sound. (It's only "unique" among lifetimes generated by this technique; other "floating" lifetimes are free to shorten to equal it.)

And actually, even in generativity, which goes out of its way to guarantee that the lifetime is as close to the region the struct make_guard<'id> is live, extends beyond that region. Read the readme for more gritty details, but the 'id lifetime begins "shortly before" the make_guard and dies "shortly after", definitely not at the same time.

@CAD97 @steffahn @Ixrec @Aloso @H2CO3 @atagunov Reading all the comments on pull-request and in discussion, I have figured out that I was wrong in name 'self

I mostly mean some life-time that is implicitly added to structure declaration

Seems like I confused all of you with name 'self, sorry :wink:

The syntax also could be something like this:

struct CompositeObject<'a> {
    counter: &'a usize,
    obj: &'_ SomeType,
    obj2: &'_ SomeType,
}
struct BigObject<'a> {
    composite_obj: CompositeObject<'a>,
    count: i32,
}
struct Application<'a> {
   big_obj: BigObject<'a>,
}

and translation to:

struct CompositeObject<'a, 'anon0, 'anon1> {
    counter: &'a usize,
    obj0: &'anon0 SomeType,
    obj1: &'anon1 SomeType,
}
struct BigObject<'a, 'anon0, 'anon1> {
    composite_obj: CompositeObject<'a, 'anon0, 'anon1>,
    count: i32,
}
struct Application<'a, 'anon0, 'anon1> {
   big_obj: BigObject<'a, 'anon0, 'anon1>,
}

Maybe this syntax much better, because it does not confuse anyone ....

'self as concept could be implemented differently like this:

struct CompositeObject<'a> {
    counter: &'a usize,
    obj: &'self SomeType,
}
struct BigObject<'a> {
    composite_obj: CompositeObject<'a>,
    count: i32,
}
struct Application<'a> {
   big_obj: BigObject<'a>,
}

'self life-time is smaller or equal than life-time of CompositeObject object

If 'comp_obj is life-time of CompositeObject than 'comp_obj: 'self

1 Like

@CAD97 @steffahn @Ixrec @Aloso @H2CO3 @atagunov

I have changed the name and the wording in RFC https://github.com/rust-lang/rfcs/pull/2949

Last time I have updated proposal with syntax ClassObj<'_> for multiple anonymous life-times ...

Should I reopen proposal ? What is the status of changes in internal organization of processes ?

Also as I mentioned about named references ... It would be nice to have named references optional ... I mean the following:

struct Candle<'a> {
    // Here is no references, but exist optional life-time
   timestamp: usize,
}

What would you use it for? There are a few use cases, which can currently be addressed with PhanthomData, but I'm intrigued which case you've hit that you want to address.

I will use it for developing ...

I mean during development I cannot know the best solution and I can use later references inside of structure or will remove it after some iterations of development

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