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 4, 2024
1 parent 2b6f5c7 commit dbc1153
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 6 deletions.
2 changes: 1 addition & 1 deletion truc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.2.1-dev"
edition = "2021"
rust-version = "1.56.1"
license-file = "../LICENSE"
description = "Stuff"
description = "Rust code generator for safe, fixed size, evolving records."
documentation = "https://docs.rs/truc"
repository = "https://github.com/arnodb/truc"
readme = "../README.md"
Expand Down
12 changes: 12 additions & 0 deletions truc/src/generator/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Configuration of the code generation.
use super::fragment::{
data_records::DataRecordsGenerator, drop_impl::DropImplGenerator,
from_previous_record_data_records::FromPreviousRecordDataRecordsGenerator,
Expand All @@ -6,6 +8,7 @@ use super::fragment::{
record_impl::RecordImplGenerator, FragmentGenerator,
};

/// Main configuration entry point.
pub struct GeneratorConfig {
pub(crate) fragment_generators: Vec<Box<dyn FragmentGenerator>>,
}
Expand All @@ -23,12 +26,20 @@ impl GeneratorConfig {
]
}

/// Constructs a new configuration instance with only the specified fragment generators.
///
/// The common fragment generators are not included. This constructor is merely used for
/// testing purpose.
pub fn new(fragment_generators: impl IntoIterator<Item = Box<dyn FragmentGenerator>>) -> Self {
Self {
fragment_generators: fragment_generators.into_iter().collect(),
}
}

