bones_schema/schema.rs
1//! [`Schema`], [`HasSchema`], [`SchemaData`], and related types.
2
3use std::{alloc::Layout, any::TypeId, borrow::Cow, ffi::c_void};
4
5use ustr::Ustr;
6
7use crate::{alloc::TypeDatas, prelude::*};
8
9/// Trait implemented for types that have a [`Schema`].
10///
11/// # Safety
12///
13/// This trait is unsafe to implement manually because it makes claims about the memory layout of a
14/// type that may be depended on in unsafe code, but it is safe to derive [`HasSchema`] on supported
15/// types.
16///
17/// If implemented manually, you must ensure that the schema accurately describes the memory layout
18/// of the type, or else accessing the type according to the schema would be unsound.
19pub unsafe trait HasSchema: Sync + Send + 'static {
20 /// Get this type's [`Schema`].
21 #[must_use = "If you are only calling schema() for it's side-effect of registering the
22 schema, it is more clear to use register_schema() instead."]
23 fn schema() -> &'static Schema;
24
25 /// Register this schema with the global schema registry.
26 ///
27 /// This is automatically done by the framework in many cases, whenever
28 /// [`schema()`][HasSchema::schema] is called, but it may be necessary sometimes to
29 /// manually register it.
30 fn register_schema() {
31 let _ = Self::schema();
32 }
33
34 /// Cast a reference of this type to a reference of another type with the same memory layout.
35 ///
36 /// # Panics
37 ///
38 /// Panics if the schema of `T` doesn't match the schema of `Self`.
39 #[track_caller]
40 fn cast<T: HasSchema>(this: &Self) -> &T {
41 HasSchema::try_cast(this).expect(SchemaMismatchError::MSG)
42 }
43
44 /// Cast a reference of this type to a reference of another type with the same memory layout.
45 ///
46 /// # Errors
47 ///
48 /// Errors if the schema of `T` doesn't match the schema of `Self`.
49 fn try_cast<T: HasSchema>(this: &Self) -> Result<&T, SchemaMismatchError> {
50 let s1 = Self::schema();
51 let s2 = T::schema();
52 if s1.represents(s2) {
53 // SOUND: the schemas have the same memory representation.
54 unsafe { Ok(&*(this as *const Self as *const T)) }
55 } else {
56 Err(SchemaMismatchError)
57 }
58 }
59
60 /// Cast a mutable reference of this type to a reference of another type with the same memory
61 /// layout.
62 ///
63 /// # Panics
64 ///
65 /// Panics if the schema of `T` doesn't match the schema of `Self`.
66 #[track_caller]
67 fn cast_mut<T: HasSchema>(this: &mut Self) -> &mut T {
68 HasSchema::try_cast_mut(this).expect(SchemaMismatchError::MSG)
69 }
70
71 /// Cast a mutable reference of this type to a reference of another type with the same memory
72 /// layout.
73 ///
74 /// # Errors
75 ///
76 /// Errors if the schema of `T` doesn't match the schema of `Self`.
77 fn try_cast_mut<T: HasSchema>(this: &mut Self) -> Result<&mut T, SchemaMismatchError> {
78 let s1 = Self::schema();
79 let s2 = T::schema();
80 if s1.represents(s2) {
81 // SOUND: the schemas have the same memory representation.
82 unsafe { Ok(&mut *(this as *mut Self as *mut T)) }
83 } else {
84 Err(SchemaMismatchError)
85 }
86 }
87
88 /// Converts a reference of `T` to a [`SchemaRef`]
89 fn as_schema_ref(&self) -> SchemaRef<'_>
90 where
91 Self: Sized,
92 {
93 SchemaRef::new(self)
94 }
95
96 /// Converts a reference of `T` to a [`SchemaRefMut`]
97 fn as_schema_mut(&mut self) -> SchemaRefMut<'_>
98 where
99 Self: Sized,
100 {
101 SchemaRefMut::new(self)
102 }
103}
104
105// Export the `Schema` type so it appears in this module. It is defined in the registry module so
106// that the registry is the only module that is allowed to construct `Schema`s.
107#[doc(inline)]
108pub use crate::registry::Schema;
109
110impl Schema {
111 /// Returns whether or not this schema represents the same memory layout as the other schema,
112 /// and you can safely cast a pointer to one to a pointer to the other.
113 pub fn represents(&self, other: &Schema) -> bool {
114 // If these have equal type/schema ids.
115 self == other
116 // If the schemas don't have any opaque fields, and are equal to each-other, then they
117 // have the same representation.
118 || (!self.kind.has_opaque() && !other.kind.has_opaque() && {
119 match (&self.kind, &other.kind) {
120 (SchemaKind::Struct(s1), SchemaKind::Struct(s2)) => {
121 s1.fields.len() == s2.fields.len() &&
122 s1.fields.iter().zip(s2.fields.iter())
123 .all(|(f1, f2)| f1.schema.represents(f2.schema))
124 },
125 (SchemaKind::Vec(v1), SchemaKind::Vec(v2)) => v1.represents(v2),
126 (SchemaKind::Primitive(p1), SchemaKind::Primitive(p2)) => p1 == p2,
127 (SchemaKind::Enum(e1), SchemaKind::Enum(e2)) => e1 == e2,
128 _ => false
129 }
130 })
131 }
132}
133
134/// Schema information describing the memory layout of a type.
135#[derive(Clone)]
136pub struct SchemaData {
137 /// The short name of the type.
138 ///
139 /// **Note:** Currently bones isn't very standardized as far as name generation for Rust or
140 /// other language type names, and this is mostly for diagnostics. This may change in the future
141 /// but for now there are no guarantees.
142 pub name: Ustr,
143 /// The full name of the type, including any module specifiers.
144 ///
145 /// **Note:** Currently bones isn't very standardized as far as name generation for Rust or
146 /// other language type names, and this is mostly for diagnostics. This may change in the future
147 /// but for now there are no guarantees.
148 pub full_name: Ustr,
149 /// The kind of schema.
150 pub kind: SchemaKind,
151 /// Container for storing [`Schema`] type datas.
152 ///
153 /// "Type data" is extra data that is stored in a type's [`Schema`] that may be used for any number
154 /// of different purposes.
155 ///
156 /// Each type data is a type that implements [`HasSchema`] and usually describes something about the
157 /// type that has the schema. For instance, a type data could be added to a struct that can be used
158 /// to serialize/deserialize that type.
159 ///
160 /// If a type data also implements [`FromType`] it can be derived for types that it implements
161 /// [`FromType`] for:
162 ///
163 /// ```rust
164 /// # use bones_schema::prelude::*;
165 /// #[derive(HasSchema, Default, Clone)]
166 /// struct SomeTypeData;
167 ///
168 /// impl<T> FromType<T> for SomeTypeData {
169 /// fn from_type() -> Self {
170 /// SomeTypeData
171 /// }
172 /// }
173 ///
174 /// #[derive(HasSchema, Default, Clone)]
175 /// #[derive_type_data(SomeTypeData)]
176 /// struct MyData;
177 /// ```
178 pub type_data: TypeDatas,
179
180 // NOTE: The fields below could be implemented as type datas, and it would be nicely elegant to
181 // do so, but for performance reasons, we put them right in the [`Schema`] struct because
182 // they're use is so common. If profiling does not reveal any performance issues with using them
183 // as type datas, we may want to remove these fields in favor of the type data.
184 /// The Rust [`TypeId`] that this [`Schema`] was created from, if it was created from a Rust
185 /// type.
186 pub type_id: Option<TypeId>,
187 /// The function pointer that may be used to clone data with this schema.
188 pub clone_fn:
189 Option<Unsafe<&'static (dyn Fn(*const c_void, *mut c_void) + Sync + Send + 'static)>>,
190 /// The function pointer that may be used to drop data with this schema.
191 pub drop_fn: Option<Unsafe<&'static (dyn Fn(*mut c_void) + Sync + Send + 'static)>>,
192 /// The function pointer that may be used to write a default value to a pointer.
193 pub default_fn: Option<Unsafe<&'static (dyn Fn(*mut c_void) + Sync + Send + 'static)>>,
194 /// The function pointer that may be used to hash the value.
195 pub hash_fn: Option<Unsafe<&'static (dyn Fn(*const c_void) -> u64 + Sync + Send + 'static)>>,
196 /// The function pointer that may be used to compare two values for equality. Note that this is
197 /// total equality, not partial equality.
198 pub eq_fn: Option<
199 Unsafe<&'static (dyn Fn(*const c_void, *const c_void) -> bool + Sync + Send + 'static)>,
200 >,
201}
202
203impl std::fmt::Debug for SchemaData {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.debug_struct("SchemaData")
206 .field("name", &self.name)
207 .field("full_name", &self.full_name)
208 .field("kind", &self.kind)
209 .field("type_data", &self.type_data)
210 .field("type_id", &self.type_id)
211 .finish_non_exhaustive()
212 }
213}
214
215/// A wrapper struct that marks it unsafe to both create and access the inner value.
216#[derive(Clone, Debug)]
217pub struct Unsafe<T>(T);
218
219impl<T> Unsafe<T> {
220 /// Create a new [`Unsafe`] contianing the `value`.
221 /// # Safety
222 /// The safety invariants are dependent on the inner type.
223 pub unsafe fn new(value: T) -> Self {
224 Self(value)
225 }
226
227 /// Unsafely get the inner value.
228 /// # Safety
229 /// The safety invariants are dependent on the inner type.
230 pub unsafe fn get(&self) -> &T {
231 &self.0
232 }
233}
234
235/// A schema describes the data layout of a type, to enable dynamic access to the type's data
236/// through a pointer.
237#[derive(Debug, Clone)]
238pub enum SchemaKind {
239 /// The type represents a struct.
240 Struct(StructSchemaInfo),
241 /// Type represents a [`SchemaVec`], where each item in the vec has the contained [`Schema`].
242 ///
243 /// The scripting solution must facilitate a way for scripts to access data in the [`Vec`] if it
244 /// is to be readable/modifyable from scripts.
245 Vec(&'static Schema),
246 /// Type represents an enum, which in the C layout is called a tagged union.
247 Enum(EnumSchemaInfo),
248 /// Type represents a [`SchemaMap`].
249 Map {
250 /// The schema of the key type.
251 key: &'static Schema,
252 /// The schema of the value type.
253 value: &'static Schema,
254 },
255 /// The represents a [`SchemaBox`] with given type inside.
256 Box(&'static Schema),
257 /// The type represents a primitive value.
258 Primitive(Primitive),
259}
260
261impl SchemaKind {
262 /// Get the primitive, if this is a primitive.
263 pub fn as_primitive(&self) -> Option<&Primitive> {
264 if let Self::Primitive(p) = self {
265 Some(p)
266 } else {
267 None
268 }
269 }
270 /// Get the struct, if this is a struct.
271 pub fn as_struct(&self) -> Option<&StructSchemaInfo> {
272 if let Self::Struct(s) = self {
273 Some(s)
274 } else {
275 None
276 }
277 }
278 /// Get the enum, if this is a enum.
279 pub fn as_enum(&self) -> Option<&EnumSchemaInfo> {
280 if let Self::Enum(e) = self {
281 Some(e)
282 } else {
283 None
284 }
285 }
286 /// Get the schema of the items in the vector, if this is a vector.
287 pub fn as_vec(&self) -> Option<&'static Schema> {
288 if let Self::Vec(v) = self {
289 Some(v)
290 } else {
291 None
292 }
293 }
294}
295
296/// Layout information computed for [`SchemaData`].
297#[derive(Debug, Clone)]
298pub struct SchemaLayoutInfo<'a> {
299 /// The layout of the type.
300 pub layout: Layout,
301 /// If this is a struct, then the field offsets will contain the byte offset from the front of
302 /// the struct to each field in the order the fields are declared.
303 ///
304 /// If this is an enum, there will be exactly one entry specifying the offset from the beginning
305 /// of the struct to the enum value.
306 pub field_offsets: Vec<(Option<&'a str>, usize)>,
307}
308
309/// Schema data for a struct.
310#[derive(Debug, Clone)]
311pub struct StructSchemaInfo {
312 /// The fields in the struct, in the order they are defined.
313 pub fields: Vec<StructFieldInfo>,
314}
315
316/// Schema data for an enum.
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct EnumSchemaInfo {
319 /// The layout of the enum tag.
320 pub tag_type: EnumTagType,
321 /// Info for the enum variants.
322 pub variants: Vec<VariantInfo>,
323}
324
325/// A type for an enum tag for [`EnumSchemaInfo`].
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub enum EnumTagType {
328 /// A [`u8`].
329 U8,
330 /// A [`u16`].
331 U16,
332 /// A [`u32`].
333 U32,
334}
335
336impl EnumTagType {
337 /// Get the memory layout of the enum tag.
338 pub fn layout(&self) -> Layout {
339 match self {
340 EnumTagType::U8 => Layout::new::<u8>(),
341 EnumTagType::U16 => Layout::new::<u16>(),
342 EnumTagType::U32 => Layout::new::<u32>(),
343 }
344 }
345}
346
347/// Information about an enum variant for [`EnumSchemaInfo`].
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct VariantInfo {
350 /// The name of the enum variant.
351 pub name: Cow<'static, str>,
352 /// The schema of this variant.
353 pub schema: &'static Schema,
354}
355
356/// A field in a [`StructSchemaInfo`].
357#[derive(Debug, Clone)]
358pub struct StructFieldInfo {
359 /// The name of the field. Will be [`None`] if this is a field of a tuple struct.
360 pub name: Option<Ustr>,
361 /// The schema of the field.
362 pub schema: &'static Schema,
363 // TODO: Investigate adding attribute info to `StructFieldInfo`.
364 // It could be very useful if the derive macro could capture custom attribute data for struct
365 // fields and put it into the schema data. This would allow type data implementations that
366 // implement `FromType` to have access to custom attributes that could be used to modify various
367 // behavior, without requiring a new macro.
368}
369
370/// A type of primitive.
371#[derive(Debug, Clone, PartialEq, Eq, Hash)]
372pub enum Primitive {
373 /// A boolean.
374 Bool,
375 /// [`u8`]
376 U8,
377 /// [`u16`]
378 U16,
379 /// [`u32`]
380 U32,
381 /// [`u64`]
382 U64,
383 /// [`u128`]
384 U128,
385 /// [`i8`]
386 I8,
387 /// [`i16`]
388 I16,
389 /// [`i32`]
390 I32,
391 /// [`i64`]
392 I64,
393 /// [`i128`]
394 I128,
395 /// [`f32`]
396 F32,
397 /// [`f64`]
398 F64,
399 /// A Rust [`String`]. Must be manipulated with Rust string methods.
400 String,
401 /// Opaque data that cannot described by a schema.
402 Opaque {
403 /// The size of the data.
404 size: usize,
405 /// The alignment of the data.
406 align: usize,
407 },
408}
409
410/// Trait implemented for types that can produce an instance of themselves from a Rust type.
411///
412/// This is useful for [type datas][`SchemaData::type_data`], which may be derived for a type if the type data
413/// implements [`FromType`] for type that is deriving it.
414pub trait FromType<T> {
415 /// Return the data for the type.
416 fn from_type() -> Self;
417}
418
419impl SchemaKind {
420 /// Calculate the layout of the type represented by the schema.
421 ///
422 /// Usually you don't need to call this and should use the static, cached layout and field
423 /// offsets from [`Schema::layout()`] and [`Schema::field_offsets()`].
424 pub fn compute_layout_info(&self) -> SchemaLayoutInfo<'_> {
425 let mut layout: Option<Layout> = None;
426 let mut field_offsets = Vec::new();
427 let mut offset;
428
429 let extend_layout = |layout: &mut Option<Layout>, l| {
430 if let Some(layout) = layout {
431 let (new_layout, offset) = layout.extend(l).unwrap();
432 *layout = new_layout;
433 offset
434 } else {
435 *layout = Some(l);
436 0
437 }
438 };
439
440 match &self {
441 SchemaKind::Struct(s) => {
442 for field in &s.fields {
443 let field_layout_info = field.schema.kind.compute_layout_info();
444 offset = extend_layout(&mut layout, field_layout_info.layout);
445 field_offsets.push((field.name.as_deref(), offset));
446 }
447 }
448 SchemaKind::Vec(_) => {
449 extend_layout(&mut layout, Layout::new::<SchemaVec>());
450 }
451 SchemaKind::Box(_) => {
452 extend_layout(&mut layout, Layout::new::<SchemaBox>());
453 }
454 SchemaKind::Map { .. } => {
455 extend_layout(&mut layout, Layout::new::<SchemaMap>());
456 }
457 SchemaKind::Enum(e) => {
458 let enum_tag_layout = e.tag_type.layout();
459 extend_layout(&mut layout, enum_tag_layout);
460
461 let mut enum_value_layout = Layout::from_size_align(0, 1).unwrap();
462 // The enum layout is the greatest of it's variants sizes and aligments
463 for var in &e.variants {
464 let var_layout = var.schema.layout();
465 if var_layout.size() > enum_value_layout.size() {
466 enum_value_layout =
467 Layout::from_size_align(var_layout.size(), enum_value_layout.align())
468 .unwrap();
469 }
470 if var_layout.align() > enum_value_layout.align() {
471 enum_value_layout =
472 Layout::from_size_align(enum_value_layout.size(), var_layout.align())
473 .unwrap();
474 }
475 }
476 let offset = extend_layout(&mut layout, enum_value_layout);
477 // Enums have a single field offset that is the offset of the value part of the enum
478 // comming after the discriminant.
479 field_offsets.push((None, offset));
480 }
481 SchemaKind::Primitive(p) => {
482 extend_layout(
483 &mut layout,
484 match p {
485 Primitive::Bool => Layout::new::<bool>(),
486 Primitive::U8 => Layout::new::<u8>(),
487 Primitive::U16 => Layout::new::<u16>(),
488 Primitive::U32 => Layout::new::<u32>(),
489 Primitive::U64 => Layout::new::<u64>(),
490 Primitive::U128 => Layout::new::<u128>(),
491 Primitive::I8 => Layout::new::<i8>(),
492 Primitive::I16 => Layout::new::<i16>(),
493 Primitive::I32 => Layout::new::<i32>(),
494 Primitive::I64 => Layout::new::<i64>(),
495 Primitive::I128 => Layout::new::<i128>(),
496 Primitive::F32 => Layout::new::<f32>(),
497 Primitive::F64 => Layout::new::<f64>(),
498 Primitive::String => Layout::new::<String>(),
499 Primitive::Opaque { size, align } => {
500 Layout::from_size_align(*size, *align).unwrap()
501 }
502 },
503 );
504 }
505 }
506
507 SchemaLayoutInfo {
508 layout: layout
509 // Handle ZST
510 .unwrap_or_else(|| Layout::from_size_align(0, 1).unwrap())
511 .pad_to_align(),
512 field_offsets,
513 }
514 }
515
516 /// Recursively checks whether or not the schema contains any [`Opaque`][Primitive::Opaque] primitives.
517 pub fn has_opaque(&self) -> bool {
518 match &self {
519 SchemaKind::Struct(s) => s.fields.iter().any(|field| field.schema.kind.has_opaque()),
520 SchemaKind::Vec(v) => v.kind.has_opaque(),
521 SchemaKind::Box(b) => b.schema().kind.has_opaque(),
522 SchemaKind::Enum(e) => e.variants.iter().any(|var| var.schema.kind.has_opaque()),
523 SchemaKind::Map { key, value } => {
524 key.schema().kind.has_opaque() || value.schema().kind.has_opaque()
525 }
526 SchemaKind::Primitive(p) => matches!(p, Primitive::Opaque { .. }),
527 }
528 }
529}