From 17d92b051bed2c9614b313e077e8b74ba5fed80d Mon Sep 17 00:00:00 2001 From: Arnaud de Bossoreille Date: Fri, 1 Nov 2024 23:07:26 +0100 Subject: [PATCH] More rust doc --- truc/src/record/definition.rs | 46 ++++++++++++++++++++++++++++++++ truc/src/record/type_resolver.rs | 40 +++++++++++++++++++++++++++ truc_runtime/src/convert.rs | 16 ++++++++--- truc_runtime/src/data.rs | 2 ++ 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/truc/src/record/definition.rs b/truc/src/record/definition.rs index 317e87a..89c3384 100644 --- a/truc/src/record/definition.rs +++ b/truc/src/record/definition.rs @@ -246,6 +246,11 @@ pub struct DatumDefinitionOverride { pub allow_uninit: Option, } +/// Main structure to start building record definitions. +/// +/// It needs a type resolver (see +/// [type_resolver](crate::record::type_resolver) module and especially +/// [HostTypeResolver](crate::record::type_resolver::HostTypeResolver)). pub struct RecordDefinitionBuilder where R: TypeResolver, @@ -261,6 +266,9 @@ impl RecordDefinitionBuilder where R: TypeResolver, { + /// Creates a new builder with a type resolver (see + /// [type_resolver](crate::record::type_resolver) module and especially + /// [HostTypeResolver](crate::record::type_resolver::HostTypeResolver)). pub fn new(type_resolver: R) -> Self { Self { datum_definitions: DatumDefinitionCollection::default(), @@ -271,6 +279,10 @@ where } } + /// Adds a new datum of type `T` to the current variant. + /// + /// `T` does not need to be `Copy`, but if it is then consider using + /// [add_datum_allow_uninit](Self::add_datum_allow_uninit) instead. pub fn add_datum(&mut self, name: N) -> DatumId where N: Into, @@ -285,6 +297,10 @@ where datum_id } + /// Adds a new datum of type `T: Copy` to the current variant. + /// + /// `T` needs to be `Copy` to allow uninitialized values, if it is not `Copy` then consider + /// using [add_datum](Self::add_datum) instead. pub fn add_datum_allow_uninit(&mut self, name: N) -> DatumId where T: Copy, @@ -300,6 +316,25 @@ where datum_id } + /// Adds a new datum to the current variant when type information of `T` needs to be overridden. + /// + /// For example if you want to add a datum of type `Vec` then call it like this: + /// + /// ```no_compile + /// add_datum_override::, _>( + /// "my_vec", + /// DatumDefinitionOverride { + /// // Real type name + /// type_name: Some("Vec".to_owned()), + /// // Same size + /// size: None, + /// // Same alignment rule + /// align: None, + /// // Same allow_uninit flag + /// allow_uninit: None, + /// }, + /// ) + /// ``` pub fn add_datum_override( &mut self, name: N, @@ -330,6 +365,7 @@ where datum_id } + /// Adds a new datum of dynamic type to the current variant. pub fn add_dynamic_datum(&mut self, name: N, r#type: T) -> DatumId where T: AsRef, @@ -346,6 +382,7 @@ where datum_id } + /// Adds a new datum by copying an existing definition. pub fn copy_datum(&mut self, datum: &DatumDefinition) -> DatumId { let datum_id = self.datum_definitions.push( datum.name().into(), @@ -357,6 +394,9 @@ where datum_id } + /// Remove a datum from the current variant. + /// + /// It panics if the operation cannot be performed or is already performed. pub fn remove_datum(&mut self, datum_id: DatumId) { if let Some(variant) = self.variants.last() { let index = variant.data.iter().position(|&did| did == datum_id); @@ -394,6 +434,7 @@ where self.variants.is_empty() || !self.data_to_remove.is_empty() || !self.data_to_add.is_empty() } + /// Closes the current record variant and allows starting a new one. pub fn close_record_variant(&mut self) -> RecordVariantId { if !self.has_pending_changes() { return (self.variants.len() - 1).into(); @@ -464,14 +505,17 @@ where variant_id } + /// Accesses datum definitions by ID. pub fn get_datum_definition(&self, id: DatumId) -> Option<&DatumDefinition> { self.datum_definitions.get(id) } + /// Accesses variant definitions by ID. pub fn get_variant(&self, id: RecordVariantId) -> Option<&RecordVariant> { self.variants.get(id.0) } + /// Accesses datum definitions by variant ID and datum name. pub fn get_variant_datum_definition_by_name( &self, variant_id: RecordVariantId, @@ -506,12 +550,14 @@ where .chain(self.data_to_add.iter().cloned()) } + /// Accesses datum definitions of the current variant by datum name. pub fn get_current_datum_definition_by_name(&self, name: &str) -> Option<&DatumDefinition> { self.get_current_data() .filter_map(|d| self.datum_definitions.get(d)) .find(|datum| datum.name() == name) } + /// Wraps up everything into a [RecordDefinition]. pub fn build(mut self) -> RecordDefinition { if !self.data_to_add.is_empty() || !self.data_to_remove.is_empty() { self.close_record_variant(); diff --git a/truc/src/record/type_resolver.rs b/truc/src/record/type_resolver.rs index 78f05ef..668b20c 100644 --- a/truc/src/record/type_resolver.rs +++ b/truc/src/record/type_resolver.rs @@ -4,22 +4,33 @@ use serde::{Deserialize, Serialize}; use crate::record::type_name::{truc_dynamic_type_name, truc_type_name}; +/// Type information (name, size and align) as given by the Rust compiler. #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct TypeInfo { + /// The type name as it can be used in Rust code. pub name: String, + /// The type size given by `std::mem::size_of()`. pub size: usize, + /// The type size given by `std::mem::align_of()`. pub align: usize, } +/// Additional type information. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DynamicTypeInfo { + /// Rust type information. pub info: TypeInfo, + /// Indicates whether or not the type can be left safely uninitialized. Merely `Copy` types + /// can be left uninitialized, any other type cannot. pub allow_uninit: bool, } +/// Abstract type resolver trait. pub trait TypeResolver { + /// Gives the Rust type information for `T`. fn type_info(&self) -> TypeInfo; + /// Gives the dynamic type information `type_name`. fn dynamic_type_info(&self, type_name: &str) -> DynamicTypeInfo; } @@ -36,9 +47,12 @@ where } } +/// A type resolver that can give Rust type information only. Any call to +/// [dynamic_type_info](HostTypeResolver::dynamic_type_info) will panic. pub struct HostTypeResolver; impl TypeResolver for HostTypeResolver { + /// Resolves the Rust type information by calling `std::mem::size_of()` and `std::mem::align_of()`. fn type_info(&self) -> TypeInfo { TypeInfo { name: truc_type_name::(), @@ -47,23 +61,34 @@ impl TypeResolver for HostTypeResolver { } } + /// Panics! fn dynamic_type_info(&self, _type_name: &str) -> DynamicTypeInfo { unimplemented!("HostTypeResolver cannot resolve dynamic types") } } +/// A type resolved that loads precomputed type information. +/// +/// In addition to allowing a good level of customization, it is also very useful for +/// cross-compilation: +/// +/// * compute data by running the resolution on the target platform +/// * serialize the data with `serde` to a file +/// * deserialize the file in the project to be cross-compiled #[derive(Debug, From, Serialize, Deserialize)] pub struct StaticTypeResolver { types: BTreeMap, } impl StaticTypeResolver { + /// Creates an empty resolver. pub fn new() -> Self { Self { types: BTreeMap::new(), } } + /// Adds a single type information to the data. pub fn add_type(&mut self) { let type_name = truc_type_name::(); match self.types.entry(type_name.clone()) { @@ -87,6 +112,7 @@ impl StaticTypeResolver { } } + /// Adds a single `Copy` type information to the data. pub fn add_type_allow_uninit(&mut self) where T: Copy, @@ -113,6 +139,14 @@ impl StaticTypeResolver { } } + /// Adds standard types to the data. + /// + /// It includes: + /// + /// * various types of integers and floating point numbers + /// * `String` + /// * `Box` + /// * `Vec<()>` which is enough to support any kind of vector pub fn add_std_types(&mut self) { macro_rules! add_type { ($type:ty) => { @@ -179,26 +213,31 @@ impl StaticTypeResolver { add_type_and_arrays!(Vec<()>); } + /// Serialization to a `serde_json::Value`. pub fn to_json_value(&self) -> Result { serde_json::to_value(&self.types) } + /// Serialization to a `String`. pub fn to_json_string(&self) -> Result { serde_json::to_string(&self.types) } + /// Serialization to a `String` with pretty printing. pub fn to_json_string_pretty(&self) -> Result { serde_json::to_string_pretty(&self.types) } } impl Default for StaticTypeResolver { + /// Creates an empty resolver. fn default() -> Self { Self::new() } } impl TypeResolver for StaticTypeResolver { + /// Gives the Rust type information for `T` by looking up loaded data. fn type_info(&self) -> TypeInfo { let type_name = truc_type_name::(); self.types @@ -208,6 +247,7 @@ impl TypeResolver for StaticTypeResolver { .clone() } + /// Gives the dynamic type information for `type_name` by looking up data. fn dynamic_type_info(&self, type_name: &str) -> DynamicTypeInfo { let type_name = truc_dynamic_type_name(type_name); self.types diff --git a/truc_runtime/src/convert.rs b/truc_runtime/src/convert.rs index ba5bbd8..f92cd31 100644 --- a/truc_runtime/src/convert.rs +++ b/truc_runtime/src/convert.rs @@ -3,11 +3,22 @@ use std::{ mem::{ManuallyDrop, MaybeUninit}, }; +/// The result of a record conversion in a call to [convert_vec_in_place]. pub enum VecElementConversionResult { + /// The record has been converted to the attached value. Converted(T), + /// The record has been abandonned. Abandonned, } +/// Converts a vector of `T` to a vector of `U` where `T` and `U` have the same size in memory and +/// the same alignment rule according to the Rust compiler. +/// +/// If the provided converter panics then your memory is safe: no invalid access is performed, +/// values that need to be dropped are dropped. +/// +/// Note: the 2 required conditions are checked at runtime. However it is reasonably expected that +/// those runtime checks are optimized statically by the compiler: NOOP or pure panic. pub fn convert_vec_in_place(input: Vec, convert: C) -> Vec where C: Fn(T, Option<&mut U>) -> VecElementConversionResult + std::panic::RefUnwindSafe, @@ -16,9 +27,8 @@ where // invariant but this would have two drawbacks: // // - you have to trust the implementations of the trait - // - this would prevent from allowing - // conversions from any type T to any other type U where they both have the same memory - // layout + // - this would prevent from allowing conversions from any type T to any other type U where + // they both have the same memory layout // // Side note: those runtime assertions are optimised statically: either code without // assertion code (the happy path), or pure panic (the incorrect path). diff --git a/truc_runtime/src/data.rs b/truc_runtime/src/data.rs index a51c739..7f617bb 100644 --- a/truc_runtime/src/data.rs +++ b/truc_runtime/src/data.rs @@ -1,10 +1,12 @@ use std::mem::MaybeUninit; +/// Internal data holder, heavily unsage, do not use it directly. pub struct RecordMaybeUninit { data: [MaybeUninit; CAP], } impl RecordMaybeUninit { + /// Constructs an uninitialized record. pub fn new() -> Self { Self { data: unsafe { std::mem::MaybeUninit::uninit().assume_init() },