Files
Sprimo/skills/unsafe-checker/rules/ffi-13-data-layout.md
2026-02-12 22:58:33 +08:00

147 lines
3.1 KiB
Markdown

---
id: ffi-13
original_id: P.UNS.FFI.13
level: P
impact: HIGH
---
# Ensure Consistent Data Layout for Custom Types
## Summary
Types shared between Rust and C must have `#[repr(C)]` to ensure the memory layout matches what C expects.
## Rationale
- Rust's default layout is unspecified and may change
- C has specific, standardized layout rules
- Mismatched layouts cause memory corruption
## Bad Example
```rust
// DON'T: Rust layout for FFI types
struct BadStruct {
a: u8,
b: u32,
c: u8,
}
// Rust may reorder to: b, a, c (for better packing)
// C expects: a, padding, b, c, padding
extern "C" {
fn use_struct(s: *const BadStruct); // Layout mismatch!
}
// DON'T: Assume Rust enum layout matches C
enum BadEnum {
A,
B(i32),
C { x: u8, y: u8 },
}
// Rust enum layout is complex and not C-compatible
```
## Good Example
```rust
// DO: Use repr(C) for FFI structs
#[repr(C)]
struct GoodStruct {
a: u8, // offset 0
// 3 bytes padding
b: u32, // offset 4
c: u8, // offset 8
// 3 bytes padding
}
// Total size: 12, align: 4
// DO: Use repr(C) for enums with explicit discriminant
#[repr(C)]
enum GoodEnum {
A = 0,
B = 1,
C = 2,
}
// Equivalent to C: enum { A = 0, B = 1, C = 2 };
// DO: For complex enums, use tagged unions
#[repr(C)]
struct TaggedUnion {
tag: GoodEnum,
data: GoodUnionData,
}
#[repr(C)]
union GoodUnionData {
a: (), // For GoodEnum::A
b: i32, // For GoodEnum::B
c: [u8; 2], // For GoodEnum::C
}
// DO: Verify layout at compile time
const _: () = {
assert!(std::mem::size_of::<GoodStruct>() == 12);
assert!(std::mem::align_of::<GoodStruct>() == 4);
};
```
## Layout Verification
```rust
use std::mem::{size_of, align_of, offset_of};
#[repr(C)]
struct Verified {
a: u8,
b: u32,
c: u8,
}
// Compile-time layout verification
const _: () = {
assert!(size_of::<Verified>() == 12);
assert!(align_of::<Verified>() == 4);
// offset_of! requires nightly or crate
// assert!(offset_of!(Verified, a) == 0);
// assert!(offset_of!(Verified, b) == 4);
// assert!(offset_of!(Verified, c) == 8);
};
// Runtime verification
#[test]
fn verify_layout() {
assert_eq!(size_of::<Verified>(), 12);
assert_eq!(align_of::<Verified>(), 4);
let v = Verified { a: 0, b: 0, c: 0 };
let base = &v as *const _ as usize;
assert_eq!(&v.a as *const _ as usize - base, 0);
assert_eq!(&v.b as *const _ as usize - base, 4);
assert_eq!(&v.c as *const _ as usize - base, 8);
}
```
## repr Options
| Attribute | Effect |
|-----------|--------|
| `#[repr(C)]` | C-compatible layout |
| `#[repr(C, packed)]` | C layout, no padding |
| `#[repr(C, align(N))]` | C layout, minimum align N |
| `#[repr(transparent)]` | Same layout as single field |
| `#[repr(u8)]` etc. | Enum discriminant type |
## Checklist
- [ ] Is every FFI struct marked `#[repr(C)]`?
- [ ] Is every FFI enum using explicit discriminants?
- [ ] Have I verified the layout matches the C header?
- [ ] Have I added compile-time assertions?
## Related Rules
- `mem-01`: Choose appropriate data layout
- `ffi-14`: Types in FFI should have stable layout