Compile-time checked Builder pattern derive macro with zero-memory overhead

Overview

Compile-time checked Builder pattern derive macro with zero-memory overhead

This is very much a work-in-progress. PRs welcome to bring this to production quality welcome.

The Builder Pattern is a design pattern to allow the construction of complex types one field at a time by calling methods on a builder type. This crate provides a derive macro that allows you to annotate any struct to create a type-level state machine that requires all mandatory fields to be set once and only at compile-time (otherwise you won't be able to call .build() on it).

use makeit::{Buildable, Builder};

#[derive(Builder)]
struct Foo<'a, T: std::fmt::Debug> {
    bar: &'a T,
    #[default(42)]
    baz: i32,
}

// This is the expected use
let x = Foo::builder().set_bar(&()).set_baz(42).build();
// The following builds because of the `default` annotation on `baz`
let x = Foo::builder().set_bar(&()).build();
// The following won't build because `bar` hasn't been set
let x = Foo::builder().set_baz(0).build();

You can look at the examples directory for a showcase of the available features.

The created Builder type has zero-memory overhead because it uses a MaybeUninit backed field of the type to be built.

Error messages

One of the main downsides of the typestate pattern are inscrutable error messages. However, the since this crate generates type-state parameters with readable names, rustc can produce readable error messages: image

