Please help me let me keep my allocation

After a bit more thought, it is not obvious that it is not undefined behavior, considering that &'a T and &'b T are different types and Vec is not #[repr(C)]. In any case, it is definitely sound if the transmute is replaced with Vec::from_raw_parts.

2 Likes

Is there any authority that says whether this is UB?

The documentation says in no uncertain terms that if A and B are different types, then transmuting MyStruct<A> to MyStruct<B> is always undefined behavior if MyStruct has no repr attribute (which Vec doesn't). This is the case even if A could be transmuted to B soundly.

I do not think the documentation addresses whether or not a different lifetime counts as a different type in this context, but you generally need to understand references with differing lifetimes as different types to truly understand the borrow checker.

That said, I find it extremely unlikely that the current implementation of the compiler will miscompile code in the case of the transmute from Vec<&'a T> to Vec<&'b T>.

Would this be a sound implementation without transmute?

fn reuse_vec2<'a, 'b, T>(mut v: Vec<&'a T>) -> Vec<&'b T> {
  unsafe {
    v.clear();
    let (ptr, len, cap): (*mut &'a T, usize, usize) = v.into_raw_parts();
    let ptr = ptr.cast::<&'b T>();         
    Vec::from_raw_parts(ptr, len, cap)
  }
}

It uses ptr::cast and I do not know in what cases it has UB.

A different concern: Is it guaranteed that sizeof::<&'a T>() == sizeof::<&'b T>() for all T and 'a and 'b?
If this would not hold, then the capacity would need to be adapted.

Once the borrow checker is done, the compiled code has no more notion of lifetimes, so I can't image how they could possibly have different representations. My imagination doesn't transmute to compiler guarantees, but I think it's fine.

Maybe it would be possible to compile some impossible lifetime &'never T as a ZST...

Could it not also be possible that the optimizer removes a reference, and since transmute is a black box, it gets ignored when making sure it can optimize this away?

For example, if it can statically prove that the Vec will only hold, say, one element and that one element is always in a single position on the stack, is it not possible it substitutes the entire thing with just referencing that element? Then later, we try to transmute it to something with a different lifetime, which in the notion of the optimizer just means a different stack frame, however, that original Vec<'a> doesn't even exist, while the Vec<'b> is used to store something it can't statically prove to be one item.

I feel like this might be a bit of a reach, however the optimizer is allowed to do things like this, no?

It seems to me that a black box couldn't be ignored, but rather must be pessimized.

Hmm that's a good point. Otherwise a lot more code which uses transmute internally would be broken, especially when it's a constant of some sort.

I think even if the black box was ignored, that situation in particular couldn't happen? *const &'a T is allowed to exist regardless of 'a and whether such an &'a T exists. So when Vec::into_raw_parts is called, the vec will count as "used", and the ptr wouldn't be subject to the optimization because it doesn't need the lifetime to be valid to be valid itself.

From my understanding, the latest post is UB free. The reference guarantees the layout of &'a T and *const T are equivalent, and that to me reads as also guaranteeing that &'a T and &'b T are equivalent, as *const T has no lifetime.

So, given &'a T and &'b T are equivalent, and that *const T is an FFI-safe type, *const &'a T and *const &'b T should also be equivalent.

1 Like

Nice -- transitive equivalence is compelling to me.

2 Likes

Ah, I'm sorry, I had missed the new version which addressed the lack of a repr attribute and replaced it with a into_raw_parts. I was still thinking we were talking about the transmute version.

In any case, the answer to the question raised by @samuelpilz could be no, because the optimizer might remove a reference, however checking the size of it, or actually using the reference might cause there to still be one: example.

2 Likes

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.