'temporary value dropped while borrowed' on value that is not borrowed

I'm getting the error temporary value dropped while borrowed from this code

use std::hint::black_box;

// The error only occurs if the type in `Wrapper` is 'complex' (`<&() as Trait>::Type` rather than `&()`)
pub trait Trait {
	type Type;
}
impl Trait for &() {
	type Type = Self;
}

// The error only occurs if `Wrapper` uses a lifetime
struct Wrapper<'a>(<&'a () as Trait>::Type);

fn foo<T>(_: T, _: &T) {}

fn main() {
	let data = Wrapper(&());

	// The error only occurs if the temporary is the result of an expression (not a literal)
	foo(Wrapper(&black_box(())), &data);

	data;
}

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:17:15
   |
17 |     foo(Wrapper(&black_box(())), &data);
   |                  ^^^^^^^^^^^^^         - temporary value is freed at the end of this statement
   |                  |
   |                  creates a temporary value which is freed while still in use
18 |
19 |     data;
   |     ---- borrow later used here
   |
help: consider using a `let` binding to create a longer lived value
   |
17 ~     let binding = black_box(());
18 ~  foo(Wrapper(&binding), &data);

The error seems to imply that data is borrowing from the temporary value passed to foo, but I don't see why.

Is there any way to tell the compiler that data doesn't borrow from the other argument of foo? I've noticed that if I make foo not generic, and give each parameter a different lifetime, the error goes away, so maybe it's just beause the parameters share the same lifetime. If that's the case, is there any way for a function to take two of the same type, just with different lifetimes?

I know I could just follow the suggestion in the error message, but this is in the public API for a library I'm writing, and it would be nicer if users had the option of writing their inputs inline, e.g.

create_bind_group(
	Material {
		base_colour_texture: Some(&texture.create_view()),
		normal_map: Some(&normal_map.create_view()),
		..Material::default()
	},
	Layout::<Material>::new()
);

vs

let texture_view = texture.create_view();
let normal_map_view = normal_map.create_view();
create_bind_group(
	Material {
		base_colour_texture: Some(&texture_view),
		normal_map: Some(&normal_map_view),
		..Material::default()
	},
	Layout::<Material>::new()
);

The important lesser-known fact here is that traits are always invariant over their generic parameters (and Self counts just like a generic parameter) — lifetimes in those generics cannot be transparently lengthened or shortened, like they can with ordinary uses of references. This is because traits don’t define whether a generic parameter can be covariant (shortenable) or contravariant (lengthenable), because different implementations could use the generic parameter in different ways. For example:

struct Foo;
impl<'a> Trait for &'a Foo {
	type Type = &'a str; // it'd be okay if `'a` could be covariant...
}

struct Bar;
impl<'a> Trait for &'a Bar {
	type Type = fn(&'a str); // ...but for this impl it could only be contravariant.
}

Then, since Wrapper makes use of the trait’s associated type, Wrapper<'a> itself is invariant in 'a. Therefore we end up with the following:

  • You call foo::<T>() on two values, one of which is data.
  • What is T? it must be Wrapper(&'x ()), with some particular lifetime 'x.
  • What is 'x? It must be a lifetime compatible with borrowing data and with borrowing the temporary value returned from black_box(()).
  • Therefore, 'x must end when the temporary value is dropped, at the end of the foo(...).
  • Therefore, all Wrapper(&'x ())es, including data, are invalid when foo is dropped.

What the best solution to this problem is depends on what you’re actually wanting Trait and Wrapper to do outside of a tiny example. Some solutions include:

  • Define Trait so it is implemented for the referent instead of the reference — keeping the references outside of the trait and thus still covariant.
  • Define Trait so it is implemented for a placeholder type that doesn’t involve the lifetime you want to keep covariant, such as &'static (). This usually means making the associated type generic over a lifetime, too.
  • Add a “manual reborrow” method to Trait to manually obtain the flexibility that you would get through covariance.

Thank you for the explaination.

In this case, Trait is something based on the condtype crate:

trait CondType<const B: bool> {
	type Type;
}
impl<const B: bool, T> CondType<B> for T {
	default type Type = ();
}
impl<T> CondType<true> for T {
	type Type = T;
}

type If<const B: bool, T> = <T as CondType<B>>::Type;

Which I use to pick a type based on a const-generic bool (i.e. If<true, T> == T and If<false, T> == ()).

Good idea, I decided to move the reference out of the If (If<B, &T> -> &If<B, T>) and that fixes the example I started with, but in my full code I found a potential issue with the generic_const_exprs feature (although it's incomplete so I can't really complain)

#![feature(generic_const_exprs)]

use std::hint::black_box;
use std::marker::PhantomData;

struct Material<'a>(&'a ());

struct Inner<const TYPE: u8>;

struct Wrapper<T>(Inner<{ 0 + 0 }>, PhantomData<T>);

fn foo<T>(_: T, _: &Wrapper<T>) {}

fn main() {
	let wrapper = Wrapper(Inner, PhantomData);

	foo(Material(&black_box(())), &wrapper);

	wrapper;
}

This code complains with the same error, but if I remove the generic const expression (change { 0 + 0 } to { 0 }), it compiles fine. Is this intentional or should I report this?