Reputation: 4617
How do you obtain address of trait object? I tried this:
fn func() {}
fn main() {
let boxed_func: Box<dyn Fn()> = Box::new(func);
println!("expect: {:p}", func as *const ()); // 0x55bb0207e570
println!("actual: {:p}", &boxed_func); // 0x7ffe5217e5a0
println!("actual: {:p}", Box::into_raw(boxed_func)); // 0x1
}
But it yields different addresses.
Upvotes: 6
Views: 1457
Reputation: 10414
First, the easy one: as mentioned in the comments, &boxed_func
is simply the address of the local variable boxed_func
, not the address of the underlying data. Think of it as a pointer to the pointer.
Now the hard part. I think one thing that's confusing you is that pointers to trait objects are fat pointers, and this isn't reflected by printing with "{:p}"
. They consist of a pointer to the actual data as well as a pointer to a vtable, which stores information about the implementation of the trait.
This can be seen with the (likely UB) code
fn func() {}
fn main() {
let boxed_func: Box<dyn Fn()> = Box::new(func);
println!("function pointer: {:p}", func as fn()); // 0x560ae655d1a0
println!("trait object data pointer: {:p}", boxed_func); // 0x1
println!("stack pointer: {:p}", &boxed_func); // 0x7ffebe8f4688
let raw = Box::into_raw(boxed_func);
println!("raw data pointer: {:p}", raw); // 0x1
// This is likely undefined behavior, since I believe the layout of trait objects isn't specified
let more_raw = unsafe { std::mem::transmute::<_, (usize, usize)>(raw) };
println!("full fat pointer: {:#x}, {:#x}", more_raw.0, more_raw.1); // 0x1, 0x560ae6789468
}
So the actual underlying pointer of boxed_func
consists of two pointers: 0x1
and 0x55ec289004c8
(results may vary here). 0x1
is the usual value for pointers to zero-sized types. Obviously, you don't want to use a null pointer for this purpose, but you don't really need a valid pointer, either. Zero-sized types are often allocated using Unique::empty
, which simply returns a dangling pointer to the memory location at the alignment of the type (the alignment of a zero-sized type is 1).
// Some zero-sized types and where they get allocated
struct Foo;
fn main() {
let x = Box::new(());
println!("{:p}", x); // 0x1
let y = Box::new(Foo);
println!("{:p}", y); // 0x1
}
So in our situation with the trait object, this is telling us that the data part of the trait object is (probably) a zero-sized type, which makes sense since func
doesn't have any data associated to it other than what's needed to call it. That information is kept in the vtable
.
A safer (less UB inducing) way to see the raw parts of the trait object is with the nightly-only struct TraitObject
.
#![feature(raw)]
use std::raw::TraitObject;
fn func() {}
fn main() {
let boxed_func: Box<dyn Fn()> = Box::new(func);
println!("function: {:p}", func as fn()); // 0x56334996e850
println!("function trait object: {:p}", boxed_func); // 0x1
println!("stack address: {:p}", &boxed_func); // 0x7ffee04c2378
// Safety: `Box<dyn Trait>` is guaranteed to have the same layout as `TraitObject`.
let trait_object = unsafe { std::mem::transmute::<_, TraitObject>(boxed_func) };
println!("data pointer: {:p}", trait_object.data); // 0x1
println!("vtable pointer: {:p}", trait_object.vtable); // 0x563349ba3068
}
Try this out with some other trait objects and see if you can find some that don't have zero-sized data.
TraitObject
is deprecated and according to Tracking Issue for pointer metadata APIs #81513, it’s replaced with to_raw_parts(self)
method of primitive type pointer.
#![feature(ptr_metadata)]
trait Trait {
fn f(&self) -> i32;
}
struct Struct {
i: i32
}
impl Trait for Struct {
fn f(&self) -> i32 {
self.i
}
}
fn main() {
let s = Struct { i: 1 };
let sp = &s as *const _;
let (sdynp, sdynvtable) = (&s as &dyn Trait as *const dyn Trait).to_raw_parts();
println!("sp = {:p}", sp);
println!("sdynp = {:p}, sdynvtable = {:#?}", sdynp, sdynvtable);
}
Upvotes: 10