Repository: jprochazk/garde (Rust)

Issue:
feature request: support for stdlib `LazyLock` in regex
garde currently supports static regexes via `once_cell`: https://github.com/jprochazk/garde/blob/be00ddddf8de14530ee890ccfdbaf0b13fb32852/garde/src/rules/pattern.rs#L147-L163

stdlib added LazyLock recently: https://doc.rust-lang.org/std/sync/struct.LazyLock.html

would you be open for a PR adding support for these? this would mean raising MSRV to `1.80`


Discussion:
I'd like to wait until at least 1.82 before setting the MSRV to 1.80



Patch:
diff --git a/README.md b/README.md
index 82a556b..348bf3d 100644
--- a/README.md
+++ b/README.md
@@ -518,8 +518,6 @@ Contributing to `garde` only requires a somewhat recent version of [`Rust`](http
 
 This repository also makes use of the following tools, but they are optional:
 - [`insta`](https://insta.rs/) for snapshot testing ([tests/rules](./garde_derive_tests/tests/rules/)).
-- [`just`](https://just.systems/) for running recipes defined in the [`justfile`](./justfile).
-  Run `just -l` to see what recipes are available.
 
 ### License
 
diff --git a/garde/src/rules/pattern.rs b/garde/src/rules/pattern.rs
index d4da3a4..478bd13 100644
--- a/garde/src/rules/pattern.rs
+++ b/garde/src/rules/pattern.rs
@@ -11,9 +11,22 @@
 //! ```
 //!
 //! Alternatively, it can be an expression of type implementing [`Matcher`] or one that dereferences to a [`Matcher`].
-//! [`Matcher`] is implemented for `regex::Regex` (if the `regex` feature is enabled) and `once_cell::sync::Lazy<T>` with any `T: Matcher`.
+//! [`Matcher`] is implemented for `regex::Regex` (if the `regex` feature is enabled) and `std::sync::LazyLock<T>` / `once_cell::sync::Lazy<T>` with any `T: Matcher`.
 //! Please note that the expression will be evaluated each time `validate` is called, so avoid doing any expensive work in the expression.
-//! If the work is unavoidable, at least try to amortize it, such as by using `once_cell::Lazy` or the nightly-only `std::sync::LazyLock`.
+//! If the work is unavoidable, at least try to amortize it, such as by using `std::sync::LazyLock` or `once_cell::Lazy`.
+//!
+//! ```rust
+//! use std::sync::LazyLock;
+//! use regex::Regex;
+//!
+//! static LAZY_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[a-zA-Z0-9][a-zA-Z0-9_]+").unwrap());
+//!
+//! #[derive(garde::Validate)]
+//! struct Test {
+//!     #[garde(pattern(LAZY_RE))]
+//!     v: String,
+//! }
+//! ```
 //!
 //! ```rust
 //! use once_cell::sync::Lazy;
@@ -50,6 +63,18 @@ pub trait Matcher: AsStr {
     fn is_match(&self, haystack: &str) -> bool;
 }
 
+impl<T: Matcher> Matcher for std::sync::LazyLock<T> {
+    fn is_match(&self, haystack: &str) -> bool {
+        std::sync::LazyLock::force(self).is_match(haystack)
+    }
+}
+
+impl<T: AsStr> AsStr for std::sync::LazyLock<T> {
+    fn as_str(&self) -> &str {
+        std::sync::LazyLock::force(self).as_str()
+    }
+}
+
 pub trait Pattern {
     fn validate_pattern<M: Matcher>(&self, matcher: &M) -> bool;
 }
@@ -77,7 +102,7 @@ impl<T: Pattern> Pattern for Option<T> {
 ))]
 #[doc(hidden)]
 pub mod regex_js_sys {
-    pub use ::js_sys::RegExp;
+    pub use js_sys::RegExp;
 
     use super::*;
 
@@ -117,7 +142,7 @@ pub mod regex_js_sys {
     unsafe impl<T> Send for SyncWrapper<T> {}
     unsafe impl<T> Sync for SyncWrapper<T> {}
 
-    pub type StaticPattern = once_cell::sync::Lazy<SyncWrapper<RegExp>>;
+    pub type StaticPattern = std::sync::LazyLock<SyncWrapper<RegExp>>;
 
     #[macro_export]
     macro_rules! __init_js_sys_pattern {
@@ -134,9 +159,9 @@ pub mod regex_js_sys {
 #[cfg(feature = "regex")]
 #[doc(hidden)]
 pub mod regex {
-    pub use ::regex::Regex;
+    pub use regex::Regex;
 
-    use super::*;
+    use super::{AsStr, Matcher};
 
     impl Matcher for Regex {
         fn is_match(&self, haystack: &str) -> bool {
@@ -162,7 +187,7 @@ pub mod regex {
         }
     }
 
-    pub type StaticPattern = once_cell::sync::Lazy<Regex>;
+    pub type StaticPattern = std::sync::LazyLock<Regex>;
 
     #[macro_export]
     macro_rules! __init_pattern {


Test patch:
diff --git a/garde/tests/rules/pattern.rs b/garde/tests/rules/pattern.rs
index 2abadc5..fa1bfaa 100644
--- a/garde/tests/rules/pattern.rs
+++ b/garde/tests/rules/pattern.rs
@@ -3,11 +3,14 @@ use regex::Regex;
 use super::util;
 
 mod sub {
+    use std::sync::LazyLock;
+
     use once_cell::sync::Lazy;
 
     use super::*;
 
-    pub static LAZY_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^abcd|efgh$").unwrap());
+    pub static LAZY_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^abcd|efgh$").unwrap());
+    pub static LAZY_RE_ONCE_CELL: Lazy<Regex> = Lazy::new(|| Regex::new(r"^abcd|efgh$").unwrap());
 }
 
 #[derive(Debug, garde::Validate)]
@@ -18,6 +21,9 @@ struct Test<'a> {
     #[garde(pattern(sub::LAZY_RE))]
     field_path: &'a str,
 
+    #[garde(pattern(sub::LAZY_RE_ONCE_CELL))]
+    field_path_once_cell: &'a str,
+
     #[garde(pattern(create_regex()))]
     field_call: &'a str,
 
@@ -46,12 +52,14 @@ fn pattern_valid() {
             Test {
                 field: "abcd",
                 field_path: "abcd",
+                field_path_once_cell: "abcd",
                 field_call: "abcd",
                 inner: &["abcd"],
             },
             Test {
                 field: "efgh",
                 field_path: "efgh",
+                field_path_once_cell: "abcd",
                 field_call: "efgh",
                 inner: &["efgh"],
             },
@@ -68,12 +76,14 @@ fn pattern_invalid() {
             Test {
                 field: "dcba",
                 field_path: "dcba",
+                field_path_once_cell: "abcd",
                 field_call: "dcba",
                 inner: &["dcba"]
             },
             Test {
                 field: "hgfe",
                 field_path: "hgfe",
+                field_path_once_cell: "abcd",
                 field_call: "hgfe",
                 inner: &["hgfe"]
             }
diff --git a/garde/tests/rules/snapshots/rules__rules__pattern__pattern_invalid.snap b/garde/tests/rules/snapshots/rules__rules__pattern__pattern_invalid.snap
index 0a9458c..6936bc9 100644
--- a/garde/tests/rules/snapshots/rules__rules__pattern__pattern_invalid.snap
+++ b/garde/tests/rules/snapshots/rules__rules__pattern__pattern_invalid.snap
@@ -5,6 +5,7 @@ expression: snapshot
 Test {
     field: "dcba",
     field_path: "dcba",
+    field_path_once_cell: "abcd",
     field_call: "dcba",
     inner: [
         "dcba",
@@ -18,6 +19,7 @@ inner[0]: does not match pattern /^abcd|efgh$/
 Test {
     field: "hgfe",
     field_path: "hgfe",
+    field_path_once_cell: "abcd",
     field_call: "hgfe",
     inner: [
         "hgfe",
@@ -27,5 +29,3 @@ field: does not match pattern /^abcd|efgh$/
 field_call: does not match pattern /^abcd|efgh$/
 field_path: does not match pattern /^abcd|efgh$/
 inner[0]: does not match pattern /^abcd|efgh$/
-
-
diff --git a/garde/tests/ui/compile-fail/pattern_mismatched_types.stderr b/garde/tests/ui/compile-fail/pattern_mismatched_types.stderr
index dfabfaf..1a7d47f 100644
--- a/garde/tests/ui/compile-fail/pattern_mismatched_types.stderr
+++ b/garde/tests/ui/compile-fail/pattern_mismatched_types.stderr
@@ -8,6 +8,7 @@ error[E0277]: the trait bound `&str: Matcher` is not satisfied
   |                     ^^^ the trait `Matcher` is not implemented for `&str`
   |
   = help: the following other types implement trait `Matcher`:
+            LazyLock<T>
             Regex
             once_cell::sync::Lazy<T>
 note: required by a bound in `garde::rules::pattern::apply`