Skip to content

Commit

Permalink
More rust doc
Browse files Browse the repository at this point in the history
  • Loading branch information
arnodb committed Nov 1, 2024
1 parent 312e58f commit 17d92b0
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 3 deletions.
46 changes: 46 additions & 0 deletions truc/src/record/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ pub struct DatumDefinitionOverride {
pub allow_uninit: Option<bool>,
}

/// 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<R>
where
R: TypeResolver,
Expand All @@ -261,6 +266,9 @@ impl<R> RecordDefinitionBuilder<R>
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(),
Expand All @@ -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<T, N>(&mut self, name: N) -> DatumId
where
N: Into<String>,
Expand All @@ -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<T, N>(&mut self, name: N) -> DatumId
where
T: Copy,
Expand All @@ -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<MyStruct>` then call it like this:
///
/// ```no_compile
/// add_datum_override::<Vec<()>, _>(
/// "my_vec",
/// DatumDefinitionOverride {
/// // Real type name
/// type_name: Some("Vec<MyStruct>".to_owned()),
/// // Same size
/// size: None,
/// // Same alignment rule
/// align: None,
/// // Same allow_uninit flag
/// allow_uninit: None,
/// },
/// )
/// ```
pub fn add_datum_override<T, N>(
&mut self,
name: N,
Expand Down Expand Up @@ -330,6 +365,7 @@ where
datum_id
}

/// Adds a new datum of dynamic type to the current variant.
pub fn add_dynamic_datum<T, N>(&mut self, name: N, r#type: T) -> DatumId
where
T: AsRef<str>,
Expand All @@ -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(),
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
40 changes: 40 additions & 0 deletions truc/src/record/type_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(&self) -> TypeInfo;

/// Gives the dynamic type information `type_name`.
fn dynamic_type_info(&self, type_name: &str) -> DynamicTypeInfo;
}

Expand All @@ -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<T>(&self) -> TypeInfo {
TypeInfo {
name: truc_type_name::<T>(),
Expand All @@ -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<String, DynamicTypeInfo>,
}

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<T>(&mut self) {
let type_name = truc_type_name::<T>();
match self.types.entry(type_name.clone()) {
Expand All @@ -87,6 +112,7 @@ impl StaticTypeResolver {
}
}

/// Adds a single `Copy` type information to the data.
pub fn add_type_allow_uninit<T>(&mut self)
where
T: Copy,
Expand All @@ -113,6 +139,14 @@ impl StaticTypeResolver {
}
}

/// Adds standard types to the data.
///
/// It includes:
///
/// * various types of integers and floating point numbers
/// * `String`
/// * `Box<str>`
/// * `Vec<()>` which is enough to support any kind of vector
pub fn add_std_types(&mut self) {
macro_rules! add_type {
($type:ty) => {
Expand Down Expand Up @@ -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::Value, serde_json::Error> {
serde_json::to_value(&self.types)
}

/// Serialization to a `String`.
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(&self.types)
}

/// Serialization to a `String` with pretty printing.
pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
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<T>(&self) -> TypeInfo {
let type_name = truc_type_name::<T>();
self.types
Expand All @@ -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
Expand Down
16 changes: 13 additions & 3 deletions truc_runtime/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
/// 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<T, U, C>(input: Vec<T>, convert: C) -> Vec<U>
where
C: Fn(T, Option<&mut U>) -> VecElementConversionResult<U> + std::panic::RefUnwindSafe,
Expand All @@ -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).
Expand Down
2 changes: 2 additions & 0 deletions truc_runtime/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::mem::MaybeUninit;

/// Internal data holder, heavily unsage, do not use it directly.
pub struct RecordMaybeUninit<const CAP: usize> {
data: [MaybeUninit<u8>; CAP],
}

impl<const CAP: usize> RecordMaybeUninit<CAP> {
/// Constructs an uninitialized record.
pub fn new() -> Self {
Self {
data: unsafe { std::mem::MaybeUninit::uninit().assume_init() },
Expand Down

0 comments on commit 17d92b0

Please sign in to comment.