Comments
  • Do not emit the parentheses wrapping the default value with `#[default(...)]`

    Do not emit the parentheses wrapping the default value with `#[default(...)]`

    This fixes the compiler suggestions; see rust-lang/rust#94074.

    Technically this is a breaking change - previously, because of the parentheses, you could actually pass a tuple as the default value without wrapping it in parentheses - e.g. #[default(1, 1)]. Now you need to do #[default((1, 1))]. However, I don't think someone relied on this, as it looks like the wrong behavior.

    opened by ChayimFriedman2 1
  • Fix `r#` fields not working

    Fix `r#` fields not working

    Fields with r# prefixed didn't work due to the capitalize function not handling the prefix. Eg:

    #[derive(Buildable)]
    struct Foo {
      r#type: String,
    }
    

    This PR fixes the issue by stripping the r# when capitalizing the ident.

    I suggest merging this as a squashed merge combining the commits into one.

    opened by tqwewe 0
  • Fields should not require `: Default` when a default is given through `#[default(value)]`

    Fields should not require `: Default` when a default is given through `#[default(value)]`

    Given the following code:

    #[derive(Builder)]
    struct MyStruct {
        #[default(Foo(0)]
        one: Foo,
    }
    
    struct Foo(i32);
    

    The code fails since Foo doesn't implement Default. But this should not be the case since I'm providing a default through the attribute, and adding the where Foo: Default is uncecessary at that point.

    where
      Foo: Default
    

    Should only be applied if #[default] is given without a value.

    opened by tqwewe 0
  • Update README to include example error messages

    Update README to include example error messages

    The first thing I tried to find was the error messages, ended up finding them in the tweet. Copied the image into the README and brought the disclaimer to the top so it doesn't get lost as the README gets longer

    opened by rcoh 0
  • `Option<T>` methods should take `T` directly

    `Option` methods should take `T` directly

    I'm using makeit heavily with props for a web framework, and currently when a prop is an Option<T>, you have to write <MyComponent prop={Some(val)} />, but it would be much more desireable in my case to do <MyComponent prop={val} />.

    Could the set_ methods generated for fields of type Option<T> take T directly rather than the option itself?

    If needed, another method set_none could be generated for options which would set it to None.

    opened by tqwewe 0
  • The way ptr_XX and inner_set_XX are implemented is UB

    The way ptr_XX and inner_set_XX are implemented is UB

    For example

    pub unsafe fn ptr_0(&mut self) -> *mut String {
        let inner = self.inner.as_mut_ptr();
        &raw mut (*inner).a
    }
    

    A reference in Rust gurantees that the memory is initialized. You need to use addr_of_mut to correctly model this.


    Somewhat unrleated but I thought I'd mention it.

    let ptr =
        &self as *const OrangeBuilder<AUnset, FieldB> as
            *const OrangeBuilder<ASet, FieldB>;
    ::core::mem::forget(self);
    unsafe { ptr.read() }
    

    That looks a lot like transmute. Is there a specific reason not to use transmute here? Also this is UB too, the docs for mem::forget

    Any resources the value manages, such as heap memory or a file handle, will linger forever in an unreachable state. However, it does not guarantee that pointers to this memory will remain valid.

    opened by Voultapher 1
  • Library leaks memory if initializing field panics

    Library leaks memory if initializing field panics

    #[derive(Builder, Debug)]
    struct Orange {
        a: String,
        b: String,
    }
     
    fn main() {
        let orange = Orange::builder()
            .set_a("xxx".into())
            .set_b(panic!())
            .build();
     
        dbg!(orange);
    }
    
    cargo +nightly miri run
     
    The following memory was leaked: alloc1014 (Rust heap, size: 3, align: 1) {
        78 78 78                                        │ xxx
    }
    

    This can be addressed by using a scope guard.

    opened by Voultapher 0
  • Expose the builder type publicly

    Expose the builder type publicly

    It looks like the generated code puts everything into a private module named after the builder type. For example, looking at the sample code from the README:

    use makeit::Builder;
    
    #[derive(Builder)]
    struct Foo<'a, T: std::fmt::Debug> {
        bar: &'a T,
        #[default(42)]
        baz: i32,
    }
    

    This produces an expansion like

    mod FooBuilderFields {
        pub struct FooBuilder<…> { … }
        pub struct BarSet;
        pub struct BazSet;
        pub struct BarUnset;
        pub struct BazUnset;
        // impls …
    }
    

    And the generated documentation does not include the FooBuilder type:

    screenshot of the generated documentation for Foo, in which the <Foo as Buildable>::Builder type is listed as FooBuilder but with no link to see the documentation for FooBuilder

    If I know the module name I can write pub use FooBuilderFields::FooBuilder, but it feels like I shouldn't have to do that. The builder should just be available by default.

    I can imagine a scenario in which someone might want to move the builder into a separate module, like pub mod builder { pub use super::FooBuilderFields::FooBuilder; } but that is a bit of an edge case and should require opt-in behavior.

    Proposed behavior

    The macro expansion should simply declare the FooBuilder type in the current module instead of inside the FooBuilderFields module, and it should mark it as pub if the decorated type itself is pub. Also see #7 in which I argue that the Buildable type should be removed and propose a #[builder(…)] attribute for renaming the builder() method and FooBuilder type as needed.

    If I have an edge case where I want Foo to be public but I want the FooBuilder type to be moved into a different module, I can deal with that using existing visibility rules by writing something like:

    mod foo {
        #[derive(makeit::Builder)]
        pub struct Foo<'a, T: std::fmt::Debug> {
            bar: &'a T,
            #[default(42)]
            baz: i32,
        }
    }
    pub use foo::Foo;
    pub mod builder {
        pub use super::foo::FooBuilder;
    }
    

    I don't see any reason to make the BarSet/BarUnset/… types public by default so they can stay as they are.

    opened by lilyball 3
  • `Buildable` trait should be removed

    `Buildable` trait should be removed

    I love the idea of this crate for writing builder patterns, but I want the use of this crate to be transparent to end-users. The comments on the Buildable trait say it exists to handle the edge case of the type already having a builder() method. I don't really understand why this is a problem; if the type already has one, why is it using makeit? Builder isn't something that can be applied to an existing type either, it's something used on a type as it's being defined. Instead I would simply prefer an optional #[builder(method = "builder")] attribute that lets me rename the builder method. This same attribute could also be used to rename the builder type (and wrapper module) as well.

    I suppose today I can implement an inherent builder() method that invokes the trait method, but that's a bit awkward and shouldn't be necessary just to let clients of my type call its builder.

    opened by lilyball 1
Owner
Esteban Kuber
Off-by-one error fixer/introducer. I 💖@rust-lang⚙️ PST tz
Esteban Kuber
A comprehensive memory scanning library

scanflow boasts a feature set similar to the likes of CheatEngine, with a simple command line interface. Utilizing memflow, scanflow works in a wide range of situations - from virtual machines, to dedicated DMA hardware.

memflow 38 Dec 30, 2022
MiniDump a process in memory with rust

safetydump Rust in-memory MiniDump implementation. Features ntdll!NtGetNextProcess to obtain a handle for the desired ProcessId as opposed to kernel32

null 26 Oct 11, 2022
In-memory, non stateful and session based code sharing application.

