`#[repr(C)]` on nested structs

This seems an easy question, but I can't confirm on my own: what happens if an outer struct are #[repr(C)], but at least one inner field in not? Is the outer struct not #[repr(C)]?

I think the answer is no. The outer struct is not #[repr(C)] as per

The intention is that if one has a set of C struct declarations and a corresponding set of Rust struct declarations, all of which are tagged with #[repr(C)] , then the layout of those structs will all be identical. Note that this setup implies that none of the structs in question can contain any #[repr(Rust)] structs (or Rust tuples), as those would have no corresponding C struct declaration -- as #[repr(Rust)] types have undefined layout, you cannot safely declare their layout in a C program.

src: Structs and tuples - Unsafe Code Guidelines Reference

/// Trap Context
#[repr(C)]
pub struct TrapContext {
    /// general regs[0..31]
    pub x: [usize; 32],
    /// CSR sstatusβ‹…β‹…β‹…β‹…β‹…β‹…
    pub sstatus: Sstatus,
    /// CSR sepc
    pub sepc: usize,
}

where Sstatus is not tagged with repr(C), it's an implicit repr(Rust) instead: sstatus.rs - source


Is it safe to read a block of memory (34*8 bytes) and treat it as *mut TrapContext? (Not safe if the answer to the above question is no IMO)

// linker.ld
    .section .text
    .globl __alltraps
    .globl __restore
    .align 2
__alltraps:
    csrrw sp, sscratch, sp
    # now sp->kernel stack, sscratch->user stack
    # allocate a TrapContext on kernel stack
    addi sp, sp, -34*8
    # save general-purpose registers
    ...
    # set input argument of trap_handler(cx: &mut TrapContext)
    mv a0, sp
    call trap_handler

// Rust
#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext { ... }

If a nested struct is not #[repr(C)] the outer struct is still #[repr(C)] if you marked it as such. All fields up to the non-#[repr(C)] fields have a predictable offset following the rules of #[repr(C)] and all fields after it (if any) have an offset you can calculate based on mem::size_of::<NonReprCStruct>(), but not necessarily predict in advance.

3 Likes

Thanks for your reply. The layout on Sstatus is size = 8, align = 0x8, in this case TrapContext can be read through 34*8 bytes.

But it looks unreliable: repr(Rust) doesn't promise the size or align of Sstatus to be 8.

Is there a way to force the field struct type being repr(C)? I think the only way is modify the source code to add the tag, leading to a patch dependency or feature gate or something? Just wonder about it, not a real issue.

You can't force foreign types to be repr(C). In fact the riscv crate may actually add new fields to Sstatus. The riscv crate should probably add an accessor for the inner bits of Sstatus at the least and likely also make it repr(C).

1 Like

Very clear to me. Greatly appreciate your insight. :heart:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.