No public description

PiperOrigin-RevId: 903835803
diff --git a/rust/e2e_tests/testdata/fuzz_tests.rs b/rust/e2e_tests/testdata/fuzz_tests.rs
index 9b827a9..d4f8431 100644
--- a/rust/e2e_tests/testdata/fuzz_tests.rs
+++ b/rust/e2e_tests/testdata/fuzz_tests.rs
@@ -32,14 +32,14 @@
     type UserValue<'user> = Vec<u8>;
     type CorpusValue = Vec<u8>;
 
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
         let mut val = vec![0u8; rng.random_range(0..100)];
         rng.fill(&mut val[..]);
         Ok(val)
     }
 
     fn mutate(
-        &self,
+        &mut self,
         val: &mut Self::CorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -78,6 +78,10 @@
     ) -> anyhow::Result<Self::UserValue<'a>> {
         Ok(val.clone())
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        Ok(value)
+    }
 }
 
 #[fuzztest(_a = Arbitrary::<bool>::default())]
@@ -160,12 +164,12 @@
     type UserValue<'user> = u32;
     type CorpusValue = u32;
 
-    fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
         Ok(0)
     }
 
     fn mutate(
-        &self,
+        &mut self,
         _val: &mut Self::CorpusValue,
         _rng: &mut dyn rand::Rng,
         _only_shrink: bool,
@@ -179,6 +183,10 @@
     ) -> anyhow::Result<Self::UserValue<'a>> {
         Ok(*val)
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        Ok(value)
+    }
 }
 
 #[fuzztest(a = FallibleDomain::new())]
diff --git a/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs
index 6cb6b58..3cb112a 100644
--- a/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs
+++ b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs
@@ -77,14 +77,14 @@
         type UserValue<#user_value_lifetime_generic> = #domain_struct_name <#(#user_value_domain_generics),*>;
         type CorpusValue = #domain_struct_name <#(#corpus_domain_generics),*>;
 
-        fn init(&self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> {
+        fn init(&mut self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> {
           Ok(#domain_struct_name {
             #(#field_names: self.#field_names.init(rng)?),*
           })
         }
 
         fn mutate(
-            &self,
+            &mut self,
             val: &mut Self::CorpusValue,
             rng: &mut dyn ::fuzztest::reexports::rand::Rng,
             only_shrink: bool,
@@ -98,6 +98,17 @@
             #(#field_names: self.#field_names.get_user_value(&corpus_value.#field_names)?),*
           })
         }
+
+        fn from_value(&self, value: Self::UserValue<'_>) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> {
+          Ok(#domain_struct_name {
+            #(#field_names: self.#field_names.from_value(value.#field_names)?),*
+          })
+        }
+
+        fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> ::fuzztest::reexports::anyhow::Result<()> {
+          #( self.#field_names.validate_corpus_value(&corpus_value.#field_names)?; )*
+          Ok(())
+        }
       }
     };
     (domain_definition_tokens, field_names)
@@ -144,7 +155,7 @@
               type UserValue<'user> = __FuzzTestTestFuzzStateWrapper<T0::UserValue<'user>, T1::UserValue<'user> >;
               type CorpusValue = __FuzzTestTestFuzzStateWrapper<T0::CorpusValue, T1::CorpusValue>;
 
-              fn init(&self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> {
+              fn init(&mut self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> {
                 Ok(__FuzzTestTestFuzzStateWrapper {
                   a: self.a.init(rng)?,
                   b: self.b.init(rng)?
@@ -152,7 +163,7 @@
               }
 
               fn mutate(
-                  &self,
+                  &mut self,
                   val: &mut Self::CorpusValue,
                   rng: &mut dyn ::fuzztest::reexports::rand::Rng,
                   only_shrink: bool,
@@ -168,6 +179,19 @@
                   b: self.b.get_user_value(&corpus_value.b)?
                 })
               }
+
+              fn from_value(&self, value: Self::UserValue<'_>) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> {
+                Ok(__FuzzTestTestFuzzStateWrapper {
+                  a: self.a.from_value(value.a)?,
+                  b: self.b.from_value(value.b)?
+                })
+              }
+
+              fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> ::fuzztest::reexports::anyhow::Result<()> {
+                self.a.validate_corpus_value(&corpus_value.a)?;
+                self.b.validate_corpus_value(&corpus_value.b)?;
+                Ok(())
+              }
             }
           }
           .to_string())
