Why this code call T1 method

trait T1{
    fn say1(&self);
}
struct ATYPE;
impl T1 for ATYPE{
    fn say1(&self) {
        println!("T1 called");
    }
}
trait T2{
    fn say2(&self);
}
impl T2 for ATYPE{
    fn say2(&self) {
        println!("T2 called");
    }
}
fn main() {
    let a: Box<dyn T1> = Box::new(ATYPE);
    let b: Box<dyn T2> = unsafe{
        std::mem::transmute(a)
    };
    b.say2();
    
}
cargo r                         
   Compiling trait2trait v0.1.0 (/workspace/trait2trait)
warning: method `say1` is never used
  --> src/main.rs:34:8
   |
33 | trait T1{
   |       -- method in this trait
34 |     fn say1(&self);
   |        ^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `trait2trait` (bin "trait2trait") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/trait2trait`
T1 called

Your code hits undefined behavior (UB) due to an invalid transmute, so anything could happen and nothing is guaranteed.

In this particular execution, probably the vtable of dyn T1 and dyn T2 were in some sense "compatible", as were they the layouts of the two Boxes. But the vtable pointers were not modified by the transmute, so the T1 method is called.

But again, your code is UB. You can't rely on any particular behavior and should seek another solution to whatever your use case is.

4 Likes

Well from the docs I gather that:

"transmute is semantically equivalent to a bitwise move of one type into another."

and

"Box is a pointer type"

So to my simple mind what that code does is copy a pointer to a into b´. I would naively assume then that operations on b would behave like operations 'a. Effectively b is now the same as a. Despite the fact that b's type is Box<dyn T2>. Which is what we see happenning.

It's not clear to me though why this should be UB. Sure that transmute is an unsafe operation but if T1 and T2 are the same size and shape why would there be a UB issue? Apart from the fact that the type of b is now lying to you.

And where the vtable lives and gets carried around is beyond me.

This "same shape" is not guaranteed and for arbitrary pair of traits would always be wrong.

1 Like

I don't understand your point here. First, Box<dyn A> and Box<dyn B> do not necessarily have the same layout. One may have the data pointer first, while the other may have the vtable pointer first. Second, the vtables have absolutely zero guarantee to even resemble each other.

I mean isn't that bad enough already?![1] Additionally, you have no guarantee that the program doesn't go up in flames under a full moon.


Of course, Rust could make more guarantees about the layout of the types involved and trait vtable compatibility.

But that is complex to do, may hinder optimization, and may limit the APIs that can be added in the future.

Vtables are structures generated by the compiler for each type-trait combination that the program casts to dyn, and they live in static memory. A Box<dyn A> consists of two pointers: a data pointer to a value of a type implementing A, and a pointer to the vtable.


  1. This lie could be used to implement safe transmute too, so it is not a contained misbehavior ↩︎

1 Like