I think the compiler could give a better help message here

I was watching crust of Rust: Subtyping and Variance, and I realized that the compiler could probably give some more help.

I think that &'a &'a T or any chain of reference using two time the same (non static) lifetime is always a bug. (EDIT: I’m looking at the lifetime of the references, not references to type generic over a liftetime). I think that it’s never what the programmer intended to write. For example:

fn foo<'a>(_: &'a mut &'a str) {
}

struct Bar<'a> {
    x: &'a mut &'a str,
}

And using any of those two can give extremely confusing error messages:

let mut x = "test";

foo(&mut x);
// or
let _ = Bar{x: &mut x};

assert_eq!(x, "test");
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
  --> src/main.rs:12:9
   |
11 |         foo(&mut x);
   |             ------ mutable borrow occurs here
12 |         assert_eq!(x, "test");
   |         ^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         immutable borrow occurs here
   |         mutable borrow later used here

playground

Instead, I think that the compiler should say something like

help: it is possible that you want to use two different lifetimes
 5 |  fn foo<'a>(_: &'a mut &'a str) {
   |                 ^^      ^^
help: you may try to introduce a new lifetime
 5 | fn foo<'a1, 'a2>(_: &'a1 mut &'a2 str) {
   |        ^^^  ^^^      ^^^      ^^^
help: You can learn more about variance and lifetimes (todo: put a link)

Given that it’s a feature request for rustc, I wasn’t sure if I had to post here or on the user forum.

There are usecases for references to references of the same lifetime. For example in a custom reimplementation of the core::fmt infrastructure I have a type like

pub struct Arguments<'a> {
    pieces: &'a [Argument<'a>],
}

since all references and inner references of this type are expected to be temporaries of the same statement. Changing it to use two lifetimes like this:

pub struct Arguments<'a, 'b> {
    pieces: &'a [Argument<'b>],
}

does appear to pass all my tests, but that's more annoying for users having to write Arguments<'_, '_> everywhere, and I don't think it provides any additional value.

It’s not exactly the same. I don’t see the value of &'a &'a, but I can see the value of &'a S<'a>. I think a rustc warning for the former would be useful, but for the later a clippy lint is more than enough (can even be disabled by default).

In addition to &'a &'a, we should probably warn against &'a [&'a T].

This is not always a problem. &'a S<'a> has its uses, for example rustc has uses &'tcx TyS<'tcx> a lot. &'a &'a is pretty useless, but hardly a bug. The main problem is when the S in &'a S<'a> is invariant with respect to its lifetime parameter.

By the way I just saw this PR which might improve the error messages in cases like these.

1 Like

&'a S<'a> is ok. It’s &'a &'a T that isn’t (more specifically &'a mut &'a T).

And thanks for the link to the PR, it’s definitively something that goes into the right direction.

Note:

These two are very different wrt variance. &mut T is invariant in T while &T is covariant T. Which is very significant. So yes, &'a mut [S<'a>] is usually a bug, but take out the mut, and it's usually benign.

3 Likes

Given that &'a &'a T is "morally equivalent" to &'a T (at least for T: Sized, a lint suggesting eliminating the extra indirection makes sense for at least clippy. This only holds for a literal &&T, though, of course, and when you are actually not using impls on &&T that aren't also on &T. You could even extend said lint for &'a &'b T to suggest collapsing to the shorter of the two lifetimes, so long as the longer lifetime parameter is not used elsewhere.

I'm not aware enough of how &'a mut &'a T can/'t be used, but I'm generally in favor of (accurate) lints that point out when you're making something harder on yourself for no benefit.

(And side note: yes &'a [&'a T] is fine, and any lint of this style probably wants a special case for 'static.)

1 Like