diff --git a/insta/src/comparator.rs b/insta/src/comparator.rs index de808637..4ae36a12 100644 --- a/insta/src/comparator.rs +++ b/insta/src/comparator.rs @@ -17,7 +17,10 @@ use crate::snapshot::{Snapshot, SnapshotContents, TextSnapshotKind}; /// /// This trait requires `'static` so that implementing structs can be stored in /// [`crate::settings::Settings`]. -pub trait Comparator: 'static { +// TODO: `Send + Sync` is required because `Settings` currently uses `Arc` +// internally. Consider removing these bounds if `Settings` switches to `Rc` +// in the next breaking change. +pub trait Comparator: Send + Sync + 'static { /// Returns `true` if the contents of `reference` and `test` match. /// /// This is the standard comparison used by [`assert_snapshot!`]. diff --git a/insta/src/redaction.rs b/insta/src/redaction.rs index 6f017aaa..61c7f67b 100644 --- a/insta/src/redaction.rs +++ b/insta/src/redaction.rs @@ -54,7 +54,7 @@ pub enum Redaction { /// Static redaction with new content. Static(Content), /// Redaction with new content. - Dynamic(Box) -> Content>), + Dynamic(Box) -> Content + Sync + Send>), } macro_rules! impl_from { @@ -127,7 +127,7 @@ impl<'a> From<&'a [u8]> for Redaction { pub fn dynamic_redaction(func: F) -> Redaction where I: Into, - F: Fn(Content, ContentPath<'_>) -> I + 'static, + F: Fn(Content, ContentPath<'_>) -> I + Send + Sync + 'static, { Redaction::Dynamic(Box::new(move |c, p| func(c, p).into())) } diff --git a/insta/src/settings.rs b/insta/src/settings.rs index 5446d6c4..3b3588ab 100644 --- a/insta/src/settings.rs +++ b/insta/src/settings.rs @@ -5,7 +5,7 @@ use std::future::Future; use std::mem; use std::path::{Path, PathBuf}; use std::pin::Pin; -use std::rc::Rc; +use std::sync::Arc; use std::task::{Context, Poll}; use crate::comparator::Comparator; @@ -23,7 +23,7 @@ thread_local!(static CURRENT_SETTINGS: RefCell = RefCell::new(Settings #[cfg(feature = "redactions")] #[cfg_attr(docsrs, doc(cfg(feature = "redactions")))] #[derive(Clone, Default)] -pub struct Redactions(Vec<(Selector<'static>, Rc)>); +pub struct Redactions(Vec<(Selector<'static>, Arc)>); #[cfg(feature = "redactions")] impl<'a> From> for Redactions { @@ -31,7 +31,7 @@ impl<'a> From> for Redactions { Redactions( value .into_iter() - .map(|x| (Selector::parse(x.0).unwrap().make_static(), Rc::new(x.1))) + .map(|x| (Selector::parse(x.0).unwrap().make_static(), Arc::new(x.1))) .collect(), ) } @@ -188,13 +188,17 @@ impl ActualSettings { /// ``` #[derive(Clone)] pub struct Settings { - inner: Rc, + // TODO: consider switching to `Rc` in the next breaking change — `Settings` + // are stored in thread-local storage and never cross thread boundaries, so + // `Arc` is unnecessary. Removing it would also let us drop the `Send + Sync` + // bounds on `Redaction` and `Comparator`. + inner: Arc, } impl Default for Settings { fn default() -> Settings { Settings { - inner: Rc::new(ActualSettings { + inner: Arc::new(ActualSettings { sort_maps: false, snapshot_path: "snapshots".into(), snapshot_suffix: "".into(), @@ -232,7 +236,7 @@ impl Settings { /// Internal helper for macros #[doc(hidden)] pub fn _private_inner_mut(&mut self) -> &mut ActualSettings { - Rc::make_mut(&mut self.inner) + Arc::make_mut(&mut self.inner) } /// Enables forceful sorting of maps before serialization. @@ -435,7 +439,7 @@ impl Settings { fn add_redaction_impl(&mut self, selector: &str, replacement: Redaction) { self._private_inner_mut().redactions.0.push(( Selector::parse(selector).unwrap().make_static(), - Rc::new(replacement), + Arc::new(replacement), )); } @@ -451,7 +455,7 @@ impl Settings { pub fn add_dynamic_redaction(&mut self, selector: &str, func: F) where I: Into, - F: Fn(Content, ContentPath<'_>) -> I + 'static, + F: Fn(Content, ContentPath<'_>) -> I + Send + Sync + 'static, { self.add_redaction(selector, dynamic_redaction(func)); } @@ -584,7 +588,7 @@ impl Settings { /// ``` pub fn bind_async, T>(&self, future: F) -> impl Future { struct BindingFuture { - settings: Rc, + settings: Arc, future: F, } @@ -660,7 +664,7 @@ impl Settings { /// This is to ensure tests under async runtimes like `tokio` don't show unexpected results #[must_use = "The guard is immediately dropped so binding has no effect. Use `let _guard = ...` to bind it."] pub struct SettingsBindDropGuard( - Option>, + Option>, /// A ZST that is not [`Send`] but is [`Sync`] /// /// This is necessary due to the lack of stable [negative impls](https://github.com/rust-lang/rust/issues/68318). @@ -677,3 +681,13 @@ impl Drop for SettingsBindDropGuard { }) } } + +// Prevent accidental removal of Send/Sync (which is a semver-breaking change). +const _: () = { + fn _assert_send_sync() {} + fn _assert() { + _assert_send_sync::(); + #[cfg(feature = "redactions")] + _assert_send_sync::(); + } +};