diff --git a/rust/fuzztest_macro/src/helpers/test_registration.rs b/rust/fuzztest_macro/src/helpers/test_registration.rs
index 3806469..bf7be38 100644
--- a/rust/fuzztest_macro/src/helpers/test_registration.rs
+++ b/rust/fuzztest_macro/src/helpers/test_registration.rs
@@ -109,9 +109,11 @@
 
         let fuzz_test_struct_instance_tokens = quote!(
           #fuzz_test_struct_name {
-              domain: #domain_struct_name {
+              domain: std::sync::Arc::new(std::sync::Mutex::new(
+                #domain_struct_name {
                   #(#fuzz_test_domain_field_names: #domain_ctors),*
-              },
+                }
+              )),
               test_fn: #prop_fn_ident
           }
         );
@@ -212,7 +214,7 @@
         {
             where_clauses.predicates.push(
           parse_quote! {
-            for <#user_value_lifetime_generic> #domain_gen: #crate_name::domains::Domain<UserValue<#user_value_lifetime_generic> = #ty >
+            for <#user_value_lifetime_generic> #domain_gen: #crate_name::domains::Domain<UserValue<#user_value_lifetime_generic> = #ty > + 'static
         });
             where_clauses.predicates.push(parse_quote! { #corpus_gen: 'static });
         }
@@ -227,7 +229,7 @@
           #fuzz_test_domain_definition
 
           struct #fuzz_test_struct_name #generics {
-            domain: #domain_struct_name #generics,
+            domain: std::sync::Arc<std::sync::Mutex<#domain_struct_name #generics>>,
             test_fn: #test_fn_type
           }
 
@@ -248,7 +250,10 @@
                                 .downcast_ref::<#domain_struct_name<#(#corpus_generics),*>>()
                                 .expect("Attempt to recover user value before testing failed.");
 
-                  let user_value = self.domain.get_user_value(wrapper).expect("Failed to get user value from corpus value");
+                  let user_value = self.domain.lock()
+                  .expect("Failed to acquire domain lock")
+                  .get_user_value(wrapper)
+                  .expect("Failed to get user value from corpus value");
 
                   let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.test_fn)(#(user_value.#fuzz_test_domain_field_names),* ) ));
 
@@ -257,8 +262,8 @@
               fn print_finding_report(&self) {
                   todo!("Not implemented!")
               }
-              fn domains(&self) -> &dyn #crate_name::domains::GenericDomain {
-                &self.domain
+              fn domains(&self) -> std::sync::Arc<std::sync::Mutex<dyn #crate_name::domains::GenericDomain>> {
+                std::sync::Arc::clone(&self.domain) as std::sync::Arc<std::sync::Mutex<dyn #crate_name::domains::GenericDomain>>
               }
           }
 
@@ -299,14 +304,14 @@
         expect_that!(
           fuzztest_object_tokenstream.to_string(), ends_with( quote! {
               struct __FuzzTestTestFuzz<T0, T1> {
-                domain: __FuzzTestTestFuzzStateWrapper<T0, T1>,
+                domain: std::sync::Arc<std::sync::Mutex<__FuzzTestTestFuzzStateWrapper<T0, T1> >>,
                 test_fn: fn(i32, std::string::String)
               }
 
               impl<T0, T1> ::fuzztest::internal::FuzzTest for __FuzzTestTestFuzz<T0, T1>
-              where for <'user> T0: ::fuzztest::domains::Domain<UserValue<'user> = i32>,
+              where for <'user> T0: ::fuzztest::domains::Domain<UserValue<'user> = i32> + 'static,
                     T0::CorpusValue: 'static,
-                    for <'user> T1: ::fuzztest::domains::Domain<UserValue<'user> = std::string::String>,
+                    for <'user> T1: ::fuzztest::domains::Domain<UserValue<'user> = std::string::String> + 'static,
                     T1::CorpusValue: 'static {
                   fn name(&self) -> &'static str {
                     "test_fuzz"
@@ -324,7 +329,10 @@
                             .downcast_ref::<__FuzzTestTestFuzzStateWrapper<T0::CorpusValue, T1::CorpusValue>>()
                             .expect("Attempt to recover user value before testing failed.");
 
-                    let user_value = self.domain.get_user_value(wrapper).expect("Failed to get user value from corpus value");
+                    let user_value = self.domain.lock()
+                        .expect("Failed to acquire domain lock")
+                        .get_user_value(wrapper)
+                        .expect("Failed to get user value from corpus value");
                     // Safety: Data is not reused after the test.
                     let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.test_fn)(user_value.a, user_value.b) ));
 
@@ -333,17 +341,19 @@
                   fn print_finding_report(&self) {
                     todo!("Not implemented!")
                   }