/// Constructs a new configuration instance with the common fragment generators included and
/// some additional custom generators like
/// [SerdeImplGenerator](super::fragment::serde::SerdeImplGenerator) to enable the serialization
/// features.
pub fn default_with_custom_generators(
custom_generators: impl IntoIterator<Item = Box<dyn FragmentGenerator>>,
) -> Self {
Expand All @@ -41,6 +52,7 @@ impl GeneratorConfig {
}

impl Default for GeneratorConfig {
/// Constructs a new configuration instance with only the common fragments generators.
fn default() -> Self {
Self::new(Self::common_fragment_generators())
}
Expand Down
2 changes: 2 additions & 0 deletions truc/src/generator/fragment/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! See [GeneratorConfig](super::config::GeneratorConfig) to customize the code generation.
use codegen::Scope;

use crate::record::definition::{DatumDefinition, RecordVariant};
Expand Down
4 changes: 4 additions & 0 deletions truc/src/generator/fragment/serde.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//! Serialization features.
use codegen::{Function, Scope};
use itertools::Itertools;

use super::{FragmentGenerator, FragmentGeneratorSpecs, RecordSpec};
use crate::generator::{CAP, CAP_GENERIC};

/// Use this generator in [GeneratorConfig](crate::generator::config::GeneratorConfig) in order to
/// enable serialization features on generated structures.
pub struct SerdeImplGenerator;

impl SerdeImplGenerator {
Expand Down
8 changes: 7 additions & 1 deletion truc/src/generator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! See [GeneratorConfig] to customize the code generation.
use std::collections::BTreeSet;

use codegen::{Scope, Type};
Expand All @@ -15,6 +17,7 @@ pub mod fragment;
const CAP_GENERIC: &str = "const CAP: usize";
const CAP: &str = "CAP";

/// Generates the code for the given record definition.
pub fn generate(definition: &RecordDefinition, config: &GeneratorConfig) -> String {
let mut scope = Scope::new();

Expand Down Expand Up @@ -80,7 +83,10 @@ This is to be used in custom allocators."#,
scope.to_string()
}

pub(crate) fn generate_variant<'a>(
/// Generates the code for a given record variant.
///
/// This function is exposed for testing purpose.
pub fn generate_variant<'a>(
definition: &'a RecordDefinition,
max_type_align: usize,
variant: &'a RecordVariant,
Expand Down
179 changes: 179 additions & 0 deletions truc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,182 @@
//! Rust code generator for safe, fixed size, evolving records.
//!
//! ## Objectives
//!
//! The objectives of this crate are:
//!
//! * define fixed size Rust structures to represent records of data
//! * define strongly typed accessors to data
//! * allow record evolution: data replacement by other data of different type
//! * optimize memory copies when records are evolving
//! * entirely safe memory management, even on panic
//!
//! Whether these objectives are met or not is still to be proven.
//!
//! ## Why and how
//!
//! ### Fixed size
//!
//! Having fixed size records aims at optimizing allocations: it is easy to manage memory when millions of objects have all the same size.
//!
//! This is achieved by leveraging the const generic Rust feature: the size of records is statically known.
//!
//! ### Strong typing
//!
//! Strong typing is a very interesting aspect of the Rust language. The only thing a developer has to do to have strong typing is to use types. _Truc_ is made to allow the use of as many types as possible, even user-defined types. There may be restrictions, especially on lifetimes, but any owned type can be used.
//!
//! ### Record evolution
//!
//! It is often nice to make a record evolve, keeping only one invariant: its size.
//!
//! This is achieved by defining record variants and ways to:
//!
//! * destructure the data
//! * convert from one variant to the next one
//! * convert a list of records of one variant to a list of records of another variant
//!
//! There is no direct way to convert from one variant to any arbitrary other variant (because use cases have to be defined), but the language allows almost anything provided it compiles.
//!
//! ### Memory copies
//!
//! Memory copies are expensive, one should try to avoid them as much as possible.
//!
//! A datum in a record is always stored at the same location in that record. If it's not at the same location then it is not the same datum. With the help of the compiler, memory copies should be optimized.
//!
//! To be proven:
//!
//! * Can the compiler optimize the conversion from one variant to another? That sounds optimistic, but one can be surprised by the level of optimization of LLVM.
//! * Does it work on both the heap and the stack?
//!
//! ### Safe memory management
//!
//! Code generated by _Truc_ is made to be entirely safe. It is built on top of some heavily `unsafe` calls but the API only exposes safe functions:
//!
//! * the types size and alignment is respected (otherwise the code would panic)
//! * everything held by a record is safely managed: dropping a record drops the data it holds
//! * the vector conversion is safe even if the conversion panics in the middle of the loop: old data and new data is dropped according to the type system rules, no value is missed
//!
//! ## Additional cool features
//!
//! * cross-compilation enabled
//!
//! ## Getting started
//!
//! ### Project organization
//!
//! Usually a project is organized this way:
//!
//! * generation of the record definitions in `build.rs`
//! * import of the generated definitions in a module of the project
//! * project implementation
//!
//! Example `Cargo.toml`:
//!
//! ```toml
//! [package]
//! name = "readme"
//! version = "0.1.0"
//! edition = "2021"
//!
//! [dependencies]
//! static_assertions = "1"
//! truc_runtime = { git = "https://github.com/arnodb/truc.git" }
//!
//! [build-dependencies]
//! truc = { git = "https://github.com/arnodb/truc.git" }
//! ```
//!
//! ### Record definitions
//!
//! First of all you need a type resolver. If you are not cross-compiling then
//! [HostTypeResolver](crate::record::type_resolver::HostTypeResolver) will work in most cases.
//!
//! Then the definitions are built with
//! [RecordDefinitionBuilder](crate::record::definition::RecordDefinitionBuilder).
//!
//! Once you have your definitions set up, you just need to generate the Rust definitions to an output file.
//!
//! Example `build.rs`:
//!
//! ```rust,no_run
//! use std::{env, fs::File, io::Write, path::PathBuf};
//!
//! use truc::{
//! generator::{config::GeneratorConfig, generate},
//! record::{definition::RecordDefinitionBuilder, type_resolver::HostTypeResolver},
//! };
//!
//! fn main() {
//! let mut definition = RecordDefinitionBuilder::new(&HostTypeResolver);
//!
//! // First variant with an integer
//! let integer_id = definition.add_datum_allow_uninit::<usize, _>("integer");
//! definition.close_record_variant();
//!
//! // Second variant with a string
//! let string_id = definition.add_datum::<String, _>("string");
//! definition.remove_datum(integer_id);
//! definition.close_record_variant();
//!
//! // Remove the integer and replace it with another
//! definition.add_datum_allow_uninit::<isize, _>("signed_integer");
//! definition.remove_datum(string_id);
//! definition.close_record_variant();
//!
//! // Build
//! let definition = definition.build();
//!
//! // Generate Rust definitions
//! let out_dir = env::var("OUT_DIR").expect("OUT_DIR");
//! let out_dir_path = PathBuf::from(out_dir);
//! let mut file = File::create(out_dir_path.join("readme_truc.rs")).unwrap();
//! write!(
//! file,
//! "{}",
//! generate(&definition, &GeneratorConfig::default())
//! )
//! .unwrap();
//! }
//! ```
//!
//! ### Project implementation
//!
//! ```text
//! #[macro_use]
//! extern crate static_assertions;
//!
//! #[allow(dead_code)]
//! #[allow(clippy::borrowed_box)]
//! #[allow(clippy::module_inception)]
//! mod truc {
//! include!(concat!(env!("OUT_DIR"), "/readme_truc.rs"));
//! }
//!
//! fn main() {
//! use crate::truc::*;
//!
//! for record_2 in (0..42)
//! .into_iter()
//! .map(|integer| Record0::new(UnpackedRecord0 { integer }))
//! .map(|mut record_0| {
//! (*record_0.integer_mut()) *= 2;
//! record_0
//! })
//! .map(|record_0| {
//! let string = record_0.integer().to_string();
//! Record1::from((record_0, UnpackedRecordIn1 { string }))
//! })
//! .map(|record_1| {
//! let UnpackedRecord1 { string } = record_1.unpack();
//! Record2::new(UnpackedRecord2 {
//! signed_integer: string.parse().unwrap(),
//! })
//! })
//! {
//! println!("{}", record_2.signed_integer());
//! }
//! }
//! ```
#![cfg_attr(test, allow(clippy::many_single_char_names))]
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]

Expand Down
13 changes: 10 additions & 3 deletions truc/src/record/definition.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Record related structures.
use std::{
fmt::{Debug, Display, Formatter},
ops::Index,
Expand Down Expand Up @@ -320,8 +322,13 @@ where
///
/// For example if you want to add a datum of type `Vec<MyStruct>` then call it like this:
///
/// ```no_compile
/// add_datum_override::<Vec<()>, _>(
/// ```rust
/// # use truc::record::definition::{DatumDefinitionOverride, RecordDefinitionBuilder};
/// # use truc::record::type_resolver::HostTypeResolver;
/// #
/// # let mut builder = RecordDefinitionBuilder::new(HostTypeResolver);
/// #
/// builder.add_datum_override::<Vec<()>, _>(
/// "my_vec",
/// DatumDefinitionOverride {
/// // Real type name
Expand All @@ -333,7 +340,7 @@ where
/// // Same allow_uninit flag
/// allow_uninit: None,
/// },
/// )
/// );
/// ```
pub fn add_datum_override<T, N>(
&mut self,
Expand Down
2 changes: 2 additions & 0 deletions truc/src/record/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Record related structures and tools.
pub mod definition;
mod type_name;
pub mod type_resolver;
2 changes: 2 additions & 0 deletions truc/src/record/type_resolver.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Type resolution tools.
use std::collections::{btree_map::Entry, BTreeMap};

use serde::{Deserialize, Serialize};
Expand Down
2 changes: 1 addition & 1 deletion truc_runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.2.1-dev"
edition = "2021"
rust-version = "1.56.1"
license-file = "../LICENSE"
description = "Stuff"
description = "Rust code generator for safe, fixed size, evolving records - runtime."
documentation = "https://docs.rs/truc_runtime"
repository = "https://github.com/arnodb/truc"
readme = "../README.md"
Expand Down

0 comments on commit dbc1153

Please sign in to comment.