Complicated lifetime annotations

Hi rustaceans! I am recently studying rust lifetimes. I devised the following program:

// Program to test complicated lifetime annotations
fn test<'a:'d, 'b:'d+'e,'c:'e, 'd, 'e>(rx: &'a i32, ry:&'b i32, rz: &'c i32)->(&'d i32, &'e i32)  {
    (if rx<ry {rx} else {ry},
     if ry<rz {ry} else {rz})
}

fn main() {
    let z:i32 = 3;
    let y:i32 = 2;
    {
        let v: &i32;
        {
            let x:i32 = 1;
            {
                let u: &i32;
                (u, v) = test(&x, &y, &z);
                println!("u={u}");
            }
        }

        println!("v={v}");
    }
}

This program compiles and runs fine. The signature of function test is obtained from an experienced rust programmer. Another rust programmer told me to use: fn test(rx: &'a i32, ry:&'a i32, rz: &'a i32)->(&'a i32, &'a i32), which I can understand, but then my main function would not compile. I feel that the all 'a lifetime annotation is too restrictive.

My question is: Although this program compiles successfully, I do not know why. I do not understand the signature of function test. I searched all the books and online materials, but I failed to find any mention of the lifetime annotation syntax used in that signature. Can someone point me to any relevant materials? Thanks!

It's described briefly in the Subtyping and Variance and Trait and lifetime bounds chapters of the reference.

2 Likes

I was going to link you to the appropriate place in The Book... but I can't find it. @2e71828 found it in the reference, though. :slight_smile: The first explanation I could find was this answer on Stack Overflow.

In this specific case, I believe you can explain the annotations for the test function as follows:

  • 'a: 'd: there exists some lifetime 'a which is at least as long-lived as 'd.
  • 'b: 'd+'e: there exists some lifetime 'b which is at least as long-lived as 'd, and at least as long-lived as 'e.
  • 'c: 'e: there exists some lifetime 'c which is at least as long-lived as 'e.
  • 'd: there exists some lifetime 'd.
  • 'e: there exists some lifetime 'e.

This is accounting for the fact that the result lifetimes are a combination of ('a OR 'b, 'b OR 'c).

Another way to think about it: the first tuple position could be either rx or ry. The lifetime of this reference will be called 'd. As such, if you return rx then its lifetime 'a must be at least as long-lived as 'd. Same goes for ry and its lifetime 'b.

2 Likes

The most important things are:

  • 'long: 'short means that 'long must be at least as long as (a superset of) 'short
  • references are covariant in their lifetime, so &'long (mut) T can be converted to &'short (mut) T.
3 Likes

To express lifetime connections in another naive way: Rust Playground

fn test<'y, 'x, 'z, 'o1, 'o2>(rx: &'x i32, ry: &'y i32, rz: &'z i32) -> (&'o1 i32, &'o2 i32)
where
    'x: 'o1,
    'y: 'o1,
    'y: 'o2,
    'z: 'o2,
{
    (if rx < ry { rx } else { ry }, if ry < rz { ry } else { rz })
}

or make use of covariance: Rust Playground

fn test<'o1, 'o2, 'y: 'o1 + 'o2>(rx: &'o1 i32, ry: &'y i32, rz: &'o2 i32) -> (&'o1 i32, &'o2 i32)
{
    (if rx < ry { rx } else { ry }, if ry < rz { ry } else { rz })
}

Update: some suggestions

  • the return value can be simply written as (rx.min(ry), ry.min(rz))
  • normally you wouldn't pass &CopyType around, pass owned Copy types instead, which won't cause lifetime troubles
1 Like

Your syntax is superb! Very straightforward

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.