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:
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:
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].
not strictly the same type, the lifetime is shorter, but it's a safe operation nontheless ↩︎