Is this use of mem::transmute_copy safe?

I'm trying to build a library for structural typing in rust, and I want to behave a certain way if two generic parameters refer to the same type. I would like this to work correctly for any type. This is a simplified version of one of my functions:

fn am_i_safe<A, B>(a: A, b: B) -> B {
    if core::intrinsics::type_id<A>() == core::intrinsics::type_id<B>() {
        let a_as_b = unsafe { std::mem::transmute_copy::<A, B>(&a) };
        std::mem::forget(a);
        a_as_b
    } else {
        b
    }
}

Is this safe? can this ever cause any type of undefined behavior or memory safety issue?
Thanks for any help :slight_smile: I'm excited to learn more about what's possible with Rust

You don't need intrinsics. Otherwise I think it's fine.

Here's a safe version (at the potential cost of an allocation and extra move).

fn am_i_safe<A: Any, B: Any>(a: A, b: B) -> B {
    match Box::<dyn Any>::downcast::<B>(Box::new(a)) {
        Ok(a_as_b) => *a_as_b,
        Err(_) => b,
    }
}

There's probably some hacky ways to emulate specialization here.

2 Likes

Here's an allocation-free safe version. A general rule is: when you want to go from (mutable) reference to value, use Option::take.

fn am_i_safe<A: 'static, B: 'static>(a: A, b: B) -> B {
    let mut a = Some(a);
    let a: &mut dyn Any = &mut a;

    a.downcast_mut()
     .and_then(Option::take)
     .unwrap_or(b)
}

Playground

10 Likes

Thanks to both of you!
One of the reasons I was using core::intrinsics is that the functionality of Any isn't const, and I'd really like for this branch to be optimized away by the compiler (because in practice I'm calling this function many times recursively). Maybe I can leave the core::intrinsics check in (until comparing type ids is const) but use downcasting instead of transmute_copy, just in case I'm doing it wrong. Does that sound reasonable? Alternatively - do you know if the type comparisons in core::any happen at compile time even though they aren't const? I feel like those aren't necessarily the same thing

Const doesn't affect optimizations. It's a contract that keeps the function author from writing code that can't be evaluated at compile time. (The part of the compiler that checks whether something is const-eligible could be used for checking whether something can be optimized at compile time and vice versa – this is probably not actually implemented exactly like that, but it shows that the two problems are of the same difficulty.) Thus, the branch will almost certainly be optimized out even for Any.

I am not an assembly expert, but I took a look at the release build asm output of the Option playground. The "fallback" string doesn't seem to be included in the final program. So at least in this case it seems likely that the compiler is seeing through the indirection.

1 Like

That's great news! thanks a lot :slight_smile: I'll use your solution then.
maybe I'll even try to benchmark it to check that it actually works in my case.

here's the project BTW, in case you're interested. The function I'm talking about isn't there yet but will probably be shortly

Nice observation! what a cool playground

And here are the two functions I was working on - insert_default and get_or