From 5cad42c96c955a78aff11104ca983f353bc42dff Mon Sep 17 00:00:00 2001 From: Nikita Patskov Date: Sun, 16 Feb 2025 20:39:15 +0000 Subject: [PATCH 1/2] Implemented macro for compile time non empty string creation This is more convenient for constant strings in the codebase that are known to be non empty. --- Cargo.toml | 1 + src/lib.rs | 3 +++ src/macros.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index 0fd59c6..2ec951a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1", features = ["derive"] } [features] default = [] +macro = [] serde = ["dep:serde"] diff --git a/src/lib.rs b/src/lib.rs index f225c34..a15228f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,9 @@ use delegate::delegate; #[cfg(feature = "serde")] mod serde_support; +#[cfg(feature = "macro")] +mod macros; + mod trait_impls; /// A simple String wrapper type, similar to NonZeroUsize and friends. diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..a1720fd --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,44 @@ +#[macro_export] +/// Creates a `NonEmptyString` from a string literal at compile time. +/// +/// This macro ensures that the provided string is **not empty** at compile time, +/// preventing runtime errors due to empty strings. +/// +/// # Examples +/// +/// ``` +/// use non_empty_string::{non_empty, NonEmptyString}; +/// +/// let s: NonEmptyString = non_empty!("Hello, Rust!"); +/// assert_eq!(s, NonEmptyString::new("Hello, Rust!".to_string()).unwrap()); +/// ``` +/// +/// # Compile-time Failure +/// +/// If an empty string is provided, this macro will cause a **compile-time error**. +/// +/// ```compile_fail +/// use non_empty_string::non_empty; +/// +/// let s = non_empty!(""); +/// ``` +macro_rules! non_empty { + ($s:expr) => {{ + // Compile-time assertion to ensure the string is non-empty + const _: () = assert!(!$s.is_empty(), "String cannot be empty"); + + // Create a NonEmptyString, unsafely wrapping since we've checked it's valid + unsafe { NonEmptyString::new_unchecked($s.to_string()) } + }}; +} + +#[cfg(test)] +mod tests { + use crate::NonEmptyString; + + #[test] + fn test_non_empty_string_macro_valid() { + let s = non_empty!("Test String"); + assert_eq!(s, NonEmptyString::try_from("Test String").unwrap()); + } +} From 1f88a2535909e579240a736e5a0146b89e971cec Mon Sep 17 00:00:00 2001 From: Midas Lambrichts Date: Wed, 9 Apr 2025 19:43:33 +0200 Subject: [PATCH 2/2] Small cleanup of macro and prepare for release The majority of the work has been done by @patskovn in #19, I'm just cleaning up and preparing a release, since he has used his branch to publish his own package, so I can't just merge it in unfortunately --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- src/lib.rs | 2 +- src/macros.rs | 33 +++++++++++++++++++++++---------- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aabd7ea..0025d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.6](https://github.com/MidasLamb/non-empty-string/compare/v0.2.5...v0.2.6) - 2025-04-09 + +### Added + +- `non_empty_string!()` macro to create a `NonEmptyString` at compile time , thanks @patskovn in #19 (requires `macros` feature flag) + ## [0.2.5](https://github.com/MidasLamb/non-empty-string/compare/v0.2.4...v0.2.5) - 2024-10-15 ### Added diff --git a/Cargo.toml b/Cargo.toml index 2ec951a..d1551ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ serde = { version = "1", features = ["derive"] } [features] default = [] -macro = [] +macros = [] serde = ["dep:serde"] diff --git a/src/lib.rs b/src/lib.rs index a15228f..8477b8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ use delegate::delegate; #[cfg(feature = "serde")] mod serde_support; -#[cfg(feature = "macro")] +#[cfg(feature = "macros")] mod macros; mod trait_impls; diff --git a/src/macros.rs b/src/macros.rs index a1720fd..1b7adda 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -7,9 +7,9 @@ /// # Examples /// /// ``` -/// use non_empty_string::{non_empty, NonEmptyString}; +/// use non_empty_string::{non_empty_string, NonEmptyString}; /// -/// let s: NonEmptyString = non_empty!("Hello, Rust!"); +/// let s: NonEmptyString = non_empty_string!("Hello, Rust!"); /// assert_eq!(s, NonEmptyString::new("Hello, Rust!".to_string()).unwrap()); /// ``` /// @@ -18,27 +18,40 @@ /// If an empty string is provided, this macro will cause a **compile-time error**. /// /// ```compile_fail -/// use non_empty_string::non_empty; +/// use non_empty_string::non_empty_string; /// -/// let s = non_empty!(""); +/// let s = non_empty_string!(""); /// ``` -macro_rules! non_empty { +macro_rules! non_empty_string { ($s:expr) => {{ // Compile-time assertion to ensure the string is non-empty const _: () = assert!(!$s.is_empty(), "String cannot be empty"); // Create a NonEmptyString, unsafely wrapping since we've checked it's valid - unsafe { NonEmptyString::new_unchecked($s.to_string()) } + unsafe { $crate::NonEmptyString::new_unchecked($s.to_string()) } }}; } #[cfg(test)] mod tests { - use crate::NonEmptyString; + // We explicitely DO NOT do `use crate::NonEmptyString` or anything of the sorts to ensure the macro has proper hygiene. + // Otherwise tests might pass, but if a user does `non_empty_string::non_empty_string!("A")`, they might get compilation + // errors that `NonEmptyString` is not in scope. + + const NON_EMPTY_STRING: &'static str = "non-empty-string"; + + #[test] + fn test_const_non_empty_string_macro_valid() { + let s = non_empty_string!(NON_EMPTY_STRING); + assert_eq!( + s, + crate::NonEmptyString::try_from(NON_EMPTY_STRING).unwrap() + ); + } #[test] - fn test_non_empty_string_macro_valid() { - let s = non_empty!("Test String"); - assert_eq!(s, NonEmptyString::try_from("Test String").unwrap()); + fn test_inline_non_empty_string_macro_valid() { + let s = non_empty_string!("Test String"); + assert_eq!(s, crate::NonEmptyString::try_from("Test String").unwrap()); } }