Skip to content

Commit

Permalink
Implements feature to disable any usage of alloc.
Browse files Browse the repository at this point in the history
  • Loading branch information
red1bluelost committed Jan 20, 2024
1 parent fd86173 commit e2d33f8
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = ["derive_builder", "derive_builder_macro", "derive_builder_core", "derive_builder_no_std_tests"]
members = ["derive_builder", "derive_builder_macro", "derive_builder_core", "derive_builder_no_alloc_tests", "derive_builder_no_std_tests"]
4 changes: 4 additions & 0 deletions derive_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] - 2023-12-21
- Add `build_fn(validation_error = <bool>)` to disable generation of `ValidationError` within the builder's error so that `alloc::string` is avoided.
- Add feature `alloc` for controlling linking of `alloc` crate during `no_std`. This way users can use `no_std` without providing a `global_allocator`.

## [Unreleased] - 2023-07-25
- Make try-setters inherit `strip_option` from `setter` for `try_setter`. Using these settings together previously caused a compile error #284

Expand Down
1 change: 1 addition & 0 deletions derive_builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ readme = "README.md"
default = ["std"]
std = []
clippy = ["derive_builder_macro/clippy"]
alloc = []

[dependencies]
derive_builder_macro = { version = "=0.12.0", path = "../derive_builder_macro" }
Expand Down
3 changes: 2 additions & 1 deletion derive_builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ It's as simple as three steps:
- **Custom build method error types**: You can use `#[builder(build_fn(error = "path::to::Error"))]` to have your builder return an error type of your choosing. By default, the macro will emit an error type alongside the builder.
- **Builder derivations**: You can use `#[builder(derive(Trait1, Trait2, ...))]` to have the builder derive additonal traits. All builders derive `Default` and `Clone`, so you should not declare those in this attribute.
- **Pass-through attributes**: Use `#[builder_struct_attr(...)]`, `#[builder_impl_attr(...)]`, `#[builder_field_attr(...)]`, and `#[builder_setter_attr(...)]` to declare attributes that will be added to the relevant part of the generated builder.
- **no_std support**: Just add `#[builder(no_std)]` to your struct and add `extern crate alloc` to your crate.
- **no_std support**: Just add `#[builder(no_std)]` to your struct, use feature `alloc`, and add `extern crate alloc` to your crate.
- **No alloc no_std support**: Do not use `alloc` feature and then either add `#[builder(no_std, build_fn(validation_error = false))]` or `#[builder(no_std, build_fn(error = "path::to::Error"))]` to your struct.
- **Renaming and re-export support**: Use `#[builder(crate = "...")]` to set the root for `derive_builder`. This is useful if you want to rename `derive_builder` in `Cargo.toml` or if your crate is re-exporting `derive_builder::Builder` and needs the generated code to not directly reference the `derive_builder` crate.

For more information and examples please take a look at our [documentation][doc].
Expand Down
4 changes: 2 additions & 2 deletions derive_builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@
#![deny(warnings)]
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(feature = "std"))]
#[cfg(feature = "alloc")]
extern crate alloc;

extern crate derive_builder_macro;
Expand All @@ -727,7 +727,7 @@ pub use error::UninitializedFieldError;
#[doc(hidden)]
pub mod export {
pub mod core {
#[cfg(not(feature = "std"))]
#[cfg(feature = "alloc")]
pub use alloc::string;
#[cfg(not(feature = "std"))]
pub use core::*;
Expand Down
116 changes: 108 additions & 8 deletions derive_builder_core/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ pub struct Builder<'a> {
///
/// This would be `false` in the case where an already-existing error is to be used.
pub generate_error: bool,
/// Whether to include `ValidationError` in the generated enum. Necessary to avoid dependency
/// on `alloc::string`.
///
/// This would be `false` when `build_fn.validation_error == false`. This is ignored when
/// `generate_error` is `false`.
pub generate_validation_error: bool,
/// Whether this builder must derive `Clone`.
///
/// This is true even for a builder using the `owned` pattern if there is a field whose setter
Expand Down Expand Up @@ -231,15 +237,35 @@ impl<'a> ToTokens for Builder<'a> {
let builder_error_ident = format_ident!("{}Error", builder_ident);
let builder_error_doc = format!("Error type for {}", builder_ident);

let validation_error = self
.generate_validation_error
.then_some(quote!(
/// Custom validation error
ValidationError(#crate_root::export::core::string::String),
))
.unwrap_or_default();
let validation_from = self.generate_validation_error.then_some(quote!(
impl #crate_root::export::core::convert::From<#crate_root::export::core::string::String> for #builder_error_ident {
fn from(s: #crate_root::export::core::string::String) -> Self {
Self::ValidationError(s)
}
}
)).unwrap_or_default();
let validation_display = self
.generate_validation_error
.then_some(quote!(
Self::ValidationError(ref error) => write!(f, "{}", error),
))
.unwrap_or_default();

tokens.append_all(quote!(
#[doc=#builder_error_doc]
#[derive(Debug)]
#[non_exhaustive]
#builder_vis enum #builder_error_ident {
/// Uninitialized field
UninitializedField(&'static str),
/// Custom validation error
ValidationError(#crate_root::export::core::string::String),
#validation_error
}

impl #crate_root::export::core::convert::From<#crate_root::UninitializedFieldError> for #builder_error_ident {
Expand All @@ -248,17 +274,13 @@ impl<'a> ToTokens for Builder<'a> {
}
}

impl #crate_root::export::core::convert::From<#crate_root::export::core::string::String> for #builder_error_ident {
fn from(s: #crate_root::export::core::string::String) -> Self {
Self::ValidationError(s)
}
}
#validation_from

impl #crate_root::export::core::fmt::Display for #builder_error_ident {
fn fmt(&self, f: &mut #crate_root::export::core::fmt::Formatter) -> #crate_root::export::core::fmt::Result {
match self {
Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field),
Self::ValidationError(ref error) => write!(f, "{}", error),
#validation_display
}
}
}
Expand Down Expand Up @@ -356,6 +378,7 @@ macro_rules! default_builder {
field_initializers: vec![quote!(foo: ::db::export::core::default::Default::default(), )],
functions: vec![quote!(fn bar() -> { unimplemented!() })],
generate_error: true,
generate_validation_error: true,
must_derive_clone: true,
doc_comment: None,
deprecation_notes: DeprecationNotes::default(),
Expand Down Expand Up @@ -757,4 +780,81 @@ mod tests {
.to_string()
);
}

#[test]
fn no_validation_error() {
let mut builder = default_builder!();
builder.generate_validation_error = false;

assert_eq!(
quote!(#builder).to_string(),
{
let mut result = quote!();

#[cfg(not(feature = "clippy"))]
result.append_all(quote!(#[allow(clippy::all)]));

result.append_all(quote!(
#[derive(Clone)]
pub struct FooBuilder {
foo: u32,
}
));

#[cfg(not(feature = "clippy"))]
result.append_all(quote!(#[allow(clippy::all)]));

result.append_all(quote!(
#[allow(dead_code)]
impl FooBuilder {
fn bar () -> {
unimplemented!()
}

/// Create an empty builder, with all fields set to `None` or `PhantomData`.
fn create_empty() -> Self {
Self {
foo: ::db::export::core::default::Default::default(),
}
}
}

impl ::db::export::core::default::Default for FooBuilder {
fn default() -> Self {
Self::create_empty()
}
}
));

result.append_all(quote!(
#[doc="Error type for FooBuilder"]
#[derive(Debug)]
#[non_exhaustive]
pub enum FooBuilderError {
/// Uninitialized field
UninitializedField(&'static str),
}

impl ::db::export::core::convert::From<::db::UninitializedFieldError> for FooBuilderError {
fn from(s: ::db::UninitializedFieldError) -> Self {
Self::UninitializedField(s.field_name())
}
}

impl ::db::export::core::fmt::Display for FooBuilderError {
fn fmt(&self, f: &mut ::db::export::core::fmt::Formatter) -> ::db::export::core::fmt::Result {
match self {
Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field),
}
}
}

impl std::error::Error for FooBuilderError {}
));

result
}
.to_string()
);
}
}
23 changes: 22 additions & 1 deletion derive_builder_core/src/macro_options/darling_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn no_visibility_conflict<T: Visibility>(v: &T) -> darling::Result<()> {
/// There is no inheritance for these settings from struct-level to field-level,
/// so we don't bother using `Option` for values in this struct.
#[derive(Debug, Clone, FromMeta)]
#[darling(default)]
#[darling(default, and_then = "Self::validation_needs_error")]
pub struct BuildFn {
skip: bool,
name: Ident,
Expand All @@ -95,6 +95,25 @@ pub struct BuildFn {
/// * If `validate` is specified, then this type must provide a conversion from the specified
/// function's error type.
error: Option<Path>,
/// Whether to generate `ValidationError(String)` as a variant of the build error type.
///
/// Setting this to `false` will prevent `derive_builder` from using the `validate` function
/// but this also means it does not generate any usage of the `alloc` crate (useful when
/// disabling the `alloc` feature in `no_std`).
validation_error: bool,
}

impl BuildFn {
fn validation_needs_error(self) -> darling::Result<Self> {
if self.validate.is_some() && !self.validation_error {
Err(darling::Error::custom(
"Cannot set `validation_error = false` when using `validate`",
)
.with_span(&self.validate))
} else {
Ok(self)
}
}
}

impl Default for BuildFn {
Expand All @@ -107,6 +126,7 @@ impl Default for BuildFn {
private: Default::default(),
vis: None,
error: None,
validation_error: true,
}
}
}
Expand Down Expand Up @@ -709,6 +729,7 @@ impl Options {
field_initializers: Vec::with_capacity(self.field_count()),
functions: Vec::with_capacity(self.field_count()),
generate_error: self.build_fn.error.is_none(),
generate_validation_error: self.build_fn.validation_error,
must_derive_clone: self.requires_clone(),
doc_comment: None,
deprecation_notes: Default::default(),
Expand Down
12 changes: 12 additions & 0 deletions derive_builder_no_alloc_tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "derive_builder_no_alloc_tests"
version = "0.1.0"
authors = ["Micah Weston <micahsweston@gmail.com>"]
edition = "2018"
publish = false

[features]
clippy = ["derive_builder/clippy"]

[dependencies]
derive_builder = { path = "../derive_builder", default-features = false }
71 changes: 71 additions & 0 deletions derive_builder_no_alloc_tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#![no_std]
#![allow(unused, clippy::blacklisted_name)]

use derive_builder::{self, Builder};

#[derive(Debug, Eq, PartialEq)]
pub struct FooError(&'static str);

impl From<derive_builder::UninitializedFieldError> for FooError {
fn from(err: derive_builder::UninitializedFieldError) -> Self {
Self(err.field_name())
}
}

#[derive(Builder)]
#[builder(no_std, build_fn(error = "FooError"))]
pub struct Foo {
pub bar: i32,
}

#[derive(Builder)]
#[builder(no_std, build_fn(validation_error = false))]
pub struct Fee {
pub bar: i32,
}

pub fn build_foo_ok() -> Foo {
FooBuilder::default().bar(42).build().unwrap()
}

pub fn build_foo_err() -> Option<FooError> {
let foo = FooBuilder::default().build();
foo.err()
}

pub fn build_fee_ok() -> Foo {
FooBuilder::default().bar(42).build().unwrap()
}

pub fn build_fee_err() -> Option<FeeBuilderError> {
let fee = FeeBuilder::default().build();
fee.err()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_foo_builder_ok() {
assert_eq!(build_foo_ok().bar, 42);
}

#[test]
fn test_foo_builder_err() {
assert_eq!(build_foo_err(), Some(FooError("bar")));
}

#[test]
fn test_fee_builder_ok() {
assert_eq!(build_fee_ok().bar, 42);
}

#[test]
fn test_fee_builder_err() {
assert!(matches!(
build_fee_err(),
Some(FeeBuilderError::UninitializedField("bar"))
));
}
}
2 changes: 1 addition & 1 deletion derive_builder_no_std_tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ publish = false
clippy = ["derive_builder/clippy"]

[dependencies]
derive_builder = { path = "../derive_builder", default-features = false }
derive_builder = { path = "../derive_builder", default-features = false, features = ["alloc"] }

0 comments on commit e2d33f8

Please sign in to comment.