Transmuting generic to specific type safe?

I've ended up creating some code that kinda looks like this:

use std::any::{TypeId, type_name};
use std::mem::transmute;

struct Thing<T>(T);
#[derive(Default)]
struct Helper<T>(T);

impl Helper<i32> {
    fn i32(&self)  {
        println!("i32!");
    }
}

impl Helper<u32> {
    fn u32(&self)  {
        println!("u32!");
    }
}

enum KnownThings {
    I32(Thing<i32>),
    U32(Thing<u32>)
}

fn do_thing<T: 'static>(thing: KnownThings, helper: &Helper<T>) {
    match thing {
        KnownThings::I32(i) => {
            assert!(TypeId::of::<T>() == TypeId::of::<i32>());
            let helper: &Helper<i32> = unsafe { std::mem::transmute(helper) };
            helper.i32();
        }
        KnownThings::U32(i) => {
            assert!(TypeId::of::<T>() == TypeId::of::<u32>());
            let helper: &Helper<u32> = unsafe { std::mem::transmute(helper) };
            helper.u32();
        }
    }
}

fn main() {
    do_thing(KnownThings::I32(Thing(1)), &Helper::<i32>::default());
    do_thing(KnownThings::U32(Thing(3)), &Helper::<u32>::default());
}

I was actually a bit surprised this even worked. Is transmuting Helper like this safe? Is there a better way I should be going about this?

Here's how you can do it without unsafe: Is it possible to get the enum field directly according to the generic type - #4 by quinedot

4 Likes

And here's how your code would look like with downcast_ref instead of your unsafe transmute.

use std::any::Any;

struct Thing<T>(T);
#[derive(Default)]
struct Helper<T>(T);

impl Helper<i32> {
    fn i32(&self)  {
        println!("i32!");
    }
}

impl Helper<u32> {
    fn u32(&self)  {
        println!("u32!");
    }
}

enum KnownThings {
    I32(Thing<i32>),
    U32(Thing<u32>)
}

fn do_thing<T: 'static>(thing: KnownThings, helper: &Helper<T>) {
    match thing {
        KnownThings::I32(_) => {
            let helper: &Helper<i32> = (helper as &dyn Any).downcast_ref().unwrap();
            helper.i32();
        }
        KnownThings::U32(_) => {
            let helper: &Helper<u32> = (helper as &dyn Any).downcast_ref().unwrap();
            helper.u32();
        }
    }
}

fn main() {
    do_thing(KnownThings::I32(Thing(1)), &Helper::<i32>::default());
    do_thing(KnownThings::U32(Thing(3)), &Helper::<u32>::default());
}
4 Likes

Thanks for this thread (and the example!). I particularly like the trait dispatch method from that thread, but in my real code I want to call a function on Thing that takes a specific Helper, ie:

fn use_helper<T>(thing: &Thing<T>, helper: &Helper<T>)

Would you know if it would be possible to call this from do_thing this via trait dispatch as well, or is the Any solution my best option?

Is something like this what you’re looking for?

struct Thing<T>(T);
#[derive(Default)]
struct Helper<T>(T);

enum KnownThings {
    I32(Thing<i32>),
    U32(Thing<u32>),
}

#[derive(Copy, Clone, Debug)]
struct WrongThing;

impl TryInto<Thing<i32>> for KnownThings {
    type Error = WrongThing;
    fn try_into(self) -> Result<Thing<i32>, WrongThing> {
        if let Self::I32(x) = self {
            Ok(x)
        } else {
            Err(WrongThing)
        }
    }
}

impl TryInto<Thing<u32>> for KnownThings {
    type Error = WrongThing;
    fn try_into(self) -> Result<Thing<u32>, WrongThing> {
        if let Self::U32(x) = self {
            Ok(x)
        } else {
            Err(WrongThing)
        }
    }
}

fn use_helper<T>(thing: &Thing<T>, helper: &Helper<T>) {
    dbg!(std::any::type_name::<T>());
}

fn do_thing<T>(thing: KnownThings, helper: &Helper<T>)
where
    KnownThings: TryInto<Thing<T>, Error: std::fmt::Debug>,
{
    use_helper(&thing.try_into().unwrap(), helper);
}

fn main() {
    do_thing(KnownThings::I32(Thing(1)), &Helper::<i32>::default());
    do_thing(KnownThings::U32(Thing(3)), &Helper::<u32>::default());
}
3 Likes

Yes! This is exactly what I was looking for. I always find it hard to tackle such generic code. Thanks!

the code (I assume you reduced from more complex code) in your example is a bit misleading. the transmute() in this particular example is safe, only because of the TypeId assertions. for example, the type signature does NOT prevent this from compiling, but the result is a panic because of the assertion failure. if the type assertions are not there, you'll get UB:

do_things(KnowThings::I32(Thing(1)), &Helper::<i64>::default());

the key to understand this code is the concept of monomophization, that is, when you call the generic function do_things<T>(...) with different type `T, you are NOT actually calling the same function, but different "instances" of it.

in fact, to understand how monomophization works and why your example worked, the KnowThings and Helper are distractions, do_things() can be further reduced to something like this,

fn do_things<T: 'static>(x: &T) {
    if TypeId::of::<T>() == TypeId::of::<i32>() {
        let x: &i32 = unsafe { transmute(x) };
        println!("i32: {x}");
    } else if TypeId::of::<T>() == TypeId::of::<u32>() {
        let x: &u32 = unsafe { transmute(x) };
        println!("u32: {x}");
    }
}

the key to understand this code is, with each instance of T, only one branch is executed, so the transmute() is actually between the same type [1].


  1. not strictly the same type, the lifetime is shorter, but it's a safe operation nontheless ↩︎