-                  fn domains(&self) -> &dyn ::fuzztest::domains::GenericDomain {
-                    &self.domain
+                  fn domains(&self) -> std::sync::Arc<std::sync::Mutex<dyn ::fuzztest::domains::GenericDomain>> {
+                    std::sync::Arc::clone(&self.domain) as std::sync::Arc<std::sync::Mutex<dyn ::fuzztest::domains::GenericDomain>>
                   }
               }
 
               fn __FuzzTestTestFuzz_factory() -> ::fuzztest::internal::BoxedFuzzTest {
                 ::std::boxed::Box::new(__FuzzTestTestFuzz {
-                  domain: __FuzzTestTestFuzzStateWrapper {
-                    a: ::fuzztest::domains::arbitrary::Arbitrary::<i32>::default(),
-                    b: ::fuzztest::domains::arbitrary::Arbitrary::<String>::default()
-                  },
+                  domain: std::sync::Arc::new(std::sync::Mutex::new(
+                    __FuzzTestTestFuzzStateWrapper {
+                      a: ::fuzztest::domains::arbitrary::Arbitrary::<i32>::default(),
+                      b: ::fuzztest::domains::arbitrary::Arbitrary::<String>::default()
+                    }
+                  )),
                   test_fn: __property_fn__test_fuzz
                 })
               }
diff --git a/rust/src/domains.rs b/rust/src/domains.rs
index a873c59..1f8836f 100644
--- a/rust/src/domains.rs
+++ b/rust/src/domains.rs
@@ -22,8 +22,10 @@
 
 use anyhow;
 use anyhow::Context;
+use rand::RngExt;
 
 use std::any::Any;
+use std::fmt;
 
 pub trait CloneAny: Any {
     fn clone_box(&self) -> Box<dyn CloneAny>;
@@ -123,14 +125,14 @@
     type CorpusValue: Serialize + DeserializeOwned + Clone;
 
     /// Initializes a new value drawn from the domain.
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue>;
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue>;
 
     /// Mutates the value in `val` to a new value drawn from the domain.
     ///
     /// If `only_shrink` is `true`, then the mutation must not increase the size of the corpus
     /// value. Otherwise, the mutation can both shrink and grow the corpus value.
     fn mutate(
-        &self,
+        &mut self,
         val: &mut Self::CorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -158,6 +160,126 @@
     fn serialize_corpus(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result<Vec<u8>> {
         postcard::to_stdvec(corpus_value).context("Failed to serialize corpus value to bytes")
     }
+
+    /// Converts a user value to a corpus value.
+    #[allow(clippy::wrong_self_convention)]
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue>;
+
+    /// Validates that a corpus value satisfies the domain's constraints.
+    fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> {
+        Ok(())
+    }
+}
+
+/// Helper struct that stores seeds and optional lazy seed provider for a domain.
+pub struct DomainSeeds<C> {
+    seeds: Vec<C>,
+    seed_provider: Option<Box<dyn FnOnce() -> Vec<C> + Send + Sync>>,
+}
+
+impl<C> Default for DomainSeeds<C> {
+    fn default() -> Self {
+        Self {
+            seeds: Vec::new(),
+            seed_provider: None,
+        }
+    }
+}
+
+impl<C> DomainSeeds<C> {
+    pub fn new() -> Self {
+        Self::default()
+    }
+}
+
+impl<C: Clone> Clone for DomainSeeds<C> {
+    fn clone(&self) -> Self {
+        if self.seed_provider.is_some() {
+            panic!("DomainSeeds with a seed provider cannot be cloned before initialization");
+        }
+        Self {
+            seeds: self.seeds.clone(),
+            seed_provider: None,
+        }
+    }
+}
+
+impl<C: Clone + fmt::Debug> fmt::Debug for DomainSeeds<C> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("DomainSeeds")
+            .field("seeds", &self.seeds)
+            .field("has_seed_provider", &self.seed_provider.is_some())
+            .finish()
+    }
+}
+
+impl<C: Clone> DomainSeeds<C> {
+    pub fn extend_seeds(&mut self, seeds: impl IntoIterator<Item = C>) {
+        self.seeds.extend(seeds);
+    }
+
+    pub fn set_provider<F>(&mut self, provider: F)
+    where
+        F: FnOnce() -> Vec<C> + Send + Sync + 'static,
+    {
+        self.seed_provider = Some(Box::new(provider));
+    }
+
+    /// Evaluates lazy seed provider (if present) and returns a random seed with 50% probability.
+    pub fn sample(&mut self, rng: &mut dyn rand::Rng) -> Option<C> {
+        if let Some(seed_provider) = self.seed_provider.take() {
+            self.extend_seeds(seed_provider());
+        }
+        if self.seeds.is_empty() || !rng.random_bool(0.5) {
+            None
+        } else {
+            let idx = rng.random_range(0..self.seeds.len());
+            Some(self.seeds[idx].clone())
+        }
+    }
+}
+
+pub trait SeedableDomain: Domain + Sized {
+    /// Mutable accessor to the domain's seed storage.
+    fn seeds_mut(&mut self) -> &mut DomainSeeds<Self::CorpusValue>;
+
+    /// Adds pre-defined seeds. Panics if any seed is invalid for this domain.
+    fn with_seeds(mut self, seeds: impl IntoIterator<Item = impl Into<Self::CorpusValue>>) -> Self {
+        for seed in seeds {
+            let corpus_val = seed.into();
+            if let Err(e) = self.validate_corpus_value(&corpus_val) {
+                panic!("Invalid seed value for domain: {e:?}");
+            }
+            self.seeds_mut().extend_seeds([corpus_val]);
+        }
+        self
+    }
+
+    /// Non-panicking version for programmatic usage.
+    fn try_with_seeds(
+        mut self,
+        seeds: impl IntoIterator<Item = impl Into<Self::CorpusValue>>,
+    ) -> anyhow::Result<Self> {
+        for seed in seeds {
+            let corpus_val = seed.into();
+            self.validate_corpus_value(&corpus_val)?;
+            self.seeds_mut().extend_seeds([corpus_val]);
+        }
+        Ok(self)
+    }
+
+    /// Adds a lazy seed provider evaluated on first sampling.
+    fn with_seed_provider<F, I, S>(mut self, seed_provider: F) -> Self
+    where
+        F: FnOnce() -> I + Send + Sync + 'static,
+        I: IntoIterator<Item = S>,
+        Self::CorpusValue: From<S>,
+    {
+        self.seeds_mut().set_provider(move || {
+            seed_provider().into_iter().map(Self::CorpusValue::from).collect()
+        });
+        self
+    }
 }
 
 /// A type-erased interface for Domain types.
