Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Is it legal Rust to cast a pointer to a struct's first member to a pointer to the struct?

In C, a pointer to a struct can be cast to a pointer to its first member, and vice-versa. That is, the address of a struct is defined to be the address of its first member.

struct Base { int x; };
struct Derived { struct Base base; int y; };

int main() {
    struct Derived d = { {5}, 10 };
    struct Base *base = &d.base; // OK
    printf("%d\n", base->x);
    struct Derived *derived = (struct Derived *)base; // OK
    printf("%d\n", derived->y);
}

This is commonly used to implement C++-style inheritance.

Is the same thing allowed in Rust if the structs are repr(C) (so that their fields aren’t reorganized)?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

#[derive(Debug)]
#[repr(C)]
struct Base {
    x: usize,
}

#[derive(Debug)]
#[repr(C)]
struct Derived {
    base: Base,
    y: usize,
}

// safety: `base` should be a reference to `Derived::base`, otherwise this is UB
unsafe fn get_derived_from_base(base: &Base) -> &Derived {
    let ptr = base as *const Base as *const Derived;
    &*ptr
}

fn main() {
    let d = Derived {
        base: Base {
            x: 5
        },
        y: 10,
    };

    let base = &d.base;
    println!("{:?}", base);

    let derived = unsafe { get_derived_from_base(base) }; // defined behaviour?
    println!("{:?}", derived);
}

The code works, but will it always work, and is it defined behaviour?

>Solution :

The way you wrote it, currently not; but it is possible to make it work.

Reference to T is only allowed to access T – no more (it has provenance for T). The expression &d.base gives you a reference that is only valid for Base. Using it to access Derived‘s fields is undefined behavior. It is not clear this is what we want, and there is active discussion about that (also this), but that is the current behavior. There is a good tool named Miri that allows you to check your Rust code for the presence of some (not all!) undefined behavior (you can run it in the playground; Tools->Miri), and indeed it flags your code:

error: Undefined Behavior: trying to reborrow <untagged> for SharedReadOnly permission at alloc1707[0x8], but that tag does not exist in the borrow stack for this location
  --> src/main.rs:17:5
   |
17 |     &*ptr
   |     ^^^^^
   |     |
   |     trying to reborrow <untagged> for SharedReadOnly permission at alloc1707[0x8], but that tag does not exist in the borrow stack for this location
   |     this error occurs as part of a reborrow at alloc1707[0x0..0x10]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
           
   = note: inside `get_derived_from_base` at src/main.rs:17:5
note: inside `main` at src/main.rs:31:28
  --> src/main.rs:31:28
   |
31 |     let derived = unsafe { get_derived_from_base(base) }; // defined behaviour?
   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^

You can make it work by creating a reference to the whole Derived and casting it to a raw pointer to Base. The raw pointer will keep the provenance of the original reference, and thus that will work:

// safety: `base` should be a reference to `Derived::base`, otherwise this is UB
unsafe fn get_derived_from_base<'a>(base: *const Base) -> &'a Derived {
    let ptr = base as *const Derived;
    &*ptr
}

fn main() {
    let d = Derived {
        base: Base {
            x: 5
        },
        y: 10,
    };

    let base = &d as *const Derived as *const Base;
    println!("{:?}", unsafe { &*base });

    let derived = unsafe { get_derived_from_base(base) };
    println!("{:?}", derived);
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading