diff --git a/README.md b/README.md index 0d74307..b8517bb 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ You can use `rust_i18n::set_locale` to set the global locale at runtime, so that rust_i18n::set_locale("zh-CN"); let locale = rust_i18n::locale(); -assert_eq!(*locale, "zh-CN"); +assert_eq!(&*locale, "zh-CN"); ``` ### Extend Backend diff --git a/crates/support/Cargo.toml b/crates/support/Cargo.toml index b75e6e6..8cb7c1b 100644 --- a/crates/support/Cargo.toml +++ b/crates/support/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/longbridgeapp/rust-i18n" version = "3.0.0" [dependencies] +arc-swap = "1.6.0" globwalk = "0.8.1" once_cell = "1.10.0" proc-macro2 = "1.0" @@ -18,3 +19,4 @@ toml = "0.7.4" normpath = "1.1.1" lazy_static = "1" regex = "1" +triomphe = { version = "0.1.11", features = ["arc-swap"] } diff --git a/crates/support/src/atomic_str.rs b/crates/support/src/atomic_str.rs index db2ac05..ebe5636 100644 --- a/crates/support/src/atomic_str.rs +++ b/crates/support/src/atomic_str.rs @@ -1,80 +1,65 @@ use std::fmt; -use std::sync::atomic::{AtomicPtr, Ordering}; -use std::sync::Arc; +use std::ops::Deref; + +use arc_swap::{ArcSwapAny, Guard}; +use triomphe::Arc; /// A thread-safe atomically reference-counting string. -pub struct AtomicStr(AtomicPtr); +pub struct AtomicStr(ArcSwapAny>); + +/// A thread-safe view the string that was stored when `AtomicStr::as_str()` was called. +struct GuardedStr(Guard>); + +impl Deref for GuardedStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_str() + } +} impl AtomicStr { /// Create a new `AtomicStr` with the given value. - pub fn new(value: impl Into) -> Self { + pub fn new(value: &str) -> Self { let arced = Arc::new(value.into()); - Self(AtomicPtr::new(Arc::into_raw(arced) as _)) + Self(ArcSwapAny::new(arced)) } /// Get the string slice. - pub fn as_str(&self) -> &str { - unsafe { - let arced_ptr = self.0.load(Ordering::SeqCst); - assert!(!arced_ptr.is_null()); - &*arced_ptr - } - } - - /// Get the cloned inner `Arc`. - pub fn clone_string(&self) -> Arc { - unsafe { - let arced_ptr = self.0.load(Ordering::SeqCst); - assert!(!arced_ptr.is_null()); - Arc::increment_strong_count(arced_ptr); - Arc::from_raw(arced_ptr) - } + pub fn as_str(&self) -> impl Deref { + GuardedStr(self.0.load()) } - /// Replaces the value at self with src, returning the old value, without dropping either. - pub fn replace(&self, src: impl Into) -> Arc { - unsafe { - let arced_new = Arc::new(src.into()); - let arced_old_ptr = self.0.swap(Arc::into_raw(arced_new) as _, Ordering::SeqCst); - assert!(!arced_old_ptr.is_null()); - Arc::from_raw(arced_old_ptr) - } + /// Replaces the value at self with src. + pub fn replace(&self, src: impl Into) { + let arced = Arc::new(src.into()); + self.0.store(arced); } } -impl Drop for AtomicStr { - fn drop(&mut self) { - unsafe { - let arced_ptr = self.0.swap(std::ptr::null_mut(), Ordering::SeqCst); - assert!(!arced_ptr.is_null()); - let _ = Arc::from_raw(arced_ptr); - } +impl From<&str> for AtomicStr { + fn from(value: &str) -> Self { + Self::new(value) } } -impl AsRef for AtomicStr { - fn as_ref(&self) -> &str { - self.as_str() +impl fmt::Display for AtomicStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.as_str()) } } -impl From for AtomicStr -where - T: Into, -{ - fn from(value: T) -> Self { - Self::new(value) - } -} +#[cfg(test)] +mod tests { + use super::*; -impl From<&AtomicStr> for Arc { - fn from(value: &AtomicStr) -> Self { - value.clone_string() + fn test_str(s: &str) { + assert_eq!(s, "hello"); } -} -impl fmt::Display for AtomicStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) + #[test] + fn test_atomic_str() { + let s = AtomicStr::from("hello"); + test_str(&s.as_str()); } } diff --git a/src/lib.rs b/src/lib.rs index c2c8dbf..6809fb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,8 @@ #![doc = include_str!("../README.md")] +use std::ops::Deref; + use once_cell::sync::Lazy; -use std::sync::Arc; #[doc(hidden)] pub use once_cell; @@ -16,8 +17,8 @@ pub fn set_locale(locale: &str) { } /// Get current locale -pub fn locale() -> Arc { - CURRENT_LOCALE.clone_string() +pub fn locale() -> impl Deref { + CURRENT_LOCALE.as_str() } /// Replace patterns and return a new string. @@ -110,7 +111,7 @@ pub fn replace_patterns(input: &str, patterns: &[&str], values: &[String]) -> St macro_rules! t { // t!("foo") ($key:expr) => { - crate::_rust_i18n_translate(rust_i18n::locale().as_str(), $key) + crate::_rust_i18n_translate(&rust_i18n::locale(), $key) }; // t!("foo", locale = "en") @@ -184,7 +185,7 @@ mod tests { #[test] fn test_locale() { - assert_locale_type(locale().as_str(), CURRENT_LOCALE.as_str()); - assert_locale_type(&locale(), CURRENT_LOCALE.as_str()); + assert_locale_type(&locale(), &CURRENT_LOCALE.as_str()); + assert_eq!(locale().deref(), "en"); } } diff --git a/tests/multi_threading.rs b/tests/multi_threading.rs new file mode 100644 index 0000000..7d55b52 --- /dev/null +++ b/tests/multi_threading.rs @@ -0,0 +1,34 @@ +use std::ops::Add; +use std::thread::spawn; +use std::time::{Duration, Instant}; + +use rust_i18n::{set_locale, t}; + +rust_i18n::i18n!("locales", fallback = "en"); + +#[test] +fn test_load_and_store() { + let end = Instant::now().add(Duration::from_secs(3)); + let store = spawn(move || { + let mut i = 0u32; + while Instant::now() < end { + for _ in 0..100 { + i = i.wrapping_add(1); + if i % 2 == 0 { + set_locale(&format!("en-{i}")); + } else { + set_locale(&format!("fr-{i}")); + } + } + } + }); + let load = spawn(move || { + while Instant::now() < end { + for _ in 0..100 { + t!("hello"); + } + } + }); + store.join().unwrap(); + load.join().unwrap(); +}