@@ -170,13 +292,13 @@
     /// Initializes a new value drawn from the domain.
     ///
     /// See `Domain::init` for more details.
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue>;
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue>;
 
     /// Mutates the value in `val` to a new value drawn from the domain.
     ///
     /// See `Domain::mutate` for more details.
     fn mutate(
-        &self,
+        &mut self,
         val: &mut GenericCorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -202,7 +324,7 @@
     D: Domain,
     D::CorpusValue: 'static,
 {
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue> {
         Ok(Box::new(self.init(rng)?))
     }
 
@@ -214,7 +336,7 @@
     ///
     /// See `GenericDomain::mutate` for more details.
     fn mutate(
-        &self,
+        &mut self,
         val: &mut GenericCorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
diff --git a/rust/src/domains/arbitrary.rs b/rust/src/domains/arbitrary.rs
index e2167bf..4f583c2 100644
--- a/rust/src/domains/arbitrary.rs
+++ b/rust/src/domains/arbitrary.rs
@@ -12,10 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use crate::domains::SeedableDomain;
+
 use super::utility::choose_value;
 use super::utility::mutate_integer;
 use super::utility::shrink_towards;
 use super::Domain;
+use super::DomainSeeds;
 use std::char;
 use std::fmt;
 use std::marker::PhantomData;
@@ -36,39 +39,58 @@
 /// # use rand::rngs::SmallRng;
 /// # use rand::SeedableRng;
 ///
-/// let arbitrary_i32 = Arbitrary::<i32>::default();
+/// let mut arbitrary_i32 = Arbitrary::<i32>::default();
 /// let mut rng = SmallRng::seed_from_u64(73);
 ///
 /// let sample = arbitrary_i32.init(&mut rng);
 /// assert!(sample.is_ok());
 /// ```
 pub struct Arbitrary<T> {
+    seeds: DomainSeeds<T>,
     _phantom: PhantomData<T>,
 }
 
-impl<T> Clone for Arbitrary<T> {
+impl<T: Clone> Clone for Arbitrary<T> {
     fn clone(&self) -> Self {
-        Self { _phantom: PhantomData }
+        Self {
+            seeds: self.seeds.clone(),
+            _phantom: PhantomData,
+        }
     }
 }
 
-impl<T> fmt::Debug for Arbitrary<T> {
+impl<T: fmt::Debug + Clone> fmt::Debug for Arbitrary<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("Arbitrary").field("_phantom", &self._phantom).finish()
+        f.debug_struct("Arbitrary")
+            .field("seeds", &self.seeds)
+            .field("_phantom", &self._phantom)
+            .finish()
     }
 }
 
 // We cannot just use `#[derive(Default)]` because `T` might not be `Default`.
 impl<T> Default for Arbitrary<T> {
     fn default() -> Self {
-        Self { _phantom: PhantomData }
+        Self {
+            seeds: DomainSeeds::new(),
+            _phantom: PhantomData,
+        }
     }
 }
 
 impl<T> Arbitrary<T> {
     /// Creates a new `Arbitrary` domain for the given type `T`.
     pub fn new() -> Self {
-        Self { _phantom: PhantomData }
+        Self::default()
+    }
+}
+
+impl<T: Clone + 'static> SeedableDomain for Arbitrary<T>
+where
+    Arbitrary<T>: Domain<CorpusValue = T>,
+{
+    fn seeds_mut(&mut self) -> &mut DomainSeeds<Self::CorpusValue> {
+        &mut self.seeds
     }
 }
 
@@ -76,12 +98,15 @@
     type UserValue<'user> = bool;
     type CorpusValue = bool;
 
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+        if let Some(seed) = self.seeds.sample(rng) {
+            return Ok(seed);
+        }
         Ok(rng.random())
     }
 
     fn mutate(
-        &self,
+        &mut self,
         val: &mut Self::CorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -101,6 +126,14 @@
     ) -> anyhow::Result<Self::UserValue<'a>> {
         Ok(*corpus_value)
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        Ok(value)
+    }
+
+    fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> {
+        Ok(())
+    }
 }
 
 macro_rules! impl_domain_for_integer {
@@ -112,14 +145,17 @@
             type UserValue<'user> = $ty;
             type CorpusValue = $ty;
 
-            fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
-                // We generate a the equivalent integer type so this works for size types.
+            fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+                if let Some(seed) = self.seeds.sample(rng) {
+                    return Ok(seed);
+                }
+                // We generate the equivalent integer type so this works for size types.
                 let val: $int_ty = choose_value(rng);
                 Ok(val as $ty)
             }
 
             fn mutate(
-                &self,
+                &mut self,
                 val: &mut Self::CorpusValue,
                 rng: &mut dyn rand::Rng,
                 only_shrink: bool,
@@ -144,6 +180,17 @@
             ) -> anyhow::Result<Self::UserValue<'a>> {
                 Ok(*corpus_value)
             }
