stable_type_layout/
lib.rs1extern crate self as stable_type_layout;
29
30pub use inventory;
31pub use stable_type_layout_derive::TypeLayout;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct Field {
36 pub name: &'static str,
37 pub offset: usize,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct Variant {
47 pub name: &'static str,
48 pub index: usize,
49 pub discriminant: Option<i64>,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum TypeStructure {
55 Struct { fields: &'static [Field] },
56 Union { fields: &'static [Field] },
57 Enum { variants: &'static [Variant] },
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct TypeLayoutInfo {
64 pub name: &'static str,
65 pub size: usize,
66 pub align: usize,
67 pub structure: TypeStructure,
68}
69
70pub trait TypeLayout: Sized {
72 const TYPE_LAYOUT: TypeLayoutInfo;
73}
74
75inventory::collect!(TypeLayoutInfo);
76
77#[macro_export]
90macro_rules! register {
91 ($ty:ty) => {
92 $crate::inventory::submit! { <$ty as $crate::TypeLayout>::TYPE_LAYOUT }
93 };
94}
95
96pub fn all() -> Vec<&'static TypeLayoutInfo> {
98 let mut infos: Vec<&'static TypeLayoutInfo> = inventory::iter::<TypeLayoutInfo>.into_iter().collect();
99 infos.sort_by_key(|info| info.name);
100 infos
101}
102
103pub fn render() -> String {
105 let mut out = String::new();
106 for info in all() {
107 out.push_str(info.name);
108 out.push_str(&format!(": size={} align={}", info.size, info.align));
109 let fields = match info.structure {
110 TypeStructure::Struct { fields } | TypeStructure::Union { fields } => fields,
111 TypeStructure::Enum { variants } => {
112 for variant in variants {
113 match variant.discriminant {
114 Some(discriminant) => {
115 out.push_str(&format!("\n {}: {} = {}", variant.index, variant.name, discriminant))
116 }
117 None => out.push_str(&format!("\n {}: {}", variant.index, variant.name)),
118 }
119 }
120 &[]
121 }
122 };
123 if fields.is_empty() {
124 out.push('\n');
125 } else {
126 out.push_str(" {\n");
127 for field in fields {
128 out.push_str(&format!(" {}: {}\n", field.name, field.offset));
129 }
130 out.push_str("}\n");
131 }
132 }
133 out
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[derive(TypeLayout)]
141 #[repr(C)]
142 struct SmallFirst {
143 small: u8,
144 large: u64,
145 }
146
147 #[derive(TypeLayout)]
148 #[repr(C)]
149 struct LargeFirst {
150 large: u64,
151 small: u8,
152 }
153
154 #[derive(TypeLayout)]
155 #[repr(u8)]
156 enum Fieldless {
157 Zero,
158 Seven = 7,
159 Eight,
160 }
161
162 #[allow(dead_code)]
163 #[derive(TypeLayout)]
164 #[repr(C, u8)]
165 enum DataCarrying {
166 Unit,
167 Payload(u32),
168 }
169
170 #[derive(TypeLayout)]
171 #[repr(C)]
172 struct Tuple<'a>(&'a str, u8);
173
174 #[derive(TypeLayout)]
175 #[repr(C)]
176 struct Unit;
177
178 register!(Fieldless);
179 register!(Tuple<'static>);
180 register!(Unit);
181
182 fn fields_of(info: TypeLayoutInfo) -> &'static [Field] {
183 match info.structure {
184 TypeStructure::Struct { fields } | TypeStructure::Union { fields } => fields,
185 TypeStructure::Enum { .. } => panic!("not a struct"),
186 }
187 }
188
189 #[test]
190 fn reorder_changes_field_offsets() {
191 let small_first = fields_of(SmallFirst::TYPE_LAYOUT);
192 let large_first = fields_of(LargeFirst::TYPE_LAYOUT);
193 assert_eq!(small_first, &[Field { name: "small", offset: 0 }, Field { name: "large", offset: 8 }]);
194 assert_eq!(large_first, &[Field { name: "large", offset: 0 }, Field { name: "small", offset: 8 }]);
195 }
196
197 #[test]
198 fn records_explicit_and_implicit_discriminants() {
199 assert_eq!(
200 Fieldless::TYPE_LAYOUT.structure,
201 TypeStructure::Enum {
202 variants: &[
203 Variant { name: "Zero", index: 0, discriminant: Some(0) },
204 Variant { name: "Seven", index: 1, discriminant: Some(7) },
205 Variant { name: "Eight", index: 2, discriminant: Some(8) },
206 ]
207 }
208 );
209 }
210
211 #[test]
212 fn data_carrying_variants_pin_index_only() {
213 assert_eq!(
214 DataCarrying::TYPE_LAYOUT.structure,
215 TypeStructure::Enum {
216 variants: &[
217 Variant { name: "Unit", index: 0, discriminant: None },
218 Variant { name: "Payload", index: 1, discriminant: None },
219 ]
220 }
221 );
222 }
223
224 #[test]
225 fn tuple_fields_are_named_by_index() {
226 assert_eq!(fields_of(Tuple::TYPE_LAYOUT), &[Field { name: "0", offset: 0 }, Field { name: "1", offset: 16 }]);
227 }
228
229 #[test]
230 fn renders_every_registered_type() {
231 assert_eq!(
232 render(),
233 "Fieldless: size=1 align=1\n 0: Zero = 0\n 1: Seven = 7\n 2: Eight = 8\nTuple: size=24 align=8 {\n 0: 0\n 1: 16\n}\nUnit: size=0 align=1\n"
234 );
235 }
236}