I define a function which takes two String references as input. 'b : 'a requires that the lifetime of y is no smaller than x. When calling the function at line 6, I think there will be an error because s2 is in the lifetime of s1, however, the compiler doesn't issue an error. Why ?
Why do you require y to outlive (or live as long as ) x? You donât return anything that relates the two. I think you should just use âa for both and be done with it. What you are trying to convey, is how the lifetime of the return value relates to the lifetimes of the inputs. You can only return 1 lifetime. Whether you return x or y, the lifetime of the thing returned is the lifetime of x or y. Neither needs to live longer or shorter than the other, so just just lifetime aâ for both. That way, if you return x, your return value has lifetime 'a and if you return y, your return value has lifetime 'a, where 'a is the lifetime associated with x or y.
Lifetimes are not associated with values, they are associated with borrows. 'a and 'b in your example name the regions in which the borrows in the expressions &s1 and &s2 in your call to longest will be considered to still be borrowed.
Of course, that only just shifts the question from the values to the borrows. The following is still allowed, even though 'a clearly is larger than 'b:
let r1 = &s1; // 'a begins
let r2 = &s2; // 'b begins
longest(r1, r2); // still valid. Huh...
The deal here is that 'b : 'a doesnât mean so much that 'a is a subset of 'b, but rather that 'b outlives 'a. That is, it constrains what happens after the function. In practice, rust will choose 'a to be the smallest possible scope based on how the return value is used (since it appears in the return value), and the âoutlivesâ constraint means s2 will also be considered to be borrowed for that period.
let s1 = String::from("world");
let borrow = {
let r1 = &s1; // 'a begins
let s2 = String::from("hello");
let r2 = &s2; // 'b begins
longest(r1, r2)
}; // s2 is dropped
// 'a is assumed to still be active because we stored a
// borrow with that lifetime in `borrow`.
// 'b: 'a means 'b must also be active;
// but that is impossible since s2 was dropped.
// This is an error.
I know one lifetime annotation is enough for this function. But my question is not how to write the longest function. I just wonder why the compiler doesnât issue an error of the code above.
Iâm not 100% sure, but I think this is precisely because 'b outlives 'a. The function says that the returned value will live at least for lifetime 'a IIRC, so a binding that can handle a larger lifetime wouldnât be an issue.
The converse (i.e. 'a : 'b) would not be true, as borrowck would rightly complain that the returned borrow doesnât live long enough.
First you have the owned values s1, s1 any borrow lifetimes are strictly smaller.
The complier (I think) works backwards. It takes result and says where will be its end. At the end will borrowed item 'a be valid. The bound 'b:'a adds the constraint that the second borrow must be at least as long. I donât think the start points are relevant other than for determining the end of scope.
What you canât do during the life of result is mutate either s1 or s2 (assuming you added mut and then tried somehow after.)
let result = {
let r1 = &s1;
let r2 = &s2;
let r = longest(r1, r2);
println!("{} {} {}", r1, r2, r);
r
};
This is valid code. Instead of just been limited to the life of r1 and r2 a reborrow occurs making r then subsequently result be borrowed from s1 and s2 independently of the lifetime of r1 and r2.
Nit: This is not what is typically referred to as a âreborrowâ in the rust lexicon. A reborrow is when a shorter borrow is produced from a longer one (by doing &*self or &mut *self; this also implicitly done on method receivers and function arguments).
Essentially, because you have the 'b: 'a relationship, the compiler can use subtyping to figure out that the lifetime of 'a is less than or equal to (or maybe greater than, I forget) the 'b lifetime. This means you can return a &'a String because both 'a and 'b satisfy the 'a lifetime bound, one because 'a == 'a, and the other because 'b is a superset of the 'a lifetime.
I believe the actual implementation is also explained in the rustc guide (I think the Type Inference and Variance chapters may be useful), but @mark-i-m and @nikomatsakis use big words which I havenât fully wrapped my head around yet
A concrete example would be in a language like Java if I were to return an object or an instance of a child class, except weâre using lifetimes.
public static Animal foo(boolean condition) {
if (condition) {
return new Dog();
} else {
return new Animal();
}
}
(probably not even valid Java, but hopefully you get the gist)
The compiler tries to use the shortest concrete lifetimes which will satisfy the requirements laid out in the function signatureâeffectively, the intersection of concrete lifetimes. With shared borrows, itâs possible to use a subset of a longer lifetime if that subset would be sufficient ('static is an extreme case of a reference which can be used anywhere because of this.)
In this case, the concrete lifetime used for 'a doesnât have to be the full lifetime of s1; it can be shortened to that of s2. That configuration now satisfies 'b: 'a (⌠lives at least as long asâŚ). For the same reason, using 'a for all references in the signature would also work.
Lifetime shortening isnât done for mutable references, because doing so would let one sneak in a dangling reference. There are more intricacies wrt the precise details of the âoutlivesâ relationship in various situation, most of which are documented in the Nomicon.
This is subtle, but &'a mut T is variant over 'a but invariant over T. As such, you can squeeze down mutable refs as well purely on the lifetime of the borrow if the T allows for it. In the String example used in this thread, you can make longest() take &mut String and it would work just as well.
As others mentioned, the lifetime parameters are of the borrow, not of the lifetime (or scope) of the value (ie referent). When you declare a lifetime parameter, whether in an fn or a type (struct, enum, trait, etc), itâs a generic parameter - think of it like a T: PartialEq generic type parameter with a constraint. Unlike generic type parameters, however, the caller never specifies the concrete lifetime - itâs selected by the compiler. So whereas you can call a method expecting T: PartialEq with a type of your own choosing, so long as it satisfies the constraint, you never get to do this for lifetimes.
Instead, the lifetime constraints (eg 'b: 'a) are solved for by the compiler. So the compiler is presented a constraint problem, and it tries to solve it. The lifetime here is, to reiterate, a borrow lifetime - it is not the lifetime/scope of the value. The compiler picks the smallest borrow scope that satisfies the constraints. In this case, it can borrow a longer scoped value for a shorter borrow and still meet the constraints youâve set out. This is similar to how you can use a single lifetime parameter 'a there for both arguments. It doesnât mean that the values live for the same amount of time - itâs a relationship that youâre expressing (taking something with 'a lifetime, and returning something with at least 'a lifetime).
In fact, you cannot use lifetime relationships (ie outlives constraints) to require that one value lives longer than another - this is not supported in any way by the Rust type system.
The typical case of needing outlives constraints is when you have mutable borrows over invariant data - in that case, compiler does not allow shrinking lifetimes like in this case. Thatâs to prevent a longer lived type from being substituted for a shorter requirement, but then having a shorter lived reference given to it that leads to a dangling reference - the linked nomicon page goes into more detail on this so I wonât repeat it here. So when lifetimes canât be shrunk down but you still want to modify some invariant type, youâll need to provide the additional outlives constraints so the compiler will know that youâre not in danger of creating a dangling reference, and it will then check that when it substitutes concrete lifetimes for the generic ones.
Let me know if somethingâs unclear and Iâll try to elaborate. This is not an easy thing to explain (or understand) once you get into the nitty gritty. The whole thing is predicated on âcan memory safety be violated otherwiseâ - how you describe/relate lifetimes is then based on that.
I believe the correct thing to say here is, "and returning something with at most 'a lifetime" or "and returning something with a lifetime no longer than 'a"
Yes, that's true for sure. One thing that helped me to clarify is that when you are specifying lifetimes, you are doing so so that you can specify how the function's output value's lifetime relates to the lifetime(s) of the inputs. What you are trying to do is ensure that anything in the return value that references something in one of the input references/borrows does not attempt to outlive the thing that it references. That is all.
The thing I wanted to actually convey here is you can return something, from an implementation standpoint, with a potentially longer lifetime (when variance/subtyping allows for it) such as 'static. Itâs true that from signature (ie callerâs) standpoint, it doesnât outlive 'a.
Thatâs certainly a good way to think about it. Itâs not always about tying input to output lifetimes, however - you can have an fn that returns nothing but takes mutable and/or immutable references as arguments, and then you may need to express the lifetime relationship purely between the inputs.
Mutable refs is also when one may need to specify multiple lifetimes and/or outlives relationships between them. Immutable refs can usually get away with a single lifetime param for all of them.
Wait, so is it implied that Rustâs lifetime lengths correspond to alphabetical order?? I.e., the lifetime indicated by 'a is <= that indicated by 'b, which is in turn <= 'c, etc.?
If so, it would be nice to put this information in the Rust book, because it doesnât even hint at it (at least, this was the case a few months ago when I went through it). Maybe other people were somehow able to assume this rule correctly, but to me all that is indicated by the different letters is that the lifetimes are not required to be the same.