Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fiddling with lifetimes: sanity check on unsafe reinterpretation of lifetimes

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);
}
like image 924
Nubarke Avatar asked Jan 25 '26 09:01

Nubarke


1 Answers

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
    }
}
like image 65
Chayim Friedman Avatar answered Jan 27 '26 00:01

Chayim Friedman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!