The following code
struct Cat<'a, T> {
coolness: &'a T,
}
complains saying
error[E0309]: the parameter type `T` may not live long enough
--> src/main.rs:2:5
|
1 | struct Cat<'a, T> {
| - help: consider adding an explicit lifetime bound `T: 'a`...
2 | coolness: &'a T,
| ^^^^^^^^^^^^^^^
|
note: ...so that the reference type `&'a T` does not outlive the data it points at
--> src/main.rs:2:5
|
2 | coolness: &'a T,
| ^^^^^^^^^^^^^^^
With an explicit lifetime bound, it compiles. When I instantiate the struct where T is an &i32 and despite each reference having a different lifetime, the code compiles. My understanding is that the compiler sees that the inner & outlives the outer &:
struct Cat<'a, T>
where
T: 'a,
{
coolness: &'a T,
}
fn main() {
let b = 10;
{
let c = &b;
{
let fluffy = Cat { coolness: &c };
}
}
}
Does Cat { coolness: &c } expand to Cat { coolness: &'a &'a i32 }? Does the inner reference also assume the same lifetime and so forth for more nested references?
Does Cat { coolness: &c } expand to Cat { coolness: &'a &'a i32 }?
Yes, the Cat ends up with a reference to a reference. This can be demonstrated by the following code compiling:
let fluffy = Cat { coolness: &c };
fn is_it_a_double_ref(_x: &Cat<&i32>) {}
is_it_a_double_ref(&fluffy);
However, the lifetime on each reference is not necessarily the same.
My understanding is that the compiler sees that the inner & outlives the outer &
That's right. And this is precisely where the T: 'a bound comes into play.
Lifetime bounds are a bit tricky to understand at first. They put restrictions on the references contained in T. For example, given the bound T: 'static, types that don't contain any references, or only contain 'static references, e.g. i32 and &'static str, satisfy the bound, while types that contain non-'static references, e.g. &'a i32, don't, because 'a: 'static is false. More generally, given the bound T: 'a, the type T satisfies the bound if, for every lifetime parameter 'x on T, 'x: 'a is true (types with no lifetime parameters trivially satisfy the bound).
Back to your code now. Let's give some names to these references. We'll say the type of coolness is &'fluffy &'c i32. 'c is the lifetime of the variable c and 'fluffy is the lifetime of the variable fluffy (counterintuitively, lifetimes encode the scope of a borrow, not the lifetime of the referent, although the compiler does check that the borrow doesn't extend beyond the referent's lifetime). That means the type of Fluffy is Cat<'fluffy, &'c i32>. Is it true that &'c i32: 'fluffy?
To check if &'c i32: 'fluffy is true, we need to check if 'c: 'fluffy is true. 'c: 'fluffy is true because the variable c goes out of scope after fluffy.
Related
The following is the code:
struct A;
fn f<'a, I>(default_args: I)
where
I: IntoIterator<Item = &'a A>,
{
{
let a1 = A;
let more_args = [&a1];
{
let i = default_args.into_iter();
let i = i.chain(more_args);
// I will use `i` here.
}
}
// I will NOT use `*_args` here.
}
The following is error:
error[E0597]: `a1` does not live long enough
--> src\main.rs:27:26
|
21 | fn f<'a, I>(default_args: I)
| -- lifetime `'a` defined here
...
27 | let more_args = [&a1];
| ^^^ borrowed value does not live long enough
...
30 | let i = i.chain(more_args);
| ------------------ argument requires that `a1` is borrowed for `'a`
...
33 | }
| - `a1` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
I want to get a new default_args_2 from default_args, where the Item inside default_args_2 has a shorter lifetime, as long as it valid inside the function.
You can add a little (almost) noop .map(|v| v):
let i = default_args.into_iter().map(|v| v);
The problem was that the iterator item types have to match, and therefore the chained iterator has to also yield &'a T, which it does not (it yields a shorter lifetime).
Since you don't really need to use the items for 'a, the trick of the noop map() is to insert a subtyping point: Now, the closure inside map() (conceptually) converts the &'a T, to a &'shorter_lifetime T, which is fine since 'a: 'shorter_lifetime, but now the chained iterator can produce the desired item type.
Putting it differently, you cannot convert impl Iterator<Item = &'a T> into impl Iterator<Item = &'shorter_lifetime T> (as it may not be covariant over the lifetime), but you can convert &'a T to &'shorter_lifetime T, and we use that fact because from the outside we have impl Iterator<Item = &'a T>, but inside map()'s closure we got &'a T.
I'm having trouble with the follwoing code...
use std::collections::BTreeSet;
use maybe_owned::MaybeOwned;
struct Thing<'a, T> {
set: BTreeSet<MaybeOwned<'a, T>>
}
impl<'a, T: Ord> Thing<'a, T> {
fn take(&mut self, x: T){
let y = self.set.take(&MaybeOwned::Borrowed(&x));
}
}
gives the compiler error
error[E0597]: `x` does not live long enough
--> src/main.rs:10:53
|
8 | impl<'a, T: Ord> Thing<'a, T> {
| -- lifetime `'a` defined here
9 | fn take(&mut self, x: T){
10 | let y = self.set.take(&MaybeOwned::Borrowed(&x));
| ------------------------------------^^--
| | |
| | borrowed value does not live long enough
| argument requires that `x` is borrowed for `'a`
11 | }
| - `x` dropped here while still borrowed
However x is clearly not borrowed at that point because the MaybeOwned has fallen out of scope and therefore the enclosed borrow has fallen out of scope.
How can I tell the rust compiler that this is fine?
The problem is that, while the temporary MaybeOwned doesn't live that long, it doesn't matter because it's implicitly MaybeOwned<'a, T>. This means x must live at least as long as 'a but it doesn't. The fact that the temporary MaybeOwned won't live that long isn't relevant to the borrow checker.
BTreeSet::take()'s second argument is a &Q, where the set's own T implements Borrow<Q>. MaybeOwned<'a, T> doesn't implement Borrow<MaybeOwned<'b, T>> where 'b: 'a, but all T and &T implement Borrow<T> thanks to a blanket implementation, so given an argument typed &MaybeOwned<'_, T>, the only lifetime that satisfies the T: Borrow<Q> constraint is &MaybeOwned<'a, T> -- thus, the lifetime parameter of the temporary MaybeOwned is inferred to be 'a. That's the only way the trait bounds can be satisfied.
Thankfully, none of this matters, because MaybeOwned<'_, T> implements Borrow<T>, which means you can just supply a &T:
let y = self.set.take(&x);
This question already has an answer here:
Why can't I assign one dereference of a reference of a reference to another when the outer lifetimes differ?
(1 answer)
Closed 2 years ago.
I'm not able to borrow where I thought I could. I reduced the problem down to this case:
struct A<'a> {
borrow: &'a mut u8,
}
fn does_nothing<'b, 'c>(a: &'b mut A<'c>) {
a.borrow = a.borrow;
}
error[E0623]: lifetime mismatch
--> src/lib.rs:6:16
|
5 | fn does_nothing<'b, 'c>(a: &'b mut A<'c>) {
| -------------
| |
| these two types are declared with different lifetimes...
6 | a.borrow = a.borrow;
| ^^^^^^^^ ...but data from `a` flows into `a` here
It seems that a.borrow has the intersection of 'b and 'c and therefore cannot be guaranteed to still have the lifetime 'c.
I don't have any real problem with this and can work around it by making both lifetimes the same, but why this does not borrow check?
It seems structs are unimportant to showing this issue and double borrows show it easily.
I have three functions which are pretty similar, but I would have trouble knowing which one compiles and which error the non-compiling ones would give.
The simple generic function:
fn only_borrow<T>(a: &mut T) {
*a = *a;
}
results in the error:
error[E0507]: cannot move out of `*a` which is behind a mutable reference
--> src/lib.rs:2:10
|
2 | *a = *a;
| ^^ move occurs because `*a` has type `T`, which does not implement the `Copy` trait
Including an extra level of indirection changes the error
fn only_borrow_double<T>(a: &mut &mut T) {
*a = *a;
}
error[E0623]: lifetime mismatch
--> src/lib.rs:2:10
|
1 | fn only_borrow_double<T>(a: &mut &mut T) {
| -----------
| |
| these two types are declared with different lifetimes...
2 | *a = *a;
| ^^ ...but data from `a` flows into `a` here
Changing away from the implied lifetimes can fix the error:
fn working_double<'b, T>(a: &'b mut &'b mut T) {
*a = *a;
}
You'll have to take a look at your lifetimes 'b and 'c:
&'b mut ... means that you have a reference that is live and valid for a "time" 'b.
A<'c> means that you have an object that is live and valid for 'c.
What you don't have is a specific relation between these two lifetimes. The only thing the compiler can deduce is that since A<'c> is behind a &'b, 'c must outlive 'b, i.e., whenever 'b is valid, so is 'c. Though, crucially, not the other way around.
As you show, the compiler requires 'b and 'c to be the same lifetime.
Why is this?
Let us have a look at our possibilities:
'c and 'b have no relation: It is easy to see that without any relation, the compiler cannot guarantee anything about what is put in A.borrow and as such won't allow it.
'c strictly outlives 'b, i.e., some places 'c is valid 'b is not:
a.borrow = a.borrow becomes a reborrow of a using the 'b lifetime. This answer explains why that happens. However, this means that a is now dependent on the 'b lifetime, which is not valid for some of the time a is valid (since a has the lifetime 'c). This gives the error.
'b strictly outlives 'c: If we had this relation, it might work. The reborrow will be valid, since we get a "bigger" lifetime ('b) than we asked for ('c). However, we already have 'c: 'b inferred by the compiler behind the scenes. Therefore, adding this lifetime means the two lifetimes become equal and we are then back where we started:
struct A<'a> {
borrow: &'a mut u8,
}
/// We add the relation 'b: 'c
fn does_nothing<'b: 'c, 'c>(a: &'b mut A<'c>) {
a.borrow = a.borrow;
}
After I read the subtyping chapter of the Nomicon, I couldn't wrap my head around covariance of a type parameter. Especially for the Box<T> type, which is described as: T is covariant.
However, if I write this code:
trait A {}
trait B: A {}
struct C;
impl A for C {}
impl B for C {}
fn foo(v: Box<dyn A>) {}
fn main() {
let c = C;
let b: Box<dyn B> = Box::new(c);
foo(b);
}
(Playground)
error[E0308]: mismatched types
--> src/main.rs:13:9
|
13 | foo(b);
| ^ expected trait `A`, found trait `B`
|
= note: expected type `std::boxed::Box<(dyn A + 'static)>`
found type `std::boxed::Box<dyn B>`
B is clearly a "subtype" of A and Box is covariant over its input. I don't know why it doesn't work or why it won't do any type coercion. Why would they consider Box<T> to be covariant where the only use cases are invariants?
What subtyping and variance means in Rust
The Nomicon is not a fully polished document. Right now, 5 of the most recent 10 issues in that repo specifically deal with subtyping or variance based on their title alone. The concepts in the Nomicon can require substantial effort, but the information is generally there.
First off, check out some initial paragraphs (emphasis mine):
Subtyping in Rust is a bit different from subtyping in other languages. This makes it harder to give simple examples, which is a problem since subtyping, and especially variance, are already hard to understand properly.
To keep things simple, this section will consider a small extension to the Rust language that adds a new and simpler subtyping relationship. After establishing concepts and issues under this simpler system, we will then relate it back to how subtyping actually occurs in Rust.
It then goes on to show some trait-based code. Reiterating the point, this code is not Rust code anymore; traits do not form subtypes in Rust!
Later on, there's this quote:
First and foremost, subtyping references based on their lifetimes is the entire point of subtyping in Rust. The only reason we have subtyping is so we can pass long-lived things where short-lived things are expected.
Rust's notion of subtyping only applies to lifetimes.
What's an example of subtyping and variance?
Variant lifetimes
Here's an example of subtyping and variance of lifetimes at work inside of a Box.
A failing case
fn smaller<'a>(v: Box<&'a i32>) {
bigger(v)
}
fn bigger(v: Box<&'static i32>) {}
error[E0308]: mismatched types
--> src/lib.rs:2:12
|
2 | bigger(v)
| ^ lifetime mismatch
|
= note: expected type `std::boxed::Box<&'static i32>`
found type `std::boxed::Box<&'a i32>`
note: the lifetime 'a as defined on the function body at 1:12...
--> src/lib.rs:1:12
|
1 | fn smaller<'a>(v: Box<&'a i32>) {
| ^^
= note: ...does not necessarily outlive the static lifetime
A working case
fn smaller<'a>(v: Box<&'a i32>) {}
fn bigger(v: Box<&'static i32>) {
smaller(v)
}
Invariant lifetimes
Here's a case that works:
struct S<'a>(&'a i32);
fn smaller<'a>(_v: &S<'a>, _x: &'a i32) {}
fn bigger(v: &S<'static>) {
let x: i32 = 1;
smaller(v, &x);
}
The same code with all the references changed to mutable references will fail because mutable references are invariant:
struct S<'a>(&'a mut i32);
fn smaller<'a>(_v: &mut S<'a>, _x: &'a mut i32) {}
fn bigger(v: &mut S<'static>) {
let mut x: i32 = 1;
smaller(v, &mut x);
}
error[E0597]: `x` does not live long enough
--> src/lib.rs:7:16
|
7 | smaller(v, &mut x);
| -----------^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `x` is borrowed for `'static`
8 | }
| - `x` dropped here while still borrowed
Addressing specific points
B is clearly a "subtype" of A
It is not.
Box is covariant over its input
It is, where covariance is only applicable to lifetimes.
I don't know why it doesn't work or why it won't do any type coercion.
This is covered by Why doesn't Rust support trait object upcasting?
Why would they consider Box<T> to be covariant
Because it is, for the things in Rust to which variance is applied.
See also
How do I deal with wrapper type invariance in Rust?
Why does linking lifetimes matter only with mutable references?
What is an example of contravariant use in Rust?
To add a bit:
I think the confusion here is mainly due to a common misconception that when we say Foo<T>, T is always assumed to be an owned type. In fact, T can refer to a reference type, such as &i32.
As for (co)variance, Wikipedia defines it as:
Variance refers to how subtyping between more complex types relates to subtyping between their components.
In Rust, as pointed by others, subtyping applies to lifetimes only. Subtrait relationships don’t define subtypes: If trait A is a subtrait of trait B, it doesn’t mean that A is a subtype of B.
An example of subtyping among lifetimes: A shared reference (e.g. &'a i32) is a subtype of another shared reference (e.g. &'b i32) if and only if the former’s lifetime outlives the latter’s lifetimes ('a outlives 'b). Below is some code that demonstrates it:
fn main() {
let r1: &'static i32 = &42;
// This obviously works
let b1: Box<&'static i32> = Box::new(r1);
// This also works
// because Box<T> is covariant over T
// and `&'static i32` is a subtype of `&i32`.
// NOTE that T here is `&i32`, NOT `i32`
let b2: Box<&i32> = Box::new(r1);
let x: i32 = 42;
let r2: &i32 = &x;
// This does NOT work
// because `&i32` is NOT a subtype of `&'static i32`
// (it is the other way around)
let b3: Box<&'static i32> = Box::new(r2);
}
Why does the Rust compiler emit an error requesting me to constrain the lifetime of the generic parameter in the following structure?
pub struct NewType<'a, T> {
x: &'a T,
}
error[E0309]: the parameter type `T` may not live long enough
--> src/main.rs:2:5
|
2 | x: &'a T,
| ^^^^^^^^
|
= help: consider adding an explicit lifetime bound `T: 'a`...
note: ...so that the reference type `&'a T` does not outlive the data it points at
--> src/main.rs:2:5
|
2 | x: &'a T,
| ^^^^^^^^
I can fix it by changing to
pub struct NewType<'a, T>
where
T: 'a,
{
x: &'a T,
}
I don't understand why it is necessary to add the T: 'a part to the structure definition. I cannot think of a way that the data contained in T could outlive the reference to T. The referent of x needs to outlive the NewType struct and if T is another structure then it would need to meet the same criteria for any references it contains as well.
Is there a specific example where this type of annotation would be necessary or is the Rust compiler just being pedantic?
What T: 'a is saying is that any references in T must outlive 'a.
What this means is that you can't do something like:
let mut o: Option<&str> = Some("foo");
let mut nt = NewType { x: &o }; // o has a reference to &'static str, ok.
{
let s = "bar".to_string();
let o2: Option<&str> = Some(&s);
nt.x = &o2;
}
This would be dangerous because nt would have a dangling reference to s after the block. In this case it would also complain that o2 didn't live long enough either.
I can't think of a way you can have a &'a reference to something which contains shorter-lifetime references, and clearly the compiler knows this in some way (because it's telling you to add the constraint). However I think it's helpful in some ways to spell out the restriction, since it makes the borrow checker less magic: you can reason about it just from just type declarations and function signatures, without having to look at how the fields are defined (often implementation details which aren't in documentation) or how the implementation of a function.