Suppose you have two collections (Vec
for simplicity here) of instances of T
, and a function to compute whether the elements in those collections appear in either or both of them:
// With lifetimes not yet annotated
fn comm(left: &Vec<T>, right: &Vec<T>) -> Vec<(Tag, &T)> {}
enum Tag {
Left,
Both,
Right,
}
comm(l,r)
guarantees that the references returned point to elements of the left
collection in both the case that T
was present in left
only, and T
was present in both.
However, because some T
might appear in right
only, the function's full signature must look like this:
fn comm<'a, 'b, 'c>(left: &'a Vec<T>, right: &'b Vec<T>) -> Vec(Tag, &'c T)
where
'a: 'c,
'b: 'c,
The actual question, then: I know that, within one of the (tag, &T)
tuples returned comm
, if tag == Left or tag == Both
, then &T
will surely point to the left
collection.
Is it sane, safe, and legitimate to use mem::transmute
or other mechanism to grab one of the references returned by comm
and cast it to the lifetime matching the left
collection?
For instance:
fn last_common<'a, 'b>(left: &'a Vec<T>, right: &'b Vec<T>) -> &'a T {
let tagged = comm(left, right);
let (tag, ref_to_T) = boring code that picks one tuple from tagged...
assert!(matches!(tag, Tag::Left) || matches!(tag, Tag::Both))
return std::mem::transmute::<&'_ T, &'a T>(ref_to_T);
}
Yes, it is sound. In fact, the official documentation for transmute()
says it can be used to extend lifetimes:
https://doc.rust-lang.org/stable/std/mem/fn.transmute.html#examples
Extending a lifetime, or shortening an invariant lifetime. This is advanced, very unsafe Rust!
struct R<'a>(&'a i32); unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> { std::mem::transmute::<R<'b>, R<'static>>(r) } unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> { std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r) }
But I will not recommend it. Instead, I'll recommend you to use an enum:
fn comm<'a, 'b, T>(left: &'a Vec<T>, right: &'b Vec<T>) -> Vec<Tag<'a, 'b, T>> {}
enum Tag<'a, 'b, T> {
Left(&'a T),
Both(&'a T), // Could be `&'b T`, too.
Right(&'b T),
}
You can also have a method to extract the value with the shorter lifetime, like:
impl<'a, T> Tag<'a, 'a, T> {
pub fn value(self) -> &'a T {
let (Self::Left(v) | Self::Right(v) | Self::Both(v)) = self;
v
}
}