diff options
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | src/hash.rs | 39 | ||||
| -rw-r--r-- | src/lib.rs | 9 | ||||
| -rw-r--r-- | src/non_empty_str.rs | 355 | ||||
| -rw-r--r-- | src/non_empty_string.rs | 378 |
6 files changed, 794 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..784eed7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.hgignore +.hg/ +.vscode/ +target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7739a36 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ministr" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..0e9ba6d --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,39 @@ +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; + +/// Hashes the string literal `s` to a `u64` using the Rust's [`default hasher`](DefaultHasher) (i.e. one used in the [`HashMap`](std::collections::HashMap)). +pub fn str_hash_default(s: &str) -> u64 { + let mut hasher = DefaultHasher::new(); + s.hash(&mut hasher); + hasher.finish() +} + +/// Hashes the string literal `s` to a `u32` using the FNV1a (32b) hash. +pub fn str_hash_fnv1a(s: &str) -> u32 { + const FNV1A32_PRIME: u32 = 0x0100_0193; + const FNV1A32_SEED: u32 = 0x811C_9DC5; + + let mut hash = FNV1A32_SEED; + + for byte in s.as_bytes() { + hash = (hash ^ *byte as u32).wrapping_mul(FNV1A32_PRIME); + } + + hash +} + +/// Hashes the string literal `s` to a `u64` using the FNV1a (64b) hash. +pub fn str_hash_fnv1a_64(s: &str) -> u64 { + const FNV1A64_PRIME: u64 = 0x0000_0100_0000_01B3; + const FNV1A64_SEED: u64 = 0xcbf2_9ce4_8422_2325; + + let mut hash = FNV1A64_SEED; + + for byte in s.as_bytes() { + hash = (hash ^ *byte as u64).wrapping_mul(FNV1A64_PRIME); + } + + hash +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..48b4a9c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +//! Exports some string utility types and functions. + +mod hash; +mod non_empty_str; +mod non_empty_string; + +pub use hash::*; +pub use non_empty_str::*; +pub use non_empty_string::*; diff --git a/src/non_empty_str.rs b/src/non_empty_str.rs new file mode 100644 index 0000000..83654f9 --- /dev/null +++ b/src/non_empty_str.rs @@ -0,0 +1,355 @@ +use { + crate::*, + std::{ + borrow::ToOwned, + cmp::PartialEq, + convert::TryFrom, + fmt::{Display, Formatter}, + ops::Deref, + }, +}; + +/// A non-empty string slice. +/// [`NonEmptyString`] is the owned version. +#[repr(transparent)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct NonEmptyStr(str); + +impl NonEmptyStr { + /// Tries to create a [`NonEmptyStr`] from the string slice `s`. + /// Returns `None` if the string `s` is empty. + pub fn new(s: &str) -> Option<&Self> { + if s.is_empty() { + None + } else { + Some(unsafe { Self::new_unchecked(s) }) + } + } + + /// Creates a [`NonEmptyStr`] from the string slice `s` + /// without checking if it is empty. + /// + /// # Safety + /// The caller guarantees the string `s` is not empty. + /// Passing an empty string slice is undefined behaviour. + /// + /// # Panics + /// Panics in debug configuration only if the `s` is empty. + pub unsafe fn new_unchecked(s: &str) -> &Self { + debug_assert!( + !s.is_empty(), + "tried to create a non-empty string slice from an empty source" + ); + &*(s as *const str as *const _) + } + + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn inner(&self) -> &str { + &self.0 + } +} + +impl Deref for NonEmptyStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.inner() + } +} + +impl<'s> AsRef<str> for &'s NonEmptyStr { + fn as_ref(&self) -> &str { + self.inner() + } +} + +impl ToOwned for NonEmptyStr { + type Owned = NonEmptyString; + + fn to_owned(&self) -> Self::Owned { + self.into() + } +} + +// Fallible conversions from string slices and owned strings. +//////////////////////////////////////////////////////////// +impl<'s> TryFrom<&'s str> for &'s NonEmptyStr { + type Error = (); + + fn try_from(s: &'s str) -> Result<Self, Self::Error> { + NonEmptyStr::new(s).ok_or(()) + } +} + +impl<'s> TryFrom<&'s String> for &'s NonEmptyStr { + type Error = (); + + fn try_from(s: &'s String) -> Result<Self, Self::Error> { + NonEmptyStr::new(s).ok_or(()) + } +} +//////////////////////////////////////////////////////////// + +// Infallible conversion from a non-empty owned string. +//////////////////////////////////////////////////////////// +impl<'s> From<&'s NonEmptyString> for &'s NonEmptyStr { + fn from(s: &'s NonEmptyString) -> Self { + s.as_ne_str() + } +} +//////////////////////////////////////////////////////////// + +// Conversions into string slices and owned strings. +// Conversion into a non-empty owned string is implemented by a `From` on it. +//////////////////////////////////////////////////////////// +impl<'s> Into<&'s str> for &'s NonEmptyStr { + fn into(self) -> &'s str { + self.as_str() + } +} + +impl<'s> Into<String> for &'s NonEmptyStr { + fn into(self) -> String { + self.0.to_owned() + } +} +//////////////////////////////////////////////////////////// + +// Comparsions. + +// <NonEmptyStr> +//////////////////////////////////////////////////////////// +impl PartialEq<&NonEmptyStr> for NonEmptyStr { + fn eq(&self, other: &&NonEmptyStr) -> bool { + PartialEq::eq(self, *other) + } + + fn ne(&self, other: &&NonEmptyStr) -> bool { + PartialEq::ne(self, *other) + } +} + +impl PartialEq<NonEmptyStr> for &NonEmptyStr { + fn eq(&self, other: &NonEmptyStr) -> bool { + PartialEq::eq(*self, other) + } + + fn ne(&self, other: &NonEmptyStr) -> bool { + PartialEq::ne(*self, other) + } +} +//////////////////////////////////////////////////////////// + +/// <str> +//////////////////////////////////////////////////////////// +impl PartialEq<str> for NonEmptyStr { + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.as_str(), other) + } + + fn ne(&self, other: &str) -> bool { + PartialEq::ne(self.as_str(), other) + } +} + +impl PartialEq<&str> for NonEmptyStr { + fn eq(&self, other: &&str) -> bool { + PartialEq::eq(self.as_str(), *other) + } + + fn ne(&self, other: &&str) -> bool { + PartialEq::ne(self.as_str(), *other) + } +} + +impl PartialEq<str> for &NonEmptyStr { + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.as_str(), other) + } + + fn ne(&self, other: &str) -> bool { + PartialEq::ne(self.as_str(), other) + } +} +//////////////////////////////////////////////////////////// + +/// <String> +//////////////////////////////////////////////////////////// +impl PartialEq<String> for NonEmptyStr { + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} + +impl PartialEq<&String> for NonEmptyStr { + fn eq(&self, other: &&String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &&String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} + +impl PartialEq<String> for &NonEmptyStr { + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} +//////////////////////////////////////////////////////////// + +/// <NonEmptyString> +//////////////////////////////////////////////////////////// +impl PartialEq<NonEmptyString> for NonEmptyStr { + fn eq(&self, other: &NonEmptyString) -> bool { + PartialEq::eq(self, other.as_ne_str()) + } + + fn ne(&self, other: &NonEmptyString) -> bool { + PartialEq::ne(self, other.as_ne_str()) + } +} + +impl PartialEq<&NonEmptyString> for NonEmptyStr { + fn eq(&self, other: &&NonEmptyString) -> bool { + PartialEq::eq(self, other.as_ne_str()) + } + + fn ne(&self, other: &&NonEmptyString) -> bool { + PartialEq::ne(self, other.as_ne_str()) + } +} + +impl PartialEq<NonEmptyString> for &NonEmptyStr { + fn eq(&self, other: &NonEmptyString) -> bool { + PartialEq::eq(self, other.as_ne_str()) + } + + fn ne(&self, other: &NonEmptyString) -> bool { + PartialEq::ne(self, other.as_ne_str()) + } +} +//////////////////////////////////////////////////////////// + +impl<'s> Display for &'s NonEmptyStr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.inner().fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn cmp(nes: &NonEmptyStr, s: &str) { + assert_eq!(nes, s); + //assert_eq!(s, nes); + assert_eq!(&nes[..], s); + assert_eq!(nes.as_ref(), s); + assert_eq!(nes.as_str(), s); + assert_eq!(nes.deref(), s); + assert_eq!(nes.inner(), s); + } + + #[test] + fn non_empty_str() { + let foo = "foo"; + let foo_str = foo.to_owned(); + let ne_foo_str = NonEmptyString::new(foo_str.clone()).unwrap(); + + // `new()` from non-empty slice + let ne_foo = NonEmptyStr::new(foo).unwrap(); + cmp(ne_foo, foo); + + assert_eq!(ne_foo, ne_foo); + assert_eq!(ne_foo, &ne_foo); + assert_eq!(&ne_foo, ne_foo); + assert_eq!(&ne_foo, &ne_foo); + assert_eq!(ne_foo, *ne_foo); + assert_eq!(*ne_foo, ne_foo); + assert_eq!(*ne_foo, *ne_foo); + + // `new()` from empty slice + let empty = ""; + assert!(NonEmptyStr::new(empty).is_none()); + + // `unchecked_new()` from non-empty slice + { + let ne_foo = unsafe { NonEmptyStr::new_unchecked(foo) }; + cmp(ne_foo, foo); + } + + // from `NonEmptyString`. + { + let ne_foo: &NonEmptyStr = (&ne_foo_str).into(); + cmp(ne_foo, foo); + + let ne_foo = <&NonEmptyStr as From<&NonEmptyString>>::from(&ne_foo_str); + cmp(ne_foo, foo); + } + + // try from non-empty slice + { + use std::convert::TryInto; + + let ne_foo: &NonEmptyStr = foo.try_into().unwrap(); + cmp(ne_foo, foo); + + let ne_foo = <&NonEmptyStr as TryFrom<&str>>::try_from(foo).unwrap(); + cmp(ne_foo, foo); + } + + // try from empty slice + { + use std::convert::TryInto; + + let ne_foo: Result<&NonEmptyStr, _> = "".try_into(); + assert!(ne_foo.is_err()); + + let ne_foo = <&NonEmptyStr as TryFrom<&str>>::try_from(""); + assert!(ne_foo.is_err()); + } + + // try from non-empty `String` + { + use std::convert::TryInto; + + let ne_foo: &NonEmptyStr = (&foo_str).try_into().unwrap(); + cmp(ne_foo, foo); + + let ne_foo = <&NonEmptyStr as TryFrom<&String>>::try_from(&foo_str).unwrap(); + cmp(ne_foo, foo); + } + + // try from empty `String` + { + use std::convert::TryInto; + + let empty_str = "".to_owned(); + + let ne_foo: Result<&NonEmptyStr, _> = (&empty_str).try_into(); + assert!(ne_foo.is_err()); + + let ne_foo = <&NonEmptyStr as TryFrom<&String>>::try_from(&empty_str); + assert!(ne_foo.is_err()); + } + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic(expected = "tried to create a non-empty string slice from an empty source")] + fn new_unchecked_panic() { + let _ = unsafe { NonEmptyStr::new_unchecked("") }; + } +} diff --git a/src/non_empty_string.rs b/src/non_empty_string.rs new file mode 100644 index 0000000..8ad7400 --- /dev/null +++ b/src/non_empty_string.rs @@ -0,0 +1,378 @@ +use { + crate::*, + std::{ + borrow::Borrow, + cmp::PartialEq, + convert::TryFrom, + fmt::{Display, Formatter}, + ops::Deref, + }, +}; + +/// A non-empty [`String`]. +/// [`NonEmptyStr`] is the borrowed version. +#[repr(transparent)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct NonEmptyString(String); + +impl NonEmptyString { + /// Tries to create a [`NonEmptyString`] from the string `s`. + /// Returns `None` if the string `s` is empty. + pub fn new(s: String) -> Option<Self> { + if s.is_empty() { + None + } else { + Some(Self(s)) + } + } + + /// Creates a [`NonEmptyString`] from the string `s` + /// without checking if it is empty. + /// + /// # Safety + /// The caller guarantees the string `s` is not empty. + /// Passing an empty string is undefined behaviour. + /// + /// # Panics + /// Panics in debug configuration only if the string `s` is empty. + pub unsafe fn new_unchecked(s: String) -> Self { + debug_assert!( + !s.is_empty(), + "tried to create a non-empty string from an empty source" + ); + Self(s) + } + + /// Creates a [`NonEmptyString`] from the [`non-empty string slice`](NonEmptyStr) `s`. + pub fn from(s: &NonEmptyStr) -> Self { + unsafe { NonEmptyString::new_unchecked(s.inner().to_owned()) } + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn as_ne_str(&self) -> &NonEmptyStr { + unsafe { NonEmptyStr::new_unchecked(&self.0) } + } + + pub fn inner(&self) -> &String { + &self.0 + } + + pub fn into_inner(self) -> String { + self.0 + } +} + +impl Deref for NonEmptyString { + type Target = NonEmptyStr; + + fn deref(&self) -> &Self::Target { + self.as_ne_str() + } +} + +impl AsRef<NonEmptyStr> for NonEmptyString { + fn as_ref(&self) -> &NonEmptyStr { + self.as_ne_str() + } +} + +impl AsRef<String> for NonEmptyString { + fn as_ref(&self) -> &String { + self.inner() + } +} + +impl AsRef<str> for NonEmptyString { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Borrow<NonEmptyStr> for NonEmptyString { + fn borrow(&self) -> &NonEmptyStr { + self.as_ne_str() + } +} + +// Fallible conversions from string slices and owned strings. +//////////////////////////////////////////////////////////// +impl<'s> TryFrom<&'s str> for NonEmptyString { + type Error = (); + + fn try_from(s: &'s str) -> Result<Self, Self::Error> { + Self::new(s.to_owned()).ok_or(()) + } +} + +impl TryFrom<String> for NonEmptyString { + type Error = (); + + fn try_from(s: String) -> Result<Self, Self::Error> { + Self::new(s).ok_or(()) + } +} +//////////////////////////////////////////////////////////// + +// Infallible conversion from a non-empty string slice. +//////////////////////////////////////////////////////////// +impl<'s> From<&'s NonEmptyStr> for NonEmptyString { + fn from(s: &'s NonEmptyStr) -> Self { + Self::from(s) + } +} +//////////////////////////////////////////////////////////// + +// Conversions into string slices and owned strings. +//////////////////////////////////////////////////////////// +impl<'s> Into<&'s str> for &'s NonEmptyString { + fn into(self) -> &'s str { + self.as_str() + } +} + +impl Into<String> for NonEmptyString { + fn into(self) -> String { + self.into_inner() + } +} +//////////////////////////////////////////////////////////// + +// Comparsions. + +// <NonEmptyString> +//////////////////////////////////////////////////////////// +impl PartialEq<&NonEmptyString> for NonEmptyString { + fn eq(&self, other: &&NonEmptyString) -> bool { + PartialEq::eq(self, *other) + } + + fn ne(&self, other: &&NonEmptyString) -> bool { + PartialEq::ne(self, *other) + } +} + +impl PartialEq<NonEmptyString> for &NonEmptyString { + fn eq(&self, other: &NonEmptyString) -> bool { + PartialEq::eq(*self, other) + } + + fn ne(&self, other: &NonEmptyString) -> bool { + PartialEq::ne(*self, other) + } +} +//////////////////////////////////////////////////////////// + +/// <str> +//////////////////////////////////////////////////////////// +impl PartialEq<str> for NonEmptyString { + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.as_str(), other) + } + + fn ne(&self, other: &str) -> bool { + PartialEq::ne(self.as_str(), other) + } +} + +impl PartialEq<&str> for NonEmptyString { + fn eq(&self, other: &&str) -> bool { + PartialEq::eq(self.as_str(), *other) + } + + fn ne(&self, other: &&str) -> bool { + PartialEq::ne(self.as_str(), *other) + } +} + +impl PartialEq<str> for &NonEmptyString { + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.as_str(), other) + } + + fn ne(&self, other: &str) -> bool { + PartialEq::ne(self.as_str(), other) + } +} +//////////////////////////////////////////////////////////// + +/// <String> +//////////////////////////////////////////////////////////// +impl PartialEq<String> for NonEmptyString { + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} + +impl PartialEq<&String> for NonEmptyString { + fn eq(&self, other: &&String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &&String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} + +impl PartialEq<String> for &NonEmptyString { + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} +//////////////////////////////////////////////////////////// + +/// <NonEmptyStr> +//////////////////////////////////////////////////////////// +impl PartialEq<NonEmptyStr> for NonEmptyString { + fn eq(&self, other: &NonEmptyStr) -> bool { + PartialEq::eq(self.as_ne_str(), other) + } + + fn ne(&self, other: &NonEmptyStr) -> bool { + PartialEq::ne(self.as_ne_str(), other) + } +} + +impl PartialEq<&NonEmptyStr> for NonEmptyString { + fn eq(&self, other: &&NonEmptyStr) -> bool { + PartialEq::eq(self.as_ne_str(), *other) + } + + fn ne(&self, other: &&NonEmptyStr) -> bool { + PartialEq::ne(self.as_ne_str(), *other) + } +} + +impl PartialEq<NonEmptyStr> for &NonEmptyString { + fn eq(&self, other: &NonEmptyStr) -> bool { + PartialEq::eq(self.as_ne_str(), other) + } + + fn ne(&self, other: &NonEmptyStr) -> bool { + PartialEq::ne(self.as_ne_str(), other) + } +} +//////////////////////////////////////////////////////////// + +impl Display for NonEmptyString { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.inner().fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn cmp(nes: &NonEmptyString, s: &str) { + assert_eq!(nes, s); + //assert_eq!(s, nes); + assert_eq!(&nes[..], s); + assert_eq!(nes.as_str(), s); + assert_eq!(nes.as_ne_str(), s); + assert_eq!(nes.deref(), s); + assert_eq!(nes.inner(), s); + } + + #[test] + fn non_empty_string() { + let foo = "foo"; + let foo_str = foo.to_owned(); + let ne_foo = NonEmptyStr::new(foo).unwrap(); + + // `new()` from non-empty string + let ne_foo_str = NonEmptyString::new(foo_str.clone()).unwrap(); + cmp(&ne_foo_str, foo); + + assert_eq!(ne_foo_str, ne_foo_str); + assert_eq!(ne_foo_str, &ne_foo_str); + assert_eq!(&ne_foo_str, ne_foo_str); + assert_eq!(&ne_foo_str, &ne_foo_str); + assert_eq!(ne_foo_str, *ne_foo_str); + assert_eq!(*ne_foo_str, ne_foo_str); + assert_eq!(*ne_foo_str, *ne_foo_str); + + // `new()` from empty string + let empty = ""; + assert!(NonEmptyString::new(empty.to_owned()).is_none()); + + // `unchecked_new()` from non-empty string + { + let ne_foo_str = unsafe { NonEmptyString::new_unchecked(foo_str.clone()) }; + cmp(&ne_foo_str, foo); + } + + // from `NonEmptyStr`. + { + let ne_foo_str: NonEmptyString = ne_foo.into(); + cmp(&ne_foo_str, foo); + + let ne_foo_str = <NonEmptyString as From<&NonEmptyStr>>::from(&ne_foo); + cmp(&ne_foo_str, foo); + } + + // try from non-empty slice + { + use std::convert::TryInto; + + let ne_foo_str: NonEmptyString = foo.try_into().unwrap(); + cmp(&ne_foo_str, foo); + + let ne_foo_str = <NonEmptyString as TryFrom<&str>>::try_from(foo).unwrap(); + cmp(&ne_foo_str, foo); + } + + // try from empty slice + { + use std::convert::TryInto; + + let ne_foo_str: Result<NonEmptyString, _> = "".try_into(); + assert!(ne_foo_str.is_err()); + + let ne_foo_str = <NonEmptyString as TryFrom<&str>>::try_from(""); + assert!(ne_foo_str.is_err()); + } + + // try from non-empty `String` + { + use std::convert::TryInto; + + let ne_foo_str: NonEmptyString = foo_str.clone().try_into().unwrap(); + cmp(&ne_foo_str, foo); + + let ne_foo_str = + <NonEmptyString as TryFrom<String>>::try_from(foo_str.clone()).unwrap(); + cmp(&ne_foo_str, foo); + } + + // try from empty `String` + { + use std::convert::TryInto; + + let empty_str = "".to_owned(); + + let ne_foo_str: Result<NonEmptyString, _> = empty_str.clone().try_into(); + assert!(ne_foo_str.is_err()); + + let ne_foo_str = <NonEmptyString as TryFrom<String>>::try_from(empty_str); + assert!(ne_foo_str.is_err()); + } + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic(expected = "tried to create a non-empty string from an empty source")] + fn new_unchecked_panic() { + let _ = unsafe { NonEmptyString::new_unchecked("".to_owned()) }; + } +} |