interviewer In-memory, non stateful and session based code sharing application. Test it here: interviewer.taras.lol Note: it's deployed to render auto

2pac 7 Aug 16, 2021
A cross-platform and safe Rust API to create and manage memory mappings in the virtual address space of the calling process.

mmap-rs A cross-platform and safe Rust API to create and manage memory mappings in the virtual address space of the calling process. This crate can be

S.J.R. van Schaik 19 Oct 23, 2022
Cross-platform library for reading/writing memory in other processes for Rust

vmemory Rust library for reading/writing memory in other processes for Windows, macOS, Linux, and in the future potentially, BSD variants. Rationale A

Jason Johnson 26 Nov 7, 2022
Rust library to interract with memory written in rust

memory-rs Rust library to interract with memory written in rust It comes with: Pattern scanner (Return address for a pattern given). A pattern example

Alex 1 Jan 13, 2022
A memory efficient syntax tree for language developers

This crate provides a tree structure which always is contiguously stored and manipulated in memory. It provides similar APIs as rowan and is intended to be an efficient replacement for it (read more below).

John-John Tedro 21 Dec 15, 2022
bevy_datasize is a library for tracking memory usage in Bevy apps.

bevy_datasize bevy_datasize is a library for tracking memory usage in Bevy apps. bevy_datasize uses the DataSize trait from the datasize crate to esti

Ben Reeves 4 Mar 8, 2022
Memory hacking library for windows

Memory hacking library for windows

Sara Wahib 4 Apr 11, 2022
tidy-builder is a builder generator that is compile-time correct.

The Builder derive macro creates a compile-time correct builder which means that it only allows you to build the given struct if and only if you provi

M.Amin Rayej 7 Dec 18, 2022
A proc macro for creating compile-time checked CSS class sets, in the style of classNames

semester Semester is a declarative CSS conditional class name joiner, in the style of React's classnames. It's intended for use in web frameworks (lik

Nathan West 11 Oct 20, 2022
🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, SQLite, and MSSQL.

SQLx ?? The Rust SQL Toolkit Install | Usage | Docs Built with ❤️ by The LaunchBadge team SQLx is an async, pure Rust† SQL crate featuring compile-tim

launchbadge 7.6k Dec 31, 2022
A query builder that builds and typechecks queries at compile time

typed-qb: a compile-time typed "query builder" typed-qb is a compile-time, typed, query builder. The goal of this crate is to explore the gap between

ferrouille 3 Jan 22, 2022
Simple rust asset handling derive macro for enums, and a proc-macro learning resource!

asset-derive Summary • Todos • Docs Summary Simple Rust asset loading derive macro for Enums, and a resource for learning proc-macros! Please feel fre

Shadorain 5 Feb 9, 2023
mail-builder is a flexible e-mail builder library written in Rust that generates RFC5322 compliant e-mail messages

mail-builder mail-builder is a flexible e-mail builder library written in Rust that generates RFC5322 compliant e-mail messages. The library has full

Stalwart Labs 37 Dec 19, 2022
Rust library to convert RGB 24-bit colors into ANSI 256 (8-bit) color codes with zero dependencies and at compile-time.

rgb2ansi256 rgb2ansi256 is a small Rust library to convert RGB 24-bit colors into ANSI 256 (8-bit) color codes with zero dependencies and const fn. Th

Linda_pp 7 Nov 17, 2022
Rust Macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.

Rust Embed Rust Custom Derive Macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev. Y

Peter 1k Jan 5, 2023
This crate defines a single macro that is a brainfunct compile-time interpreter.

Compile Protection This crate defines a single macro that is a brainfunct compile-time interpreter. One example is as follows #![recursion_limit = "18

John Marsden 7 Nov 29, 2021
Rust I18n is use Rust codegen for load YAML file storage translations on compile time, and give you a t! macro for simply get translation texts.

Rust I18n Rust I18n is use Rust codegen for load YAML file storage translations on compile time, and give you a t! macro for simply get translation te

Longbridge 73 Dec 27, 2022
ChatGPT powered Rust proc macro that generates code at compile-time.

gpt-macro ChatGPT powered Rust proc macro that generates code at compile-time. Implemented Macros auto_impl!{} #[auto_test(...)] Usage Get ChatGPT API

Akira Moroo 429 Apr 15, 2023