+
+            fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+                Ok(value)
+            }
+
+            fn validate_corpus_value(
+                &self,
+                _corpus_value: &Self::CorpusValue,
+            ) -> anyhow::Result<()> {
+                Ok(())
+            }
         }
     };
 }
@@ -167,12 +214,15 @@
             type UserValue<'user> = $ty;
             type CorpusValue = $ty;
 
-            fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+            fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+                if let Some(seed) = self.seeds.sample(rng) {
+                    return Ok(seed);
+                }
                 Ok(choose_value(rng))
             }
 
             fn mutate(
-                &self,
+                &mut self,
                 val: &mut Self::CorpusValue,
                 rng: &mut dyn rand::Rng,
                 only_shrink: bool,
@@ -215,6 +265,17 @@
             ) -> anyhow::Result<Self::UserValue<'a>> {
                 Ok(*corpus_value)
             }
+
+            fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+                Ok(value)
+            }
+
+            fn validate_corpus_value(
+                &self,
+                _corpus_value: &Self::CorpusValue,
+            ) -> anyhow::Result<()> {
+                Ok(())
+            }
         }
     };
 }
@@ -263,12 +324,15 @@
     type UserValue<'user> = char;
     type CorpusValue = char;
 
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+        if let Some(seed) = self.seeds.sample(rng) {
+            return Ok(seed);
+        }
         Ok(choose_value(rng))
     }
 
     fn mutate(
-        &self,
+        &mut self,
         val: &mut Self::CorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -300,18 +364,29 @@
     ) -> anyhow::Result<Self::UserValue<'a>> {
         Ok(*corpus_value)
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        Ok(value)
+    }
+
+    fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> {
+        Ok(())
+    }
 }
 
 impl Domain for Arbitrary<()> {
     type UserValue<'user> = ();
     type CorpusValue = ();
 
-    fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+        if let Some(seed) = self.seeds.sample(rng) {
+            return Ok(seed);
+        }
         Ok(())
     }
 
     fn mutate(
-        &self,
+        &mut self,
         _val: &mut Self::CorpusValue,
         _rng: &mut dyn rand::Rng,
         _only_shrink: bool,
@@ -326,6 +401,14 @@
     ) -> anyhow::Result<Self::UserValue<'a>> {
         Ok(())
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        Ok(value)
+    }
+
+    fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> {
+        Ok(())
+    }
 }
 
 #[cfg(test)]
@@ -412,7 +495,7 @@
         CorpusValueForArbitrary<T>:
             std::fmt::Debug + Default + Clone + Copy + PartialOrd + PartialEq + 'static,
     {
-        let domain = Arbitrary::<T>::default();
+        let mut domain = Arbitrary::<T>::default();
         let mut rng = get_rng();
         let mut value = domain.init(&mut rng).unwrap();
 
@@ -442,7 +525,7 @@
             + std::hash::Hash
             + 'static,
     {
-        let domain = Arbitrary::<T>::default();
+        let mut domain = Arbitrary::<T>::default();
         let mut rng = get_rng();
         for _ in 0..100 {
             let mut value = domain.init(&mut rng).unwrap();
@@ -474,7 +557,7 @@
             + NumTraitsExtended
             + 'static,
     {
-        let domain = Arbitrary::<T>::default();
+        let mut domain = Arbitrary::<T>::default();
         let mut rng = get_rng();
 
         // Get a value that is not the shrink target
@@ -619,7 +702,7 @@
     }
 
     fn test_bool_shrink() {
-        let domain = Arbitrary::<bool>::default();
+        let mut domain = Arbitrary::<bool>::default();
         let mut rng = get_rng();
         let mut value = true;
         domain.mutate(&mut value, &mut rng, true).unwrap();
@@ -636,7 +719,7 @@
     #[test]
     fn test_unit() {
         let mut rng = get_rng();
-        let domain = Arbitrary::<()>::default();
+        let mut domain = Arbitrary::<()>::default();
 
         // init() always returns ()
         assert_eq!(domain.init(&mut rng).unwrap(), ());
@@ -659,7 +742,7 @@
             Float + SampleUniform + std::fmt::Display + std::fmt::Debug + SpecialValues + 'static,
         StandardUniform: Distribution<T>,
     {
-        let domain = Arbitrary::<T>::default();
+        let mut domain = Arbitrary::<T>::default();
         let mut rng = get_rng();
 
         // Positive.
@@ -723,7 +806,7 @@
 
     #[test]
     fn test_char_mutate_boundaries() {
-        let domain = Arbitrary::<char>::default();
+        let mut domain = Arbitrary::<char>::default();
         let mut rng = get_rng();
         let mut val = '\u{0000}';
         domain.mutate(&mut val, &mut rng, false).unwrap();
@@ -762,7 +845,7 @@
     #[test]
     fn test_char_shrink_to_null() {
         for _ in 0..10 {
-            let domain = Arbitrary::<char>::default();
+            let mut domain = Arbitrary::<char>::default();
             let mut rng = get_rng();
             let mut value = domain.init(&mut rng).unwrap();
 
diff --git a/rust/src/domains/containers.rs b/rust/src/domains/containers.rs
index a15e28c..7dee293 100644
--- a/rust/src/domains/containers.rs
+++ b/rust/src/domains/containers.rs
@@ -1,6 +1,5 @@
 use rand::RngExt;
 use std::fmt;
-
 use super::Domain;
 
 const DEFAULT_MAX_LEN: usize = 5000;
@@ -113,7 +112,12 @@
 
 impl<T> VecOf<T> {
     pub fn new(inner: T) -> Self {
-        Self { inner, min_len: 0, max_len: None, max_len_is_soft: false }
+        Self {
+            inner,
+            min_len: 0,
+            max_len: None,
+            max_len_is_soft: false,
+        }
     }
 
     fn max_len(&self) -> usize {
@@ -128,7 +132,7 @@
     type CorpusValue = Vec<T::CorpusValue>;
     type UserValue<'user> = Vec<T::UserValue<'user>>;
 
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
         if self.max_len() == 0 {
             return Ok(Vec::new());
         }
@@ -143,7 +147,7 @@
     }
 
     fn mutate(
-        &self,
+        &mut self,
         val: &mut Self::CorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -193,9 +197,40 @@
         }
         Ok(user_values)
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        let mut corpus_values = Vec::with_capacity(value.len());
+        for item in value {
+            corpus_values.push(self.inner.from_value(item)?);
+        }
+        Ok(corpus_values)
+    }
+
+    fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result<()> {
+        if self.max_len_is_soft {
+            anyhow::ensure!(
+                self.min_len <= corpus_value.len(),
+                "Length {} is less than the minimum length {}",
+                corpus_value.len(),
+                self.min_len
+            );
+        } else {
+            anyhow::ensure!(
+                self.min_len <= corpus_value.len() && corpus_value.len() <= self.max_len(),
+                "Length {} is not between the minimum length {} and maximum length {}",
+                corpus_value.len(),
+                self.min_len,
+                self.max_len()
+            );
+        }
+        for item in corpus_value {
+            self.inner.validate_corpus_value(item)?;
+        }
+        Ok(())
+    }
 }
 
-impl<T> ContainerDomain for VecOf<T> {
+impl<T: Domain> ContainerDomain for VecOf<T> {
     fn with_len(self, len: usize) -> Self {
         Self { min_len: len, max_len: Some(len), ..self }
     }
@@ -245,7 +280,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_shrink() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10);
 
         let mut rng = get_rng();
 
@@ -264,7 +299,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_grow_and_change() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10);
 
         let mut rng = get_rng();
 
@@ -284,7 +319,7 @@
 
     #[gtest]
     fn test_vec_of_init_respects_min_len() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5);
         let mut rng = get_rng();
 
         for _ in 0..100 {
@@ -295,7 +330,7 @@
 
     #[gtest]
     fn test_vec_of_init_fixed_len() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(7);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_len(7);
         let mut rng = get_rng();
 
         for _ in 0..100 {
@@ -306,7 +341,7 @@
 
     #[gtest]
     fn test_vec_of_init_default_max_len() {
-        let domain = VecOf::new(Arbitrary::<u32>::default());
+        let mut domain = VecOf::new(Arbitrary::<u32>::default());
         let mut rng = get_rng();
 
         for _ in 0..100 {
@@ -317,7 +352,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_respects_min_len() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(3);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(3);
         let mut rng = get_rng();
 
         let mut val = vec![1, 2, 3];
@@ -329,7 +364,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_respects_max_len() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(3);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(3);
         let mut rng = get_rng();
 
         let mut val = vec![1, 2, 3];
@@ -341,7 +376,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_min_len_validation() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5);
         let mut rng = get_rng();
 
         let mut val = vec![1, 2, 3]; // Length 3, which is < 5
@@ -358,7 +393,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_soft_max_len_behavior() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_soft_max_len(5);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_soft_max_len(5);
         let mut rng = get_rng();
 
         // Valid mutation within bounds
@@ -394,7 +429,7 @@
 
     #[gtest]
     fn test_vec_of_mutate_no_action_at_bounds() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(1);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_len(1);
         let mut rng = get_rng();
 
         let mut val = vec![100u32];
@@ -415,7 +450,7 @@
 
     #[gtest]
     fn test_vec_of_zero_len() {
-        let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(0);
+        let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_len(0);
         let mut rng = get_rng();
 
         let val = domain.init(&mut rng).unwrap();
@@ -433,4 +468,5 @@
         let user_val = domain.get_user_value(&corpus_val).unwrap();
         expect_that!(user_val, container_eq(vec![1u32, 2u32, 3u32]));
     }
+
 }
diff --git a/rust/src/domains/range.rs b/rust/src/domains/range.rs
index 3bf413b..cb5f641 100644
--- a/rust/src/domains/range.rs
+++ b/rust/src/domains/range.rs
@@ -17,31 +17,32 @@
 use anyhow;
 use rand::distr::uniform::SampleUniform;
 use rand::distr::uniform::UniformSampler;
+use std::fmt;
 
 /// Generates values of type `T` in a given range.
-///
-/// For example, `InRange::new(0, 100)` generates integer
-/// values from the inclusive range `[0, 100]`.
-///
-/// Example usage:
-/// ```
-/// # use fuzztest::domains::Domain;
-/// # use fuzztest::domains::range::InRange;
-/// # use rand::prelude::*;
-///
-/// let range_i32 = InRange::new(21i32, 73);
-/// let sample = range_i32.init(&mut rand::rng());
-///
-/// assert!(sample.is_ok());
-/// let sample = sample.unwrap();
-/// assert!(sample >= 21);
-/// assert!(sample <= 73);
-/// ```
 pub struct InRange<T> {
     lower: T,
     upper: T,
 }
 
+impl<T: Clone> Clone for InRange<T> {
+    fn clone(&self) -> Self {
+        Self {
+            lower: self.lower.clone(),
+            upper: self.upper.clone(),
+        }
+    }
+}
+
+impl<T: fmt::Debug + Clone> fmt::Debug for InRange<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("InRange")
+            .field("lower", &self.lower)
+            .field("upper", &self.upper)
+            .finish()
+    }
+}
+
 impl InRange<i32> {
     pub fn new(lower: i32, upper: i32) -> Self {
         Self { lower, upper }
@@ -57,12 +58,12 @@
     type UserValue<'user> = i32;
     type CorpusValue = i32;
 
-    fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
+    fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
         Ok(self.get_in_range(rng))
     }
 
     fn mutate(
-        &self,
+        &mut self,
         val: &mut Self::CorpusValue,
         rng: &mut dyn rand::Rng,
         only_shrink: bool,
@@ -81,6 +82,20 @@
     ) -> anyhow::Result<Self::UserValue<'a>> {
         Ok(*corpus_value)
     }
+
+    fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> {
+        Ok(value)
+    }
+
+    fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result<()> {
+        if *corpus_value < self.lower || *corpus_value > self.upper {
+            anyhow::bail!(
+                "Value {} is out of range [{}, {}]",
+                corpus_value, self.lower, self.upper
+            );
+        }
+        Ok(())
+    }
 }
 
 #[cfg(test)]
diff --git a/rust/src/internal.rs b/rust/src/internal.rs
index 213e66a..5f7e95b 100644
--- a/rust/src/internal.rs
+++ b/rust/src/internal.rs
@@ -14,8 +14,11 @@
 
 use super::domains::GenericCorpusValue;
 use super::domains::GenericDomain;
+
 use std::collections::HashMap;
+use std::sync::Arc;
 use std::sync::LazyLock;
+use std::sync::Mutex;
 
 /// A trait implemented by types used to Fuzz a given property function.
 ///
@@ -35,7 +38,7 @@
     /// Returns `true` if the property function holds, `false` if it crashes.
     fn execute(&self, args: &GenericCorpusValue) -> bool;
     fn print_finding_report(&self);
-    fn domains(&self) -> &dyn GenericDomain;
+    fn domains(&self) -> Arc<Mutex<dyn GenericDomain>>;
 }
 
 /// Identifies the property function of a fuzz test.
diff --git a/rust/src/worker.rs b/rust/src/worker.rs
index 8eae57f..d4786f9 100644
--- a/rust/src/worker.rs
+++ b/rust/src/worker.rs
@@ -158,7 +158,9 @@
     }
 
     pub fn get_random_seed_input(&self, sink: &mut InputSink) {
-        match self.fuzz_test.domains().init(&mut rand::rng()) {
+        let domains = self.fuzz_test.domains();
+        let mut domains_guard = domains.lock().expect("Failed to lock domains");
+        match domains_guard.init(&mut rand::rng()) {
             Ok(val) => {
                 sink.emit(pack_input(val));
             }
@@ -170,8 +172,10 @@
 
     pub fn mutate(&self, origin: &GenericCorpusValue, shrink: bool, sink: &mut InputSink) {
         let mut mutant = origin.clone();
+        let domains = self.fuzz_test.domains();
+        let mut domains_guard = domains.lock().expect("Failed to lock domains");
 
-        if let Err(e) = self.fuzz_test.domains().mutate(&mut mutant, &mut rand::rng(), shrink) {
+        if let Err(e) = domains_guard.mutate(&mut mutant, &mut rand::rng(), shrink) {
             emit_error(&format!("Failed to mutate: {:?}", e));
             return;
         }
@@ -211,7 +215,9 @@
     }
 
     pub fn serialize_input_content(&self, input: &GenericCorpusValue, sink: &mut BytesSink) {
-        match self.fuzz_test.domains().serialize_corpus(input) {
+        let domains = self.fuzz_test.domains();
+        let domains_guard = domains.lock().expect("Failed to lock domains");
+        match domains_guard.serialize_corpus(input) {
             Ok(serialized) => {
                 sink.emit(&serialized);
             }
@@ -222,7 +228,9 @@
     }
 
     pub fn deserialize_input_content(&self, content: &[u8], sink: &mut InputSink) {
-        match self.fuzz_test.domains().parse_corpus(content) {
+        let domains = self.fuzz_test.domains();
+        let domains_guard = domains.lock().expect("Failed to lock domains");
+        match domains_guard.parse_corpus(content) {
             Ok(val) => {
                 sink.emit(pack_input(val));
             }
@@ -639,6 +647,8 @@
 
     let mut generic_corpus_value = fuzztest
         .domains()
+        .lock()
+        .expect("Failed to lock domains")
         .init(&mut rng)
         .expect("domain initialization should succeed to provide an initial corpus value");
 
@@ -649,6 +659,8 @@
     while start_time.elapsed() < smoke_test_duration {
         fuzztest
             .domains()
+            .lock()
+            .expect("Failed to lock domains")
             .mutate(&mut generic_corpus_value, &mut rng, only_shrink)
             .expect("domain mutation should succeed");
         let result = fuzztest.execute(&generic_corpus_value);
diff --git a/rust/tests/macro_compiles.rs b/rust/tests/macro_compiles.rs
index 5eee104..96b6d8a 100644
--- a/rust/tests/macro_compiles.rs
+++ b/rust/tests/macro_compiles.rs
@@ -15,6 +15,7 @@
 use fuzztest::domains::arbitrary::Arbitrary;
 use fuzztest::domains::containers::ContainerDomain;
 use fuzztest::domains::containers::VecOf;
+use fuzztest::domains::SeedableDomain;
 use fuzztest::fuzztest;
 
 #[fuzztest(_a = Arbitrary::<i32>::default())]
@@ -26,4 +27,10 @@
 #[fuzztest(_a = VecOf::new(Arbitrary::<i32>::default()).with_max_len(10))]
 fn fuzztest_macro_compiles_with_vec(_a: Vec<i32>) {}
 
+#[fuzztest(_a = Arbitrary::<i32>::default().with_seeds([1, 2, 3]))]
+fn fuzztest_macro_compiles_with_seeds(_a: i32) {}
+
+#[fuzztest(_a = Arbitrary::<i32>::default().with_seed_provider(|| vec![1, 2, 3]))]
+fn fuzztest_macro_compiles_with_seed_provider(_a: i32) {}
+
 fn main() {}