1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*!
[![GitHub CI Status](https://github.com/LPGhatguy/type-layout/workflows/CI/badge.svg)](https://github.com/LPGhatguy/type-layout/actions)
[![type-layout on crates.io](https://img.shields.io/crates/v/type-layout.svg)](https://crates.io/crates/type-layout)
[![type-layout docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/type-layout)

type-layout is a type layout debugging aid, providing a `#[derive]`able trait
that reports:
- The type's name, size, and minimum alignment
- Each field's name, type, offset, and size
- Padding due to alignment requirements

**type-layout currently only functions on structs with named fields.** This is a
temporary limitation.

## Examples

The layout of types is only defined if they're `#[repr(C)]`. This crate works on
non-`#[repr(C)]` types, but their layout is unpredictable.

```rust
use type_layout::TypeLayout;

#[derive(TypeLayout)]
#[repr(C)]
struct Foo {
    a: u8,
    b: u32,
}

println!("{}", Foo::type_layout());
// prints:
// Foo (size 8, alignment 4)
// | Offset | Name      | Size |
// | ------ | --------- | ---- |
// | 0      | a         | 1    |
// | 1      | [padding] | 3    |
// | 4      | b         | 4    |
```

Over-aligned types have trailing padding, which can be a source of bugs in some
FFI scenarios:

```rust
use type_layout::TypeLayout;

#[derive(TypeLayout)]
#[repr(C, align(128))]
struct OverAligned {
    value: u8,
}

println!("{}", OverAligned::type_layout());
// prints:
// OverAligned (size 128, alignment 128)
// | Offset | Name      | Size |
// | ------ | --------- | ---- |
// | 0      | value     | 1    |
// | 1      | [padding] | 127  |
```

## Minimum Supported Rust Version (MSRV)

type-layout supports Rust 1.34.1 and newer. Until type-layout reaches 1.0,
changes to the MSRV will require major version bumps. After 1.0, MSRV changes
will only require minor version bumps, but will need significant justification.
*/

use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str;

pub use type_layout_derive::TypeLayout;

#[doc(hidden)]
pub use memoffset;

pub trait TypeLayout {
    fn type_layout() -> TypeLayoutInfo;
}

#[derive(Debug)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct TypeLayoutInfo {
    pub name: Cow<'static, str>,
    pub size: usize,
    pub alignment: usize,
    pub fields: Vec<Field>,
}

#[derive(Debug)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Field {
    Field {
        name: Cow<'static, str>,
        ty: Cow<'static, str>,
        size: usize,
    },
    Padding {
        size: usize,
    },
}

impl fmt::Display for TypeLayoutInfo {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        writeln!(
            formatter,
            "{} (size {}, alignment {})",
            self.name, self.size, self.alignment
        )?;

        let longest_name = self
            .fields
            .iter()
            .map(|field| match field {
                Field::Field { name, .. } => name.len(),
                Field::Padding { .. } => "[padding]".len(),
            })
            .max()
            .unwrap_or(1);

        let widths = RowWidths {
            offset: "Offset".len(),
            name: longest_name,
            size: "Size".len(),
        };

        write_row(
            formatter,
            widths,
            Row {
                offset: "Offset",
                name: "Name",
                size: "Size",
            },
        )?;

        write_row(
            formatter,
            widths,
            Row {
                offset: "------",
                name: str::repeat("-", longest_name),
                size: "----",
            },
        )?;

        let mut offset = 0;

        for field in &self.fields {
            match field {
                Field::Field { name, size, .. } => {
                    write_row(formatter, widths, Row { offset, name, size })?;

                    offset += size;
                }
                Field::Padding { size } => {
                    write_row(
                        formatter,
                        widths,
                        Row {
                            offset,
                            name: "[padding]",
                            size,
                        },
                    )?;

                    offset += size;
                }
            }
        }

        Ok(())
    }
}

#[derive(Clone, Copy)]
struct RowWidths {
    offset: usize,
    name: usize,
    size: usize,
}

struct Row<O, N, S> {
    offset: O,
    name: N,
    size: S,
}

fn write_row<O: Display, N: Display, S: Display>(
    formatter: &mut fmt::Formatter,
    widths: RowWidths,
    row: Row<O, N, S>,
) -> fmt::Result {
    writeln!(
        formatter,
        "| {:<offset_width$} | {:<name_width$} | {:<size_width$} |",
        row.offset,
        row.name,
        row.size,
        offset_width = widths.offset,
        name_width = widths.name,
        size_width = widths.size
    )
}