|
@@ -47,6 +47,8 @@
|
|
|
//!
|
|
|
//! The use of type aliases is highly encouraged.
|
|
|
|
|
|
+use std::ffi::{OsString, OsStr};
|
|
|
+
|
|
|
static STR_STORE: std::sync::LazyLock<std::sync::Mutex<std::collections::HashSet<&'static str>>> =
|
|
|
std::sync::LazyLock::new(Default::default);
|
|
|
|
|
@@ -247,3 +249,163 @@ mod test_stored_string {
|
|
|
assert_eq!(ss, ss3);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+static OS_STR_STORE: std::sync::LazyLock<std::sync::Mutex<std::collections::HashSet<&'static OsStr>>> =
|
|
|
+ std::sync::LazyLock::new(Default::default);
|
|
|
+
|
|
|
+/// See crate documentation for general description.
|
|
|
+pub struct StoredOsString<Tag: 'static> {
|
|
|
+ stored: &'static OsStr,
|
|
|
+ _ghost: std::marker::PhantomData<Tag>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> Clone for StoredOsString<Tag> {
|
|
|
+ fn clone(&self) -> Self {
|
|
|
+ Self { stored: self.stored, _ghost: Default::default() }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> Copy for StoredOsString<Tag> { }
|
|
|
+
|
|
|
+impl<Tag: 'static> std::ops::Deref for StoredOsString<Tag> {
|
|
|
+ type Target = OsStr;
|
|
|
+ fn deref(&self) -> &Self::Target {
|
|
|
+ self.stored
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> AsRef<OsStr> for StoredOsString<Tag> {
|
|
|
+ fn as_ref(&self) -> &OsStr {
|
|
|
+ self.stored
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: NamespaceTag> std::fmt::Debug for StoredOsString<Tag> {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ f.write_fmt(format_args!("{}:{:?}", Tag::PREFIX, self.stored))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> std::hash::Hash for StoredOsString<Tag> {
|
|
|
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
+ // pointers are unique per content by construction
|
|
|
+ (self.stored as *const OsStr).hash(state);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> PartialEq for StoredOsString<Tag> {
|
|
|
+ fn eq(&self, other: &Self) -> bool {
|
|
|
+ // pointers are unique per content by construction
|
|
|
+ self.stored.as_encoded_bytes().as_ptr() == other.stored.as_encoded_bytes().as_ptr()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> Eq for StoredOsString<Tag> {
|
|
|
+ fn assert_receiver_is_total_eq(&self) {}
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> PartialOrd for StoredOsString<Tag> {
|
|
|
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
|
+ Some(<Self as Ord>::cmp(self, other))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> Ord for StoredOsString<Tag> {
|
|
|
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
|
+ self.stored.as_encoded_bytes().as_ptr().cmp(&other.stored.as_encoded_bytes().as_ptr())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag> StoredOsString<Tag> {
|
|
|
+ pub fn new(from: &OsStr) -> Self {
|
|
|
+ let mut mg = OS_STR_STORE.lock().expect("couldn't lock STR_STORE?");
|
|
|
+ match mg.get(from) {
|
|
|
+ Some(name) => Self {
|
|
|
+ stored: name,
|
|
|
+ _ghost: Default::default(),
|
|
|
+ },
|
|
|
+ None => {
|
|
|
+ let s = Box::leak(from.to_owned().into_boxed_os_str());
|
|
|
+ mg.insert(s);
|
|
|
+ Self {
|
|
|
+ stored: mg.get(from).unwrap(),
|
|
|
+ _ghost: Default::default(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn as_str(&self) -> &'static OsStr {
|
|
|
+ self.stored
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Coerce a StoredOsString between namespaces. This exists solely to change the type and
|
|
|
+ /// performs no actual work under the hood.
|
|
|
+ pub fn coerce<Tag2>(&self) -> StoredOsString<Tag2> {
|
|
|
+ StoredOsString {
|
|
|
+ stored: self.stored,
|
|
|
+ _ghost: Default::default(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(feature = "serde")]
|
|
|
+impl<'de, Tag> serde::de::Visitor<'de> for StoredOsString<Tag> {
|
|
|
+ type Value = Self;
|
|
|
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
+ formatter.write_fmt(format_args!("stored string"))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
|
|
+ where
|
|
|
+ E: serde::de::Error,
|
|
|
+ {
|
|
|
+ Ok(Self::new(v))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn visit_string<E>(self, v: OsString) -> Result<Self::Value, E>
|
|
|
+ where
|
|
|
+ E: serde::de::Error,
|
|
|
+ {
|
|
|
+ Ok(Self::new(v.as_str()))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(feature = "serde")]
|
|
|
+impl<'de, Tag> serde::de::Deserialize<'de> for StoredOsString<Tag> {
|
|
|
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
+ where
|
|
|
+ D: serde::Deserializer<'de>,
|
|
|
+ {
|
|
|
+ deserializer.deserialize_str(Self::new(""))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(feature = "serde")]
|
|
|
+impl<Tag> serde::ser::Serialize for StoredOsString<Tag> {
|
|
|
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
+ where
|
|
|
+ S: serde::Serializer,
|
|
|
+ {
|
|
|
+ serializer.serialize_str(self.name)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<Tag: 'static> From<OsString> for StoredOsString<Tag> {
|
|
|
+ fn from(value: OsString) -> Self {
|
|
|
+ Self::new(value.as_os_str())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'l, Tag: 'static> From<&'l OsString> for StoredOsString<Tag> {
|
|
|
+ fn from(value: &'l OsString) -> Self {
|
|
|
+ Self::new(value.as_os_str())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'l, Tag: 'static> From<&'l OsStr> for StoredOsString<Tag> {
|
|
|
+ fn from(value: &'l OsStr) -> Self {
|
|
|
+ Self::new(value)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|