type_layout/
lib.rs

1/*!
2[![GitHub CI Status](https://github.com/LPGhatguy/type-layout/workflows/CI/badge.svg)](https://github.com/LPGhatguy/type-layout/actions)
3[![type-layout on crates.io](https://img.shields.io/crates/v/type-layout.svg)](https://crates.io/crates/type-layout)
4[![type-layout docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/type-layout)
5
6type-layout is a type layout debugging aid, providing a `#[derive]`able trait
7that reports:
8- The type's name, size, and minimum alignment
9- Each field's name, type, offset, and size
10- Padding due to alignment requirements
11
12**type-layout currently only functions on structs with named fields.** This is a
13temporary limitation.
14
15## Examples
16
17The layout of types is only defined if they're `#[repr(C)]`. This crate works on
18non-`#[repr(C)]` types, but their layout is unpredictable.
19
20```rust
21use type_layout::TypeLayout;
22
23#[derive(TypeLayout)]
24#[repr(C)]
25struct Foo {
26    a: u8,
27    b: u32,
28}
29
30println!("{}", Foo::type_layout());
31// prints:
32// Foo (size 8, alignment 4)
33// | Offset | Name      | Size |
34// | ------ | --------- | ---- |
35// | 0      | a         | 1    |
36// | 1      | [padding] | 3    |
37// | 4      | b         | 4    |
38```
39
40Over-aligned types have trailing padding, which can be a source of bugs in some
41FFI scenarios:
42
43```rust
44use type_layout::TypeLayout;
45
46#[derive(TypeLayout)]
47#[repr(C, align(128))]
48struct OverAligned {
49    value: u8,
50}
51
52println!("{}", OverAligned::type_layout());
53// prints:
54// OverAligned (size 128, alignment 128)
55// | Offset | Name      | Size |
56// | ------ | --------- | ---- |
57// | 0      | value     | 1    |
58// | 1      | [padding] | 127  |
59```
60
61## Minimum Supported Rust Version (MSRV)
62
63type-layout supports Rust 1.34.1 and newer. Until type-layout reaches 1.0,
64changes to the MSRV will require major version bumps. After 1.0, MSRV changes
65will only require minor version bumps, but will need significant justification.
66*/
67
68use std::borrow::Cow;
69use std::fmt::{self, Display};
70use std::str;
71
72pub use type_layout_derive::TypeLayout;
73
74#[doc(hidden)]
75pub use memoffset;
76
77pub trait TypeLayout {
78    fn type_layout() -> TypeLayoutInfo;
79}
80
81#[derive(Debug)]
82#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
83pub struct TypeLayoutInfo {
84    pub name: Cow<'static, str>,
85    pub size: usize,
86    pub alignment: usize,
87    pub fields: Vec<Field>,
88}
89
90#[derive(Debug)]
91#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
92pub enum Field {
93    Field {
94        name: Cow<'static, str>,
95        ty: Cow<'static, str>,
96        size: usize,
97    },
98    Padding {
99        size: usize,
100    },
101}
102
103impl fmt::Display for TypeLayoutInfo {
104    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
105        writeln!(
106            formatter,
107            "{} (size {}, alignment {})",
108            self.name, self.size, self.alignment
109        )?;
110
111        let longest_name = self
112            .fields
113            .iter()
114            .map(|field| match field {
115                Field::Field { name, .. } => name.len(),
116                Field::Padding { .. } => "[padding]".len(),
117            })
118            .max()
119            .unwrap_or(1);
120
121        let widths = RowWidths {
122            offset: "Offset".len(),
123            name: longest_name,
124            size: "Size".len(),
125        };
126
127        write_row(
128            formatter,
129            widths,
130            Row {
131                offset: "Offset",
132                name: "Name",
133                size: "Size",
134            },
135        )?;
136
137        write_row(
138            formatter,
139            widths,
140            Row {
141                offset: "------",
142                name: str::repeat("-", longest_name),
143                size: "----",
144            },
145        )?;
146
147        let mut offset = 0;
148
149        for field in &self.fields {
150            match field {
151                Field::Field { name, size, .. } => {
152                    write_row(formatter, widths, Row { offset, name, size })?;
153
154                    offset += size;
155                }
156                Field::Padding { size } => {
157                    write_row(
158                        formatter,
159                        widths,
160                        Row {
161                            offset,
162                            name: "[padding]",
163                            size,
164                        },
165                    )?;
166
167                    offset += size;
168                }
169            }
170        }
171
172        Ok(())
173    }
174}
175
176#[derive(Clone, Copy)]
177struct RowWidths {
178    offset: usize,
179    name: usize,
180    size: usize,
181}
182
183struct Row<O, N, S> {
184    offset: O,
185    name: N,
186    size: S,
187}
188
189fn write_row<O: Display, N: Display, S: Display>(
190    formatter: &mut fmt::Formatter,
191    widths: RowWidths,
192    row: Row<O, N, S>,
193) -> fmt::Result {
194    writeln!(
195        formatter,
196        "| {:<offset_width$} | {:<name_width$} | {:<size_width$} |",
197        row.offset,
198        row.name,
199        row.size,
200        offset_width = widths.offset,
201        name_width = widths.name,
202        size_width = widths.size
203    )
204}