Browse Source

Add StoredOsString for storing OsStrings.

Kestrel 2 months ago
parent
commit
649caf3003
1 changed files with 162 additions and 0 deletions
  1. 162 0
      src/lib.rs

+ 162 - 0
src/lib.rs

@@ -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)
+    }
+}
+