diff --git a/Cargo.toml b/Cargo.toml index d94ff59..0625ccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,10 @@ name = "libshire" version = "0.1.0" edition = "2021" +[features] +default = ["std", "serde"] +alloc = ["serde?/alloc"] +std = ["serde?/std"] + [dependencies] serde = { version = "1", default-features = false, optional = true } diff --git a/src/convert.rs b/src/convert.rs index 2542396..4f382e7 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,4 +1,4 @@ -use std::convert::Infallible; +use core::convert::Infallible; /// Consumes an element of type `Infallible` and produces an element of any type `T`. This is /// possible because `Infallible` has no elements and thus this function can never be called, so diff --git a/src/either.rs b/src/either.rs index f3ba30f..935df28 100644 --- a/src/either.rs +++ b/src/either.rs @@ -1,9 +1,8 @@ -use std::{ +use core::{ convert::identity, fmt, hint, ops::{Deref, DerefMut}, - process::{ExitCode, Termination}, }; use crate::convert::{clone, clone_mut, copy, copy_mut, Empty}; @@ -586,14 +585,15 @@ where } } -impl Termination for Either +#[cfg(feature = "std")] +impl std::process::Termination for Either where - L: Termination, - R: Termination, + L: std::process::Termination, + R: std::process::Termination, { #[inline] - fn report(self) -> ExitCode { - self.fold(::report, ::report) + fn report(self) -> std::process::ExitCode { + self.fold(::report, ::report) } } diff --git a/src/hex.rs b/src/hex.rs index 99d6d20..9158bf7 100644 --- a/src/hex.rs +++ b/src/hex.rs @@ -1,5 +1,4 @@ -use std::{ - error, +use core::{ fmt::{self, Write}, marker::PhantomData, }; @@ -215,7 +214,8 @@ impl fmt::Display for ParseError { } } -impl error::Error for ParseError {} +#[cfg(feature = "std")] +impl std::error::Error for ParseError {} #[derive(Debug)] pub enum ArrayParseError { @@ -232,7 +232,8 @@ impl fmt::Display for ArrayParseError { } } -impl error::Error for ArrayParseError {} +#[cfg(feature = "std")] +impl std::error::Error for ArrayParseError {} impl From for ArrayParseError { fn from(err: ParseError) -> Self { @@ -244,8 +245,12 @@ impl From for ArrayParseError { mod tests { use super::*; + #[cfg(any(feature = "alloc", feature = "std"))] #[test] fn test_hex_bytes_debug() { + #[cfg(not(feature = "std"))] + use alloc::format; + assert_eq!( format!("{:?}", HexBytes::::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94])), "[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94]" @@ -257,8 +262,12 @@ mod tests { ); } + #[cfg(any(feature = "alloc", feature = "std"))] #[test] fn test_hex_bytes_display() { + #[cfg(not(feature = "std"))] + use alloc::string::ToString; + assert_eq!( HexBytes::::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94]).to_string(), "87e18faa888d434ef2b25de1a51ba094" diff --git a/src/lib.rs b/src/lib.rs index c2eec97..7434738 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,8 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc; + pub mod convert; pub mod either; pub mod hex; diff --git a/src/strings/capped.rs b/src/strings/capped.rs index 7b967d5..ae41e5b 100644 --- a/src/strings/capped.rs +++ b/src/strings/capped.rs @@ -1,13 +1,25 @@ -use std::{ - borrow::{self, Cow}, +use core::{ + borrow, cmp::Ordering, - error, fmt, hash::{Hash, Hasher}, ops, str, }; +#[cfg(not(feature = "std"))] +use core::convert::TryFrom; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + string::String, +}; + +#[cfg(feature = "std")] +use std::borrow::Cow; + #[derive(Clone)] pub struct CappedString { buf: [u8; N], @@ -97,10 +109,6 @@ impl CappedString { unsafe { str::from_utf8_unchecked_mut(slice) } } - pub fn into_string(self) -> String { - self.as_str().to_owned() - } - pub fn len(&self) -> usize { usize::from(self.len) } @@ -110,6 +118,17 @@ impl CappedString { } } +#[cfg(any(feature = "alloc", feature = "std"))] +impl CappedString { + pub fn into_boxed_str(self) -> Box { + self.as_str().into() + } + + pub fn into_string(self) -> String { + self.as_str().to_owned() + } +} + impl Default for CappedString { #[inline] fn default() -> Self { @@ -170,6 +189,7 @@ impl<'a, const N: usize> TryFrom<&'a str> for CappedString { } } +#[cfg(any(feature = "alloc", feature = "std"))] impl TryFrom for CappedString { type Error = Error; @@ -179,6 +199,7 @@ impl TryFrom for CappedString { } } +#[cfg(any(feature = "alloc", feature = "std"))] impl<'a, const N: usize> TryFrom> for CappedString { type Error = Error; @@ -188,6 +209,7 @@ impl<'a, const N: usize> TryFrom> for CappedString { } } +#[cfg(any(feature = "alloc", feature = "std"))] impl From> for String { #[inline] fn from(s: CappedString) -> Self { @@ -275,4 +297,5 @@ impl fmt::Display for Error { } } -impl error::Error for Error {} +#[cfg(feature = "std")] +impl std::error::Error for Error {} diff --git a/src/strings/experimental.rs b/src/strings/experimental.rs index 53b430a..9cddc3e 100644 --- a/src/strings/experimental.rs +++ b/src/strings/experimental.rs @@ -1,5 +1,5 @@ -use std::{ - borrow::{self, Cow}, +use core::{ + borrow, cmp::Ordering, convert::Infallible, fmt, @@ -12,6 +12,16 @@ use std::{ str, }; +#[cfg(not(feature = "std"))] +use alloc::{ + borrow::Cow, + boxed::Box, + string::String, +}; + +#[cfg(feature = "std")] +use std::borrow::Cow; + /// A non-growable string where strings 23 bytes or shorter are stored inline and longer strings /// use a separate heap allocation. If maximum inline lengths other than 23 are desired, see the /// more general [InliningString]. @@ -274,7 +284,7 @@ impl InliningString { #[inline] #[must_use] - pub fn into_string(self) -> String { + pub fn into_boxed_str(self) -> Box { match self.inline_string_len() { Some(len) => { // Get a pointer to the `inline` field of the union. @@ -296,7 +306,7 @@ impl InliningString { // of `InliningString`. let str_slice = unsafe { str::from_utf8_unchecked(bytes) }; - str_slice.to_owned() + Box::from(str_slice) }, None => { @@ -328,13 +338,18 @@ impl InliningString { // The boxed string is initialised, as we obtained it by moving `repr.boxed`, and // the only time `repr.boxed` is uninitialised is when it is briefly replaced with // a temporary value in the block above. - let box_str = unsafe { maybe_box_str.assume_init() }; - - box_str.into_string() + unsafe { maybe_box_str.assume_init() } }, } } + #[inline] + #[must_use] + pub fn into_string(self) -> String { + self.into_boxed_str() + .into_string() + } + #[inline] #[must_use] pub fn heap_allocated(&self) -> bool { @@ -519,19 +534,26 @@ impl<'de, const N: usize> serde::Deserialize<'de> for InliningString { #[cfg(test)] mod tests { + #[cfg(not(feature = "std"))] + use alloc::{ + borrow::{Cow, ToOwned}, + vec::Vec, + }; + + #[cfg(feature = "std")] use std::borrow::Cow; use super::*; #[test] fn test_align() { - use std::mem::align_of; + use core::mem::align_of; assert_eq!(align_of::(), align_of::>()); } #[test] fn test_niche() { - use std::mem::size_of; + use core::mem::size_of; assert_eq!(size_of::(), size_of::>()); } diff --git a/src/strings/fixed.rs b/src/strings/fixed.rs index 67f5764..b60744b 100644 --- a/src/strings/fixed.rs +++ b/src/strings/fixed.rs @@ -1,7 +1,6 @@ -use std::{ +use core::{ borrow, cmp::Ordering, - error, fmt, hash::{Hash, Hasher}, ops, @@ -191,7 +190,8 @@ impl fmt::Display for Error { } } -impl error::Error for Error {} +#[cfg(feature = "std")] +impl std::error::Error for Error {} #[cfg(test)] mod tests { diff --git a/src/strings/inlining.rs b/src/strings/inlining.rs index 301071d..0a5acf5 100644 --- a/src/strings/inlining.rs +++ b/src/strings/inlining.rs @@ -1,5 +1,5 @@ -use std::{ - borrow::{self, Cow}, +use core::{ + borrow, cmp::Ordering, convert::Infallible, fmt, @@ -8,12 +8,18 @@ use std::{ str::FromStr, }; +#[cfg(not(feature = "std"))] +use alloc::{borrow::Cow, boxed::Box, string::String}; + +#[cfg(feature = "std")] +use std::borrow::Cow; + use super::CappedString; /// A non-growable string where strings 22 bytes or shorter are stored inline and longer strings /// use a separate heap allocation. If maximum inline lengths other than 22 are desired, see the /// more general [InliningString]. -/// +/// /// 22 bytes is chosen because it is optimal for 64-bit architectures; the minimum possible size /// of the data structure on 64-bit architectures which always keeps the data properly aligned is /// 24 bytes (because, when heap-allocated, the data structure contains a 16-byte `Box<[u8]>` with @@ -24,23 +30,23 @@ pub type InliningString22 = InliningString<22>; /// A non-growable string which stores small strings inline; strings of length less than or equal /// to `N` are stored inside the data structure itself, whereas strings of length greater than `N` /// use a separate heap allocation. -/// +/// /// This type is intended to be used when lots of small strings need to be stored, and these /// strings do not need to grow. -/// +/// /// For 64-bit targets, `N = 22` allows the greatest amount of inline string data to be stored /// without exceeding the size of a regular [String]. Therefore, [InliningString22] is provided as /// a type alias for `InliningString<22>`. -/// +/// /// Although `N` is a `usize`, it may be no greater than `u8::MAX`; larger values will result in a /// compile-time error. -/// +/// /// ``` /// # use libshire::strings::InliningString; /// let s1 = InliningString::<22>::new("Hello, InliningString!"); /// assert_eq!(&*s1, "Hello, InliningString!"); /// assert!(!s1.heap_allocated()); -/// +/// /// let s2 = InliningString::<22>::new("This string is 23 bytes"); /// assert_eq!(&*s2, "This string is 23 bytes"); /// assert!(s2.heap_allocated()); @@ -51,7 +57,7 @@ pub struct InliningString(Repr); impl InliningString { /// Creates a new `InliningString` from the given string, storing the string data inline if /// possible or creating a new heap allocation otherwise. - /// + /// /// ``` /// # use libshire::strings::InliningString; /// let s = InliningString::<22>::new("Hello, InliningString!"); @@ -71,7 +77,7 @@ impl InliningString { } /// Returns a new empty `InliningString`. - /// + /// /// ``` /// # use libshire::strings::InliningString; /// let s = InliningString::<22>::empty(); @@ -103,6 +109,15 @@ impl InliningString { } } + #[inline] + #[must_use] + pub fn into_boxed_str(self) -> Box { + match self { + Self(Repr::Inline(buf)) => buf.into_boxed_str(), + Self(Repr::Boxed(buf)) => buf, + } + } + /// Consumes the `InliningString` and converts it to a heap-allocated `String`. #[inline] #[must_use] @@ -114,7 +129,7 @@ impl InliningString { } /// Returns the length of the string in bytes. - /// + /// /// ``` /// # use libshire::strings::InliningString; /// let s = InliningString::<22>::new("こんにちは"); @@ -130,12 +145,12 @@ impl InliningString { } /// Returns `true` if the string has length 0. - /// + /// /// ``` /// # use libshire::strings::InliningString; /// let s1 = InliningString::<22>::new(""); /// assert!(s1.is_empty()); - /// + /// /// let s2 = InliningString::<22>::new("Hello"); /// assert!(!s2.is_empty()); /// ``` @@ -149,12 +164,12 @@ impl InliningString { } /// Returns `true` if the string data is stored on the heap, and `false` otherwise. - /// + /// /// ``` /// # use libshire::strings::InliningString; /// let s1 = InliningString::<22>::new("This string's 22 bytes"); /// assert!(!s1.heap_allocated()); - /// + /// /// let s2 = InliningString::<22>::new("This string is 23 bytes"); /// assert!(s2.heap_allocated()); /// ``` @@ -304,7 +319,7 @@ impl fmt::Display for InliningString { impl serde::Serialize for InliningString { fn serialize(&self, serializer: S) -> Result where - S: serde::Serializer + S: serde::Serializer, { serde::Serialize::serialize(&**self, serializer) } @@ -314,10 +329,9 @@ impl serde::Serialize for InliningString { impl<'de, const N: usize> serde::Deserialize<'de> for InliningString { fn deserialize(deserializer: D) -> Result where - D: serde::Deserializer<'de> + D: serde::Deserializer<'de>, { - serde::Deserialize::deserialize(deserializer) - .map(Self::new::<&'de str>) + serde::Deserialize::deserialize(deserializer).map(Self::new::>) } } @@ -329,6 +343,10 @@ enum Repr { #[cfg(test)] mod tests { + #[cfg(not(feature = "std"))] + use alloc::borrow::{Cow, ToOwned}; + + #[cfg(feature = "std")] use std::borrow::Cow; use super::{InliningString, InliningString22}; @@ -341,7 +359,7 @@ mod tests { "Somethingfortheweekend", "Dichlorodifluoromethane", "こんにちは", - "❤️🧡💛💚💙💜" + "❤️🧡💛💚💙💜", ]; for s in test_strings { diff --git a/src/strings/mod.rs b/src/strings/mod.rs index cffe278..92aed64 100644 --- a/src/strings/mod.rs +++ b/src/strings/mod.rs @@ -1,8 +1,11 @@ +#[cfg(any(feature = "alloc", feature = "std"))] pub mod experimental; pub mod fixed; pub mod capped; +#[cfg(any(feature = "alloc", feature = "std"))] pub mod inlining; pub use fixed::{FixedString, Error as FixedStringError}; pub use capped::{CappedString, Error as CappedStringError}; +#[cfg(any(feature = "alloc", feature = "std"))] pub use inlining::{InliningString, InliningString22}; diff --git a/src/uuid.rs b/src/uuid.rs index 5b7371a..eb7570e 100644 --- a/src/uuid.rs +++ b/src/uuid.rs @@ -1,4 +1,4 @@ -use std::{error, fmt, str}; +use core::{fmt, str}; use crate::{hex, strings::FixedString}; @@ -222,7 +222,8 @@ impl fmt::Display for ParseError { } } -impl error::Error for ParseError {} +#[cfg(feature = "std")] +impl std::error::Error for ParseError {} #[derive(Debug)] pub enum UuidV5Error { @@ -237,7 +238,8 @@ impl fmt::Display for UuidV5Error { } } -impl error::Error for UuidV5Error {} +#[cfg(feature = "std")] +impl std::error::Error for UuidV5Error {} fn sha1(namespace: [u8; 16], name: &[u8]) -> Result<[u8; 20], UuidV5Error> { let (mut h0, mut h1, mut h2, mut h3, mut h4) = ( @@ -410,8 +412,12 @@ impl<'a> Iterator for Sha1ChunkIter<'a> { mod tests { use super::Uuid; + #[cfg(any(feature = "alloc", feature = "std"))] #[test] fn test_uuid_display() { + #[cfg(not(feature = "std"))] + use alloc::string::ToString; + assert_eq!( Uuid::from_bytes([ 0x12, 0x3e, 0x45, 0x67, 0xe8, 0x9b, 0x12, 0xd3, 0xa4, 0x56, 0x42, 0x66, 0x14, 0x17, diff --git a/test.sh b/test.sh index 4c6420a..691ad70 100755 --- a/test.sh +++ b/test.sh @@ -1,5 +1,9 @@ #!/bin/bash +cargo miri test --no-default-features --features serde + +cargo miri test --no-default-features --features alloc,serde + cargo miri test # 32-bit target