From ee8432d8187fd98b23a9783849c1a123108e6eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leo=20Bl=C3=B6cher?= Date: Fri, 19 Jun 2026 13:10:19 +0100 Subject: [PATCH] Drop Default supertrait from Settings This allows types without a sensible default value to implement `Settings`. Top-level setting types used with `Cli` still need to implement Default for the `--generate` option, but individual fields can now use types like `NonZeroUsize` or `SystemTime`. The `#[settings]` proc-macro still generates a serde-based Default impl. If a field does not have a Default impl, use `#[settings(impl_default = false)]` or specify a _field-specific_ Default with the `#[serde(default = ...)]` field attribute. This is a breaking change: a function taking a `T: Settings` and calling `T::default()` was valid before, and is not anymore. However, this is the only breakage this change can cause. Removing the Default supertrait also allows us to clean up the compatibility hack from foundations#150. --- foundations-macros/src/settings.rs | 37 ++----------------------- foundations/src/cli.rs | 6 ++-- foundations/src/settings/basic_impls.rs | 24 ++++++++++------ foundations/src/settings/mod.rs | 2 +- 4 files changed, 22 insertions(+), 47 deletions(-) diff --git a/foundations-macros/src/settings.rs b/foundations-macros/src/settings.rs index 9efdc3b..1926fbe 100644 --- a/foundations-macros/src/settings.rs +++ b/foundations-macros/src/settings.rs @@ -7,7 +7,7 @@ use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{ Attribute, Expr, ExprLit, Field, Fields, Ident, Item, ItemEnum, ItemStruct, Lit, LitStr, Meta, - MetaNameValue, Path, Type, parse_macro_input, parse_quote, + MetaNameValue, Path, parse_macro_input, parse_quote, }; const ERR_NOT_STRUCT_OR_ENUM: &str = "Settings should be either structure or enum."; @@ -265,18 +265,9 @@ fn impl_settings_trait_for_field( let mut impl_for_field = quote_spanned! { span=> let mut key = parent_key.to_vec(); key.push(#name_str.into()); + #crate_path::settings::Settings::add_docs(&self.#name, &key, docs); }; - // foundations#150: `[T; 0]` used to impl Settings for `T: !Default`, but this - // is not possible anymore. We thus can't call `Settings::add_docs` for such - // fields, but it was a noop anyway. - // TODO(lblocher): Remove this compatibility hack with the next major release - if !is_array_zst(&field.ty) { - impl_for_field.append_all(quote_spanned! { span => - #crate_path::settings::Settings::add_docs(&self.#name, &key, docs); - }); - } - if !docs.is_empty() { impl_for_field.append_all(quote! { docs.insert(key, &[#(#docs,)*][..]); @@ -319,30 +310,6 @@ fn extract_doc_comments(attrs: &[Attribute]) -> Vec { comments } -/// Returns whether `ty` is exactly `[T; 0]` (for some T) -fn is_array_zst(ty: &Type) -> bool { - let Type::Array(array) = ty else { - return false; - }; - - let mut expr = &array.len; - while let Expr::Cast(cast) = expr { - expr = &cast.expr; - } - - let Expr::Lit(ExprLit { - lit: Lit::Int(len_lit), - .. - }) = expr - else { - return false; - }; - len_lit - .base10_parse::() - .map(|len| len == 0) - .unwrap_or(false) -} - /// Returns whether `attrs` contains `serde(flatten)`. fn is_serde_flattened(attrs: &[Attribute]) -> bool { matches!( diff --git a/foundations/src/cli.rs b/foundations/src/cli.rs index 6cd9438..0a50b38 100644 --- a/foundations/src/cli.rs +++ b/foundations/src/cli.rs @@ -29,7 +29,7 @@ const USE_CONFIG_OPT_ID: &str = "config"; /// Additional arguments can be added via `custom_args` argument of the [`Cli::new`] function. /// /// [`Settings`]: crate::settings::Settings -pub struct Cli { +pub struct Cli { /// Parsed service settings. pub settings: S, @@ -37,7 +37,7 @@ pub struct Cli { pub arg_matches: ArgMatches, } -impl Cli { +impl Cli { /// Bootstraps a new command line interface (CLI) for the service. /// /// `custom_args` argument can be used to add extra service-specific arguments to the CLI. @@ -116,7 +116,7 @@ fn get_arg_matches( }) } -fn get_settings(arg_matches: &ArgMatches) -> BootstrapResult { +fn get_settings(arg_matches: &ArgMatches) -> BootstrapResult { if let Some(path) = arg_matches.get_one::(GENERATE_CONFIG_OPT_ID) { let settings = S::default(); diff --git a/foundations/src/settings/basic_impls.rs b/foundations/src/settings/basic_impls.rs index fa47046..486fe25 100644 --- a/foundations/src/settings/basic_impls.rs +++ b/foundations/src/settings/basic_impls.rs @@ -13,7 +13,7 @@ use std::ops::Range; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, SystemTime}; macro_rules! impl_noop { ( $( $impl_desc:tt )* ) => { @@ -30,7 +30,8 @@ macro_rules! impl_noop { } impl_noop!( Settings for PhantomData where T: 'static); -impl_noop!( Settings for Range where Idx: Debug + Serialize + DeserializeOwned + Clone + Default + 'static); +impl_noop!( Settings for [T; 0] where T: Debug + Clone + 'static); +impl_noop!( Settings for Range where Idx: Debug + Serialize + DeserializeOwned + Clone + 'static); impl_noop!( Settings for Reverse where T: Settings); impl_noop!( Settings for Wrapping where T: Settings); @@ -39,7 +40,7 @@ macro_rules! impl_for_num { ( $( $Ty:ty )* ) => { $( impl_noop!(Settings for $Ty); impl_noop!(Settings for Saturating<$Ty>); - impl_noop!(Settings for Option>); + impl_noop!(Settings for NonZero<$Ty>); )* }; } @@ -64,7 +65,14 @@ impl_for_non_generic! { CString, OsString, Duration, - PathBuf + SystemTime, + PathBuf, + std::net::IpAddr, + std::net::Ipv4Addr, + std::net::Ipv6Addr, + std::net::SocketAddr, + std::net::SocketAddrV4, + std::net::SocketAddrV6 } macro_rules! impl_for_ref { @@ -120,10 +128,10 @@ macro_rules! impl_for_array { } impl_for_array! { - 0 1 2 3 4 5 6 7 8 9 - 10 11 12 13 14 15 16 17 18 19 - 20 21 22 23 24 25 26 27 28 29 - 30 31 32 + 1 2 3 4 5 6 7 8 9 10 + 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 + 31 32 } impl Settings for Option { diff --git a/foundations/src/settings/mod.rs b/foundations/src/settings/mod.rs index c9f8556..8742ce9 100644 --- a/foundations/src/settings/mod.rs +++ b/foundations/src/settings/mod.rs @@ -343,7 +343,7 @@ pub use foundations_macros::settings; /// [`settings`] macro. /// /// [`settings`]: crate::settings::settings -pub trait Settings: Default + Clone + Serialize + DeserializeOwned + Debug + 'static { +pub trait Settings: Clone + Serialize + DeserializeOwned + Debug + 'static { /// Add Rust doc comments for the settings fields. /// /// Docs for each field need to be added to the provided hashmap with the key consisting of the