diff --git a/src/bytes.rs b/src/bytes.rs index 0c23104..88d8ea1 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -237,6 +237,30 @@ where } } + /// Returns the borrowed slice if this `HipByt` is actually borrowed, `None` + /// otherwise. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipByt; + /// static SEQ: &[u8] = &[1 ,2, 3]; + /// let s = HipByt::borrowed(SEQ); + /// let c: Option<&'static [u8]> = s.as_borrowed(); + /// assert_eq!(c, Some(SEQ)); + /// assert!(std::ptr::eq(SEQ, c.unwrap())); + /// + /// let s2 = HipByt::from(SEQ); + /// assert!(s2.as_borrowed().is_none()); + /// ``` + #[inline] + pub const fn as_borrowed(&self) -> Option<&'borrow [u8]> { + match self.split() { + RawSplit::Allocated(_) | RawSplit::Inline(_) => None, + RawSplit::Borrowed(borrowed) => Some(borrowed.as_slice()), + } + } + /// Returns `true` if this `HipByt` is a shared heap-allocated byte sequence, `false` otherwise. /// /// # Examples diff --git a/src/bytes/tests.rs b/src/bytes/tests.rs index 186fc08..c9b4ae2 100644 --- a/src/bytes/tests.rs +++ b/src/bytes/tests.rs @@ -241,7 +241,9 @@ fn test_clone_drop() { fn test_into_borrowed() { // static let a = H::borrowed(ABC); - assert_eq!(a.into_borrowed(), Ok(ABC)); + let s = a.into_borrowed().unwrap(); + assert_eq!(s, ABC); + assert!(core::ptr::eq(s, ABC)); // inline let a = H::from(ABC); @@ -254,6 +256,23 @@ fn test_into_borrowed() { assert_eq!(a.into_borrowed(), Err(b)); } +#[test] +fn test_as_borrowed() { + // borrowed + let a = H::borrowed(ABC); + let b = a.as_borrowed().unwrap(); + assert_eq!(b, ABC); + assert!(core::ptr::eq(b, ABC)); + + // inline + let a = H::from(ABC); + assert_eq!(a.as_borrowed(), None); + + // heap + let a = H::from(MEDIUM); + assert_eq!(a.as_borrowed(), None); +} + #[test] fn test_as_mut_slice() { // static diff --git a/src/os_string.rs b/src/os_string.rs index 061d72d..a4c7a94 100644 --- a/src/os_string.rs +++ b/src/os_string.rs @@ -243,6 +243,35 @@ where .map_err(Self) } + /// Returns the borrowed slice if this `HipOsStr` is actually borrowed, + /// `None` otherwise. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipOsStr; + /// # use std::ffi::OsStr; + /// let abc: &'static OsStr = OsStr::new("abc"); + /// let s = HipOsStr::borrowed(abc); + /// let c: Option<&'static OsStr> = s.as_borrowed(); + /// assert_eq!(c, Some(abc)); + /// assert!(std::ptr::eq(abc, c.unwrap())); + /// + /// let s2 = HipOsStr::from(abc); + /// assert!(s2.as_borrowed().is_none()); + /// ``` + #[inline] + pub const fn as_borrowed(&self) -> Option<&'borrow OsStr> { + match self.0.as_borrowed() { + Some(slice) => { + // SAFETY: type invariant + // transmute to be "const" + Some(unsafe { core::mem::transmute(slice) }) + } + None => None, + } + } + /// Returns the length of this `HipOsStr`, in bytes, not [`char`]s or /// graphemes. In other words, it might not be what a human considers the /// length of the string. diff --git a/src/os_string/tests.rs b/src/os_string/tests.rs index 3efa9b9..1b1c6db 100644 --- a/src/os_string/tests.rs +++ b/src/os_string/tests.rs @@ -185,8 +185,8 @@ fn test_clone() { } #[test] -fn test_into_static() { - // static +fn test_into_borrowed() { + // borrowed let a = HipOsStr::borrowed("abc"); assert_eq!(a.into_borrowed(), Ok("abc".as_ref())); @@ -201,6 +201,26 @@ fn test_into_static() { assert_eq!(a.into_borrowed(), Err(b)); } +#[test] +fn test_as_borrowed() { + let abc: &'static OsStr = OsStr::new("abc"); + let medium: &'static OsStr = OsStr::new("abcdefghijklmnopqrstuvwxyz"); + + // borrowed + let a = H::borrowed(abc); + let b = a.as_borrowed().unwrap(); + assert_eq!(b, abc); + assert!(core::ptr::eq(b, abc)); + + // inline + let a = H::from(abc); + assert_eq!(a.as_borrowed(), None); + + // heap + let a = H::from(medium); + assert_eq!(a.as_borrowed(), None); +} + #[test] fn test_into_bytes() { let s = HipOsStr::from("A".repeat(42)); diff --git a/src/path.rs b/src/path.rs index f11c519..df593ae 100644 --- a/src/path.rs +++ b/src/path.rs @@ -218,6 +218,35 @@ where self.0.into_borrowed().map(Path::new).map_err(Self) } + /// Returns the borrowed slice if this `Path` is actually borrowed, `None` + /// otherwise. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipPath; + /// # use std::path::Path; + /// let abc: &'static Path = Path::new("abc"); + /// let s = HipPath::borrowed(abc); + /// let c: Option<&'static Path> = s.as_borrowed(); + /// assert_eq!(c, Some(abc)); + /// assert!(std::ptr::eq(abc, c.unwrap())); + /// + /// let s2 = HipPath::from(abc); + /// assert!(s2.as_borrowed().is_none()); + /// ``` + #[inline] + pub const fn as_borrowed(&self) -> Option<&'borrow Path> { + match self.0.as_borrowed() { + Some(slice) => { + // SAFETY: type invariant + // transmute to be "const" + Some(unsafe { core::mem::transmute(slice) }) + } + None => None, + } + } + /// Converts a `HipPath` into a `HipOsStr`. /// /// It consumes the `HipPath` without copying the content diff --git a/src/path/tests.rs b/src/path/tests.rs index 6c954dc..88c7def 100644 --- a/src/path/tests.rs +++ b/src/path/tests.rs @@ -7,22 +7,24 @@ use crate::alloc::string::String; use crate::os_string::HipOsStr; use crate::HipPath; +type H<'borrow> = HipPath<'borrow>; + const INLINE_CAPACITY: usize = HipPath::inline_capacity(); #[test] fn test_deref() { - let h = HipPath::borrowed("test"); + let h = H::borrowed("test"); let _: &Path = &h; } #[test] fn test_new_default() { let empty_path: &Path = "".as_ref(); - let new = HipPath::new(); + let new = H::new(); assert_eq!(new, empty_path); assert!(new.0.is_empty()); - let new = HipPath::default(); + let new = H::default(); assert_eq!(new, empty_path); assert!(new.0.is_empty()); } @@ -30,8 +32,8 @@ fn test_new_default() { #[test] fn test_borrow_and_hash() { let mut set = HashSet::new(); - set.insert(HipPath::from("a")); - set.insert(HipPath::from("b")); + set.insert(H::from("a")); + set.insert(H::from("b")); assert!(set.contains::("a".as_ref())); assert!(!set.contains::("c".as_ref())); @@ -40,14 +42,14 @@ fn test_borrow_and_hash() { #[test] fn test_fmt() { let source: &OsStr = "Rust \u{1F980}".as_ref(); - let a = HipPath::borrowed(source); + let a = H::borrowed(source); assert_eq!(format!("{a:?}"), format!("{source:?}")); } #[test] fn test_from_string() { let s = "A".repeat(42); - let hs = HipPath::from(s.clone()); + let hs = H::from(s.clone()); assert!(!hs.is_borrowed()); assert!(!hs.is_inline()); assert!(hs.is_allocated()); @@ -58,7 +60,7 @@ fn test_from_string() { #[test] fn test_borrowed() { let s = "0123456789"; - let path = HipPath::borrowed(s); + let path = H::borrowed(s); assert!(path.is_borrowed()); assert!(!path.is_inline()); assert_eq!(path.0.len(), s.len()); @@ -71,7 +73,7 @@ fn test_from_static() { const fn is_static_type(_: &T) {} let s = "abcdefghijklmnopqrstuvwxyz"; - let path = HipPath::from_static(s); + let path = H::from_static(s); // compiler check is_static_type(&path); @@ -90,7 +92,7 @@ fn test_from_slice() { let s = core::str::from_utf8(V).unwrap(); for size in [0, 1, INLINE_CAPACITY, INLINE_CAPACITY + 1, 256, 1024] { - let path = HipPath::from(&s[..size]); + let path = H::from(&s[..size]); assert_eq!(size <= INLINE_CAPACITY, path.is_inline()); assert_eq!(size > INLINE_CAPACITY, path.is_allocated()); assert_eq!(path.0.len(), size); @@ -101,7 +103,7 @@ fn test_from_slice() { fn test_as_slice() { // static { - let a = HipPath::borrowed("abc"); + let a = H::borrowed("abc"); assert!(a.is_borrowed()); assert!(!a.is_inline()); assert!(!a.is_allocated()); @@ -109,7 +111,7 @@ fn test_as_slice() { } // inline { - let a = HipPath::from("abc"); + let a = H::from("abc"); assert!(!a.is_borrowed()); assert!(a.is_inline()); assert!(!a.is_allocated()); @@ -118,7 +120,7 @@ fn test_as_slice() { // allocated { let s = "A".repeat(42); - let a = HipPath::from(s.as_str()); + let a = H::from(s.as_str()); assert!(!a.is_borrowed()); assert!(!a.is_inline()); assert!(a.is_allocated()); @@ -131,7 +133,7 @@ fn test_clone() { // static { let s: &'static str = "abc"; - let a = HipPath::borrowed(s); + let a = H::borrowed(s); assert!(a.is_borrowed()); let b = a.clone(); drop(a); @@ -141,7 +143,7 @@ fn test_clone() { // inline { - let a = HipPath::from("abc"); + let a = H::from("abc"); assert!(a.is_inline()); let b = a.clone(); drop(a); @@ -152,7 +154,7 @@ fn test_clone() { { let s = "a".repeat(42); let p = s.as_ptr(); - let a = HipPath::from(s); + let a = H::from(s); assert!(a.is_allocated()); let b = a.clone(); drop(a); @@ -162,25 +164,45 @@ fn test_clone() { } #[test] -fn test_into_static() { - // static - let a = HipPath::borrowed("abc"); +fn test_into_borrowed() { + // borrowed + let a = H::borrowed("abc"); assert_eq!(a.into_borrowed(), Ok("abc".as_ref())); // inline - let a = HipPath::from("abc"); + let a = H::from("abc"); let b = a.clone(); assert_eq!(a.into_borrowed(), Err(b)); // heap - let a = HipPath::from("a".repeat(42).as_str()); + let a = H::from("a".repeat(42).as_str()); let b = a.clone(); assert_eq!(a.into_borrowed(), Err(b)); } +#[test] +fn test_as_borrowed() { + let abc: &'static Path = Path::new("abc"); + let medium: &'static Path = Path::new("abcdefghijklmnopqrstuvwxyz"); + + // borrowed + let a = H::borrowed(abc); + let b = a.as_borrowed().unwrap(); + assert_eq!(b, abc); + assert!(core::ptr::eq(b, abc)); + + // inline + let a = H::from(abc); + assert_eq!(a.as_borrowed(), None); + + // heap + let a = H::from(medium); + assert_eq!(a.as_borrowed(), None); +} + #[test] fn test_into_os_string() { - let h = HipPath::from("A".repeat(42)); + let h = H::from("A".repeat(42)); let os_string = h.into_os_string().unwrap(); assert_eq!(os_string.len(), 42); assert_eq!(os_string.as_encoded_bytes(), [b'A'; 42]); @@ -188,7 +210,7 @@ fn test_into_os_string() { #[test] fn test_into_path_buf() { - let h = HipPath::from("A".repeat(42)); + let h = H::from("A".repeat(42)); let path_buf = h.into_path_buf().unwrap(); assert_eq!(path_buf.as_os_str().len(), 42); assert_eq!(path_buf, Path::new(&"A".repeat(42))); @@ -196,7 +218,7 @@ fn test_into_path_buf() { #[test] fn test_into_str() { - let h = HipPath::from("A".repeat(42)); + let h = H::from("A".repeat(42)); let string = h.into_str().unwrap(); assert_eq!(string.len(), 42); assert_eq!(string, "A".repeat(42)); @@ -207,37 +229,37 @@ fn test_into_str() { use std::os::windows::ffi::OsStringExt; let shorts = [u16::from(b'a'), u16::from(b'b'), u16::from(b'c'), 0xD800]; let source = OsString::from_wide(&shorts); - let hp = HipPath::from(source); + let hp = H::from(source); let _ = hp.into_str().unwrap_err(); } #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; let bytes = b"abc\x80"; - let hp = HipPath::from(OsStr::from_bytes(bytes)); + let hp = H::from(OsStr::from_bytes(bytes)); let _ = hp.into_str().unwrap_err(); } } #[test] fn test_capacity() { - let a = HipPath::borrowed("abc"); + let a = H::borrowed("abc"); assert_eq!(a.capacity(), a.0.len()); - let a = HipPath::from("abc"); - assert_eq!(a.capacity(), HipPath::inline_capacity()); + let a = H::from("abc"); + assert_eq!(a.capacity(), H::inline_capacity()); let mut v = String::with_capacity(42); for _ in 0..10 { v.push_str("abc"); } - let a = HipPath::from(v); + let a = H::from(v); assert_eq!(a.capacity(), 42); } #[test] fn test_mutate_debug() { - let mut a = HipPath::borrowed("abc"); + let mut a = H::borrowed("abc"); let debug = format!("{a:?}"); let r = a.mutate(); assert_eq!(format!("{r:?}"), debug); @@ -245,7 +267,7 @@ fn test_mutate_debug() { #[test] fn test_mutate_borrowed() { - let mut a = HipPath::borrowed("abc"); + let mut a = H::borrowed("abc"); assert!(a.is_borrowed()); { let mut r = a.mutate(); @@ -258,7 +280,7 @@ fn test_mutate_borrowed() { #[test] fn test_mutate_inline() { - let mut a = HipPath::from("abc"); + let mut a = H::from("abc"); assert!(a.is_inline()); a.mutate().push("def"); assert_eq!(a, Path::new("abc/def")); @@ -271,7 +293,7 @@ fn test_mutate_allocated() { let mut v = String::with_capacity(42); v.push_str("abcdefghijklmnopqrstuvwxyz"); let p = v.as_ptr(); - let mut a = HipPath::from(v); + let mut a = H::from(v); assert!(a.is_allocated()); a.mutate().push("0123456789"); assert!(a.is_allocated()); @@ -283,7 +305,7 @@ fn test_mutate_allocated() { // allocated, shared let mut v = String::with_capacity(42); v.push_str("abcdefghijklmnopqrstuvwxyz"); - let mut a = HipPath::from(v); + let mut a = H::from(v); assert!(a.is_allocated()); let b = a.clone(); a.mutate().push("0123456789"); @@ -297,7 +319,7 @@ fn test_mutate_allocated() { #[test] fn test_to_owned() { let b = "abc"; - let h = HipPath::from(b); + let h = H::from(b); assert!(h.is_inline()); let h = h.into_owned(); assert!(h.is_inline()); @@ -305,12 +327,12 @@ fn test_to_owned() { let r = "*".repeat(42); let v = r.clone(); - let a = HipPath::borrowed(&v[0..2]); + let a = H::borrowed(&v[0..2]); let a = a.into_owned(); drop(v); assert_eq!(a, Path::new(&r[0..2])); - let a = HipPath::from(&r[..]); + let a = H::from(&r[..]); drop(r); let p = a.0.as_ptr(); let a = a.into_owned(); @@ -320,15 +342,15 @@ fn test_to_owned() { #[test] fn test_shrink_to_fit() { let h = HipOsStr::with_capacity(INLINE_CAPACITY + 1); - let mut h = HipPath::from(h); + let mut h = H::from(h); h.shrink_to_fit(); assert_eq!(h.capacity(), INLINE_CAPACITY); - let mut h = HipPath::from("abc"); + let mut h = H::from("abc"); h.shrink_to_fit(); assert_eq!(h.capacity(), INLINE_CAPACITY); - let mut h = HipPath::from_static("abc"); + let mut h = H::from_static("abc"); h.shrink_to_fit(); assert_eq!(h.capacity(), 3); } @@ -337,16 +359,16 @@ fn test_shrink_to_fit() { fn test_shrink_to() { let mut h = HipOsStr::with_capacity(INLINE_CAPACITY + 1); h.push("a"); - let mut h = HipPath::from(h); + let mut h = H::from(h); h.shrink_to(0); assert_eq!(h.capacity(), INLINE_CAPACITY); assert_eq!(h.as_os_str().len(), 1); - let mut h = HipPath::from("abc"); + let mut h = H::from("abc"); h.shrink_to(4); assert_eq!(h.capacity(), INLINE_CAPACITY); - let mut h = HipPath::from_static("abc"); + let mut h = H::from_static("abc"); assert_eq!(h.capacity(), 3); h.shrink_to(0); assert_eq!(h.capacity(), 3); diff --git a/src/string.rs b/src/string.rs index 5c4b332..1d97b23 100644 --- a/src/string.rs +++ b/src/string.rs @@ -242,6 +242,33 @@ where .map_err(Self) } + /// Returns the borrowed string slice if this `HipStr` is actually borrowed, + /// `None` otherwise. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipStr; + /// static S: &str = "abc"; + /// let s = HipStr::borrowed(S); + /// let c: Option<&'static str> = s.as_borrowed(); + /// assert_eq!(c, Some(S)); + /// assert!(std::ptr::eq(S, c.unwrap())); + /// + /// let s2 = HipStr::from(S); + /// assert!(s2.as_borrowed().is_none()); + /// ``` + #[inline] + pub const fn as_borrowed(&self) -> Option<&'borrow str> { + match self.0.as_borrowed() { + Some(slice) => Some(unsafe { + // SAFETY: type invariant + core::str::from_utf8_unchecked(slice) + }), + None => None, + } + } + /// Returns the length of this `HipStr`, in bytes, not [`char`]s or /// graphemes. In other words, it might not be what a human considers the /// length of the string. diff --git a/src/string/tests.rs b/src/string/tests.rs index de4bb9a..73a220c 100644 --- a/src/string/tests.rs +++ b/src/string/tests.rs @@ -219,9 +219,11 @@ fn test_clone() { #[test] fn test_into_borrowed() { - // static + // borrowed let a = H::borrowed(ABC); - assert_eq!(a.into_borrowed(), Ok(ABC)); + let s = a.into_borrowed().unwrap(); + assert_eq!(s, ABC); + assert!(core::ptr::eq(s, ABC)); // inline let a = H::from(ABC); @@ -234,6 +236,23 @@ fn test_into_borrowed() { assert_eq!(a.into_borrowed(), Err(b)); } +#[test] +fn test_as_borrowed() { + // borrowed + let a = H::borrowed(ABC); + let b = a.as_borrowed().unwrap(); + assert_eq!(b, ABC); + assert!(core::ptr::eq(b, ABC)); + + // inline + let a = H::from(ABC); + assert_eq!(a.as_borrowed(), None); + + // heap + let a = H::from(MEDIUM); + assert_eq!(a.as_borrowed(), None); +} + #[test] fn test_as_mut_str() { // static