pub mod external {
pub use nohash_hasher;
pub use paste;
pub use serde;
}
#[inline]
fn hash(value: impl std::hash::Hash) -> u64 {
use std::hash::Hasher as _;
let mut hasher =
std::hash::BuildHasher::build_hasher(&ahash::RandomState::with_seeds(0, 1, 2, 3));
value.hash(&mut hasher);
hasher.finish()
}
#[derive(Copy, Clone, Eq, re_byte_size::SizeBytes)]
pub struct InternedString {
hash: u64, string: &'static str,
}
static_assertions::assert_not_impl_any!(InternedString: std::borrow::Borrow<str>);
impl InternedString {
#[inline]
pub fn new(string: &str) -> Self {
global_intern(string)
}
#[inline]
pub fn as_str(&self) -> &'static str {
self.string
}
#[inline]
pub fn hash(&self) -> u64 {
self.hash
}
}
impl From<&str> for InternedString {
#[inline]
fn from(string: &str) -> Self {
Self::new(string)
}
}
impl From<String> for InternedString {
#[inline]
fn from(string: String) -> Self {
Self::new(&string)
}
}
impl From<&String> for InternedString {
#[inline]
fn from(string: &String) -> Self {
Self::new(string)
}
}
impl std::cmp::PartialEq for InternedString {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
}
}
impl std::hash::Hash for InternedString {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
impl nohash_hasher::IsEnabled for InternedString {}
impl std::cmp::PartialOrd for InternedString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for InternedString {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.string.cmp(other.string)
}
}
impl AsRef<str> for InternedString {
#[inline]
fn as_ref(&self) -> &str {
self.string
}
}
impl std::ops::Deref for InternedString {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Debug for InternedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl std::fmt::Display for InternedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl serde::Serialize for InternedString {
#[inline]
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for InternedString {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer).map(|s| global_intern(&s))
}
}
#[derive(Default)]
struct StringInterner {
map: nohash_hasher::IntMap<u64, &'static str>,
}
impl StringInterner {
#[cfg_attr(not(test), expect(dead_code))] pub fn len(&self) -> usize {
self.map.len()
}
pub fn intern(&mut self, string: &str) -> InternedString {
let hash = hash(string);
let static_ref_string = self
.map
.entry(hash)
.or_insert_with(|| Box::leak(Box::<str>::from(string)));
InternedString {
hash,
string: static_ref_string,
}
}
pub fn bytes_used(&self) -> usize {
self.map
.iter()
.map(|(k, v): (_, &&str)| {
std::mem::size_of_val(k) + std::mem::size_of::<&str>() + v.len()
})
.sum()
}
}
#[macro_export]
macro_rules! intern_static {
($ty:ty, $lit:literal) => {{
static CACHED: ::std::sync::LazyLock<$ty> =
::std::sync::LazyLock::new(|| <$ty as ::std::convert::From<&str>>::from($lit));
*CACHED
}};
}
#[macro_export]
macro_rules! intern_static_nonempty {
($ty:ty, $lit:literal) => {{
const _: () = assert!(!$lit.is_empty(), "empty string literal");
static CACHED: ::std::sync::LazyLock<$ty> =
::std::sync::LazyLock::new(|| <$ty>::from_static_str($lit));
*CACHED
}};
}
#[macro_export]
macro_rules! declare_new_type {
(
$(#[$meta:meta])* // capture docstrings; see https://stackoverflow.com/questions/33999341/generating-documentation-in-macros
$vis:vis struct $StructName:ident;
) => {
$(#[$meta])*
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct $StructName($crate::InternedString);
impl $StructName {
#[inline]
pub fn new(string: &str) -> Self {
Self($crate::InternedString::new(string))
}
#[inline]
pub fn as_str(&self) -> &'static str {
self.0.as_str()
}
#[inline]
pub fn hash(&self) -> u64 {
self.0.hash()
}
}
impl $crate::external::nohash_hasher::IsEnabled for $StructName {}
impl From<&str> for $StructName {
#[inline]
fn from(string: &str) -> Self {
Self::new(string)
}
}
impl From<String> for $StructName {
#[inline]
fn from(string: String) -> Self {
Self::new(&string)
}
}
impl AsRef<str> for $StructName {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::ops::Deref for $StructName {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Debug for $StructName {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl std::fmt::Display for $StructName {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl<'a> PartialEq<&'a str> for $StructName {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl<'a> PartialEq<&'a str> for &$StructName {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl<'a> PartialEq<$StructName> for &'a str {
#[inline]
fn eq(&self, other: &$StructName) -> bool {
*self == other.as_str()
}
}
impl re_byte_size::SizeBytes for $StructName {
const IS_POD: bool = true;
#[inline]
fn heap_size_bytes(&self) -> u64 {
0
}
}
};
}
#[macro_export]
macro_rules! declare_new_type_nonempty {
(
$(#[$meta:meta])* // capture docstrings; see https://stackoverflow.com/questions/33999341/generating-documentation-in-macros
$vis:vis struct $StructName:ident;
) => {
$crate::external::paste::paste! {
$(#[$meta])*
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct $StructName($crate::InternedString);
#[doc = "Error returned when constructing an invalid [`" $StructName "`]."]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct [<Invalid $StructName Error>] {
reason: &'static str,
}
impl std::fmt::Display for [<Invalid $StructName Error>] {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, concat!("Invalid `", stringify!($StructName), "`: {}"), self.reason)
}
}
impl std::fmt::Debug for [<Invalid $StructName Error>] {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, concat!("Invalid", stringify!($StructName), "Error({:?})"), self.reason)
}
}
impl std::error::Error for [<Invalid $StructName Error>] {}
impl $StructName {
#[inline]
fn validate(string: &str) -> Result<(), [<Invalid $StructName Error>]> {
if string.is_empty() {
return Err([<Invalid $StructName Error>] { reason: "must not be empty" });
}
Ok(())
}
#[inline]
pub fn try_new(string: impl AsRef<str>) -> Result<Self, [<Invalid $StructName Error>]> {
let string = string.as_ref();
Self::validate(string)?;
Ok(Self($crate::InternedString::new(string)))
}
#[inline]
pub fn from_static_str(string: &'static str) -> Self {
match Self::validate(string) {
Ok(()) => Self($crate::InternedString::new(string)),
Err(err) => panic!("{err} (got {string:?})"),
}
}
#[inline]
pub fn as_str(&self) -> &'static str {
self.0.as_str()
}
#[inline]
pub fn hash(&self) -> u64 {
self.0.hash()
}
}
impl $crate::external::nohash_hasher::IsEnabled for $StructName {}
impl TryFrom<String> for $StructName {
type Error = [<Invalid $StructName Error>];
#[inline]
fn try_from(string: String) -> Result<Self, Self::Error> {
Self::try_new(string)
}
}
impl From<&'static str> for $StructName {
#[inline]
fn from(string: &'static str) -> Self {
Self::from_static_str(string)
}
}
impl AsRef<str> for $StructName {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::ops::Deref for $StructName {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Debug for $StructName {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl std::fmt::Display for $StructName {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl<'a> PartialEq<&'a str> for $StructName {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl<'a> PartialEq<&'a str> for &$StructName {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl<'a> PartialEq<$StructName> for &'a str {
#[inline]
fn eq(&self, other: &$StructName) -> bool {
*self == other.as_str()
}
}
impl re_byte_size::SizeBytes for $StructName {
const IS_POD: bool = true;
#[inline]
fn heap_size_bytes(&self) -> u64 {
0
}
}
impl $crate::external::serde::Serialize for $StructName {
#[inline]
fn serialize<S: $crate::external::serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
$crate::external::serde::Serialize::serialize(self.as_str(), serializer)
}
}
impl<'de> $crate::external::serde::Deserialize<'de> for $StructName {
#[inline]
fn deserialize<D: $crate::external::serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
use $crate::external::serde::de::Error as _;
let string = <String as $crate::external::serde::Deserialize>::deserialize(
deserializer,
)?;
Self::try_new(string).map_err(D::Error::custom)
}
}
}
};
}
use parking_lot::Mutex;
static GLOBAL_INTERNER: std::sync::LazyLock<Mutex<StringInterner>> =
std::sync::LazyLock::new(|| Mutex::new(StringInterner::default()));
pub fn bytes_used() -> usize {
GLOBAL_INTERNER.lock().bytes_used()
}
fn global_intern(string: &str) -> InternedString {
GLOBAL_INTERNER.lock().intern(string)
}
#[test]
fn test_interner() {
let mut interner = StringInterner::default();
assert_eq!(interner.len(), 0);
let a = interner.intern("Hello World!");
assert_eq!(interner.len(), 1);
let b = interner.intern("Hello World!");
assert_eq!(interner.len(), 1);
assert_eq!(a, b);
let c = interner.intern("Another string");
assert_eq!(interner.len(), 2);
assert!(a.hash == b.hash);
assert!(a.hash != c.hash);
}
#[test]
fn test_newtype_macro() {
declare_new_type!(
pub struct MyString;
);
let a = MyString::new("test");
let b = MyString::new("test");
assert_eq!(a, b);
assert_eq!(a.as_str(), "test");
}
#[test]
fn do_not_implement_borrow() {
declare_new_type!(
pub struct MyString;
);
static_assertions::assert_not_impl_any!(MyString: std::borrow::Borrow<str>);
}
#[test]
fn test_nonempty_newtype_macro() {
declare_new_type_nonempty!(
pub struct MyNonEmptyString;
);
assert!(MyNonEmptyString::try_new("").is_err());
assert!(MyNonEmptyString::try_new(String::new()).is_err());
assert!(MyNonEmptyString::try_from(String::new()).is_err());
let a = MyNonEmptyString::try_new("test").expect("non-empty");
let b = MyNonEmptyString::try_from("test".to_owned()).expect("non-empty");
assert_eq!(a, b);
assert_eq!(a.as_str(), "test");
assert_eq!(a, "test");
assert_eq!("test", a);
let c = MyNonEmptyString::from_static_str("test");
assert_eq!(a, c);
let d: MyNonEmptyString = "test".into();
assert_eq!(a, d);
fn takes(_: impl Into<MyNonEmptyString>) {}
takes("test");
let err = MyNonEmptyString::try_new("").unwrap_err();
let msg = std::string::ToString::to_string(&err);
assert!(msg.contains("MyNonEmptyString"), "{msg:?}");
assert!(msg.contains("must not be empty"), "{msg:?}");
let _: &dyn std::error::Error = &err;
}
#[test]
#[should_panic(expected = "must not be empty")]
fn test_nonempty_from_static_str_panics_on_empty() {
declare_new_type_nonempty!(
pub struct MyNonEmptyString;
);
let _ = MyNonEmptyString::from_static_str("");
}
#[test]
#[should_panic(expected = "must not be empty")]
fn test_nonempty_from_empty_static_str_panics() {
declare_new_type_nonempty!(
pub struct MyNonEmptyString;
);
let _val: MyNonEmptyString = "".into();
}
#[test]
fn nonempty_do_not_implement_borrow() {
declare_new_type_nonempty!(
pub struct MyNonEmptyString;
);
static_assertions::assert_not_impl_any!(MyNonEmptyString: std::borrow::Borrow<str>);
}