Transmute into a generic type to modify internal lifetime

This question is motivated by my struggles here: https://users.rust-lang.org/t/borrowing-copying-from-a-more-restrictive-generic-to-a-less-restrictive-generic.

My problem has been solved, inelegantly, but safely. But the fact that I couldn't figure out how to do this has been driving me nuts!

So this is the question stripped to its essence. What can I pass to transmute when I don't have a symbol to represent the concrete type I need to transmute into?

use std::mem::transmute;

trait Key<'a> : Copy {}
impl <'a>Key<'a> for &'a str {}

fn make_new_key<'a, 'b, K : Key<'a>>(template : &K, lifetime_holder : &'b usize) -> impl Key<'b>
{
    let new_key = template.clone();
    unsafe { transmute::<K, ????>(new_key) }
}

fn main() {
    let new_key = make_new_key(&"hello", &2);
}

I thought perhaps there are some internal accessors for the "fields" of a type definition, if the language doesn't already have syntax to do this.

Thank you for any ideas.

The caller of make_new_key chooses K, and thus K does not have a fixed layout (e.g. it could have any size), and thus K cannot be transmuted to a concrete type (which must have one specific layout/size) as required for the return type.

Thank you for the reply.

In general, yes. I should have bounded K by Copy to be more clear.

I know that the K I'm starting with and the type I'm returning have exactly the same memory layout because the point of this function is that they are exactly the same type except for their internal lifetimes.

I want to know if I can modify the internal lifetime on a generic (knowing full-well that this is unsafe in the general case)

Thanks.

Ah -- your -> impl Key<'b> threw me off.

Let's ignore the lifetimes for a second and indicate we're returning the same type otherwise:

-fn make_new_key<'a, 'b, K : Key<'a>>(template : &K, lifetime_holder : &'b usize) -> impl Key<'b>
+fn make_new_key<'a, 'b, K : Key<'a>>(template : &K, lifetime_holder : &'b usize) -> K
 {
     let new_key = template.clone();
-    unsafe { transmute::<K, ????>(new_key) }
+    unsafe { transmute::<K, _>(new_key) }
 }

We still get "error[E0512]: cannot transmute between types of different sizes, or dependently-sized types". Which I take to mean, transmuting a generic is expressly forbidden. We could just stop there, but let's continue exploring some...

What if transmute wasn't limited like this? You're still not really trying to change the lifetime of K as written. The 'a on make_new_key fills a type parameter on the Key trait. But

// the lifetime of the implementer (`&'a str`)
// and the lifetime of the trait implementation (`Key<'a>`)
// are the same
impl<'a> Key<'a> for &'a str {}

is not the only possibility. For example,

impl<'a> Key<'static> for &'a i32 {}
impl<'a, 'b> Key<'a> for &'b usize {}
impl<'a> Key<'a> for &'static Vec<bool> {}

So there's not necessarily a direct correlation between the lifetimes. And you can't change what a type is or isn't implemented for. So maybe you meant something closer to this:

fn make_new_key<'a, 'b, K: 'a + Key<'a>>(template : &K, lifetime_holder : &'b usize) -> K: 'b
//                     new ^^^^                                                      new ^^^^
{ todo!() }

You'll get an error about the return type: it doesn't know what to make of that bound.

And it turns out, this approach cannot work either, because lifetime bounds are descriptive and not prescriptive. There's not a way to change the bound that the compiler has calculated.


So when can you change the lifetime (ignoring soundness)? When the lifetime is part of the type, and not a bound.

I don't think there's any way to do it with a generic which may or may not have a lifetime parameter (or many!) as part of its type, depending on the caller.

2 Likes

Thanks for the awesome explanation!

I thought perhaps there would be a way to poke the compiler's internals, but I see your points about why that's a can of worms.

Thank you again.

This doesn't change the rest of your explanation, but we can get around that limitation by using transmute_copy.

Part of the problem here is there's no way to instantiate the Key trait as written. One possibility is to define a new trait (unsafe for many reasons) which can instantiate a new Key type with a different lifetime parameter:

unsafe trait KeyFamily<'a> {
    type Key: Key<'a>;
}

unsafe impl<'a, 'b> KeyFamily<'b> for &'a str {
    type Key = &'b str;
}

fn make_new_key<'a, 'b, K : Key<'a> + KeyFamily<'b>>(template : &K, lifetime_holder : &'b usize) -> <K as KeyFamily<'b>>::Key {
    unsafe { transmute_copy(template) }
}

Note that TyEq in that example in the playground is just a safety mechanism to try to guarantee that the returned Key has the same layout as K, but I'm not sure it actually guarantees that. We can also do the same without a new trait, with GATs:

trait Key<'a> : Copy {
    type New<'b>: Key<'b>;
}

impl<'a> Key<'a> for &'a str {
    type New<'b> = &'b str;
}

fn make_new_key<'a, 'b, K : Key<'a>>(template : &K, lifetime_holder : &'b usize) -> K::New::<'b> {
    unsafe { transmute_copy(template) }
}

As written in the example, make_new_key is obviously super unsafe as it can extend lifetimes arbitrarily, but I'm assuming this is just isolated code from a larger system.

2 Likes

You can't do the transmute generically, but you can define a conversion trait. As each implementation knows the concrete types it's dealing with, the problem doesn't arise.

The downside, of course, is that the conversion trait has to be implemented separately for every key type.

trait IntoKey<'a> {
    type IntoKey: Key<'a>;
    fn into_key(self) -> Self::IntoKey;
}

trait Key<'a>: Copy {}

impl<'a> Key<'a> for &'a str {}

impl<'b, 'a: 'b> IntoKey<'b> for &'a str {
    type IntoKey = &'b str;
    fn into_key(self) -> &'b str {
        self
    }
}

fn make_new_key<'a, K: Copy + IntoKey<'a>>(
    template: &K,
    lifetime_holder: &'a usize,
) -> impl Key<'a> {
    template.into_key()
}

fn main() {
    let new_key = make_new_key(&"hello", &2);
}
3 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.