diff --git a/CHANGELOG.md b/CHANGELOG.md index 728bea0..00517cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,24 @@ Notable changes only. -## Unreleased +## [0.8.0] - 2025-02-10 + +### Added + +- add `as_ptr` to strings. +- add `as_mut_ptr` and `as_mut_ptr_unchecked` to strings and bytes ### Changed - improve feature testing in CI +- improve doc +- update some dev dependencies ### Fixed +- remove accidentally mandatory dependency to `serde` - fix `bstr` feature in `no_std` +- fix some clippy lints ## [0.7.0] - 2025-01-10 @@ -46,7 +55,7 @@ Notable changes only. - implement `core::error:Error` for custom errors, rather than `std::error::Error` and bump msrv -### Fixe +### Fixed - fix doc issue [#28](https://github.com/polazarus/hipstr/issues/28) - fix MIRI check due to provenance loss @@ -163,8 +172,9 @@ Most of those addition are breaking because they shadows `str`'s methods. Initial release - + +[0.8.0]: https://github.com/polazarus/hipstr/compare/0.7.0...0.8.0 [0.7.0]: https://github.com/polazarus/hipstr/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/polazarus/hipstr/compare/0.5.1...0.6.0 [0.5.1]: https://github.com/polazarus/hipstr/compare/0.5.0...0.5.1 diff --git a/Cargo.toml b/Cargo.toml index 4bc6e45..505840f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hipstr" -version = "0.7.0" +version = "0.8.0" authors = ["Polazarus "] description = """Yet another string for Rust: zero-cost borrow and slicing, inline representation for small strings, (atomic) reference counting""" @@ -17,23 +17,18 @@ all-features = true [features] default = ["std"] -std = ["serde/std"] +std = ["serde?/std"] unstable = [] serde = ["dep:serde"] bstr = ["dep:bstr"] borsh = ["dep:borsh"] [dev-dependencies] -fastrand = "2.0.0" -serde_test = "1.0.176" -serde = { version = "1.0.100", default-features = false, features = [ - "derive", - "alloc", -] } -serde_json = { version = "1.0.45", default-features = false, features = [ - "alloc", -] } -divan = "0.1.15" +fastrand = "2.3.0" +serde_test = "1.0.177" +serde = { version = "1.0.100", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0.45", default-features = false, features = ["alloc"] } +divan = "0.1.17" [dependencies.bstr] version = "1.3" diff --git a/README.md b/README.md index 2952ee3..5510249 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ let _clone = simple_greetings.clone(); // no copy let user = "John"; let greetings = HipStr::from(format!("Hello {}", user)); -let user = greetings.slice(6..): // no copy +let user = greetings.slice(6..); // no copy drop(greetings); // the slice is owned, it exists even if greetings disappear let chars = user.chars().count(); // "inherits" `&str` methods @@ -96,7 +96,7 @@ cross test --target i686-unknown-linux-gnu # 32-bit LE cross test --target x86_64-unknown-linux-gnu # 64-bit LE ``` -NB: previously I used MIPS targets for big endian, but due to some LLVM-related +Note: previously I used MIPS targets for big endian, but due to some LLVM-related issue they are not working anymore… see [Rust issue #113065](https://github.com/rust-lang/rust/issues/113065) diff --git a/src/backend.rs b/src/backend.rs index 8d2e4c4..23ef25a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,12 +1,8 @@ //! Sealed backend trait and the built-in implementations. -/// Shared (thread-safe) reference counted backend. #[cfg(target_has_atomic = "ptr")] pub use crate::smart::Arc; -/// Use a local reference counted backend. -pub use crate::smart::Rc; -/// Use a unique reference. -pub use crate::smart::Unique; +pub use crate::smart::{Rc, Unique}; #[deprecated(note = "renamed to Rc")] pub type Local = crate::smart::Rc; diff --git a/src/bytes.rs b/src/bytes.rs index 736e37b..a73bd36 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -8,6 +8,7 @@ use alloc::vec::Vec; use core::borrow::Borrow; use core::error::Error; use core::hash::Hash; +use core::hint::unreachable_unchecked; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::{Bound, Deref, DerefMut, Range, RangeBounds}; use core::ptr; @@ -53,10 +54,14 @@ where /// /// Function provided for [`Vec::new`] replacement. /// - /// # ⚠️ Warning! + /// # Representation + /// + ///
/// /// The used representation of the empty string is unspecified. - /// It may be *borrowed* or *inlined* but will never be allocated. + /// It may be _borrowed_ or _inlined_ but will never be allocated. + /// + ///
/// /// # Examples /// @@ -75,6 +80,10 @@ where /// Creates a new inline `HipByt` by copying the given slice. /// The slice **must not** be too large to be inlined. /// + /// # Representation + /// + /// The created `HipByt` is _inline_. + /// /// # Panics /// /// It panics if the slice is too large. @@ -99,6 +108,10 @@ where /// Creates a new inline `HipByt` by copying the given the slice. /// Return `None` if the given slice is too large to be inlined. /// + /// # Representation + /// + /// In case of success, the created `HipByt` is _inline_. + /// /// # Examples /// /// Basic usage: @@ -120,7 +133,18 @@ where /// Creates a new `HipByt` with the given capacity. /// - /// The underlying representation is not **normalized**. + /// The final capacity depends on the representation and is not guaranteed + /// to be exact. However, the returned `HipByt` will be able to hold at + /// least `capacity` bytes without reallocating or changing representation. + /// + /// # Representation + /// + /// If the capacity is less or equal to the inline capacity, the + /// representation will be *inline*. + /// + /// Otherwise, it will be *allocated*. + /// + /// The representation is **not normalized**. /// /// # Examples /// @@ -208,6 +232,164 @@ where self.len() == 0 } + /// Returns a raw pointer to the start of the byte sequence. + /// + /// The caller must ensure the `HipByt` outlives the pointer this function + /// returns, or else it will end up dangling. + /// Modifying the byte sequence may change representation or reallocate, + /// which would invalid the returned pointer. + #[inline] + #[must_use] + pub const fn as_ptr(&self) -> *const u8 { + match self.split() { + Split::Inline(inline) => inline.as_ptr(), + Split::Allocated(heap) => heap.as_ptr(), + Split::Borrowed(borrowed) => borrowed.as_ptr(), + } + } + + /// Returns a raw mutable pointer to the start of the byte sequence. + /// + /// The caller must ensure the `HipByt` outlives the pointer this function + /// returns, or else it will end up dangling. + /// Modifying the byte sequence may change representation or reallocate, + /// which would invalid the returned pointer. + #[inline] + #[must_use] + pub fn as_mut_ptr(&mut self) -> Option<*mut u8> { + match self.split_mut() { + SplitMut::Inline(inline) => Some(inline.as_mut_ptr()), + SplitMut::Allocated(heap) => heap.as_mut_ptr(), + SplitMut::Borrowed(_) => None, + } + } + + /// Returns a raw mutable pointer to the start of the byte sequence. + /// + /// The caller must ensure the `HipByt` outlives the pointer this function + /// returns, or else it will end up dangling. Modifying the byte sequence + /// may change representation or reallocate, which would invalid the + /// returned pointer. + /// + /// # Safety + /// + /// The caller must ensure the sequence is actually unique: not shared and + /// not borrowed. + /// + /// # Panics + /// + /// In debug mode, this function panics if the sequence is borrowed or + /// shared. + #[inline] + #[must_use] + pub unsafe fn as_mut_ptr_unchecked(&mut self) -> *mut u8 { + match self.split_mut() { + SplitMut::Inline(inline) => inline.as_mut_ptr(), + SplitMut::Allocated(heap) => unsafe { heap.as_mut_ptr_unchecked() }, + SplitMut::Borrowed(_) => { + if cfg!(debug_assertions) { + panic!("mutable pointer of borrowed string"); + } else { + unsafe { + unreachable_unchecked(); + } + } + } + } + } + + /// Extracts a slice of the entire `HipByt`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use hipstr::HipByt; + /// let s = HipByt::from(b"foobar"); + /// + /// assert_eq!(b"foobar", s.as_slice()); + /// ``` + #[inline] + #[must_use] + pub const fn as_slice(&self) -> &[u8] { + match self.split() { + Split::Inline(inline) => inline.as_slice(), + Split::Allocated(heap) => heap.as_slice(), + Split::Borrowed(borrowed) => borrowed.as_slice(), + } + } + + /// Extracts a mutable slice of the entire `HipByt` if possible. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use hipstr::HipByt; + /// let mut s = HipByt::from(b"foo"); + /// let slice = s.as_mut_slice().unwrap(); + /// slice.copy_from_slice(b"bar"); + /// assert_eq!(b"bar", slice); + /// ``` + #[inline] + #[must_use] + pub fn as_mut_slice(&mut self) -> Option<&mut [u8]> { + match self.split_mut() { + SplitMut::Inline(inline) => Some(inline.as_mut_slice()), + SplitMut::Allocated(allocated) => allocated.as_mut_slice(), + SplitMut::Borrowed(_) => None, + } + } + + /// Extracts a mutable slice of the entire `HipByt`. + /// + /// # Safety + /// + /// This `HipByt` should not be shared or borrowed. + /// + /// # Panics + /// + /// In debug mode, panics if the sequence is borrowed or shared. + #[inline] + pub unsafe fn as_mut_slice_unchecked(&mut self) -> &mut [u8] { + match self.split_mut() { + SplitMut::Inline(inline) => inline.as_mut_slice(), + SplitMut::Allocated(allocated) => unsafe { allocated.as_mut_slice_unchecked() }, + SplitMut::Borrowed(_) => { + if cfg!(debug_assertions) { + panic!("mutable slice of borrowed string"); + } else { + unsafe { unreachable_unchecked() } + } + } + } + } + + /// Extracts a mutable slice of the entire `HipByt` changing the + /// representation (and thus _potentially reallocating_) if the current + /// representation cannot be mutated. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use hipstr::HipByt; + /// let mut s = HipByt::borrowed(b"foo"); + /// let slice = s.to_mut_slice(); // change the representation to inline + /// slice.copy_from_slice(b"bar"); + /// assert_eq!(b"bar", slice); + /// ``` + #[inline] + #[doc(alias = "make_mut")] + pub fn to_mut_slice(&mut self) -> &mut [u8] { + self.make_unique(); + // SAFETY: `make_unique` above ensures that it is uniquely owned + unsafe { self.as_mut_slice_unchecked() } + } + /// Returns `true` if this `HipByt` uses the inline representation, `false` otherwise. /// /// # Examples @@ -259,7 +441,7 @@ where /// /// # Errors /// - /// Returns `Err(self)` if this `HipByt` is not a borrow. + /// Returns `Err(self)` if this `HipByt` is not borrowed. /// /// # Examples /// @@ -421,29 +603,6 @@ where } } - /// Extracts a mutable slice of the entire `HipByt` changing the - /// representation (and thus _potentially reallocating_) if the current - /// representation cannot be mutated. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// # use hipstr::HipByt; - /// let mut s = HipByt::borrowed(b"foo"); - /// let slice = s.to_mut_slice(); // change the representation to inline - /// slice.copy_from_slice(b"bar"); - /// assert_eq!(b"bar", slice); - /// ``` - #[inline] - #[doc(alias = "make_mut")] - pub fn to_mut_slice(&mut self) -> &mut [u8] { - self.make_unique(); - // SAFETY: `make_unique` above ensures that it is uniquely owned - unsafe { self.as_mut_slice_unchecked() } - } - /// Extracts a slice as its own `HipByt`. /// /// # Panics @@ -496,7 +655,7 @@ where /// /// # Safety /// - /// `range` must be a equivalent to some `a..b` with `a <= b <= len`. + /// `range` must be equivalent to some `a..b` with `a <= b <= len`. /// /// Panics in debug mode. UB in release mode. #[must_use] @@ -880,7 +1039,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -1279,10 +1438,14 @@ impl HipByt<'static, B> where B: Backend, { - /// Creates a new `HipByt` from a static slice without copying the slice. + /// Creates a new `HipByt` from a `'static` slice without copying the slice. /// /// Handy shortcut to make a `HipByt<'static, _>` out of a `&'static [u8]`. /// + /// # Representation + /// + /// The created `HipByt` is _borrowed_. + /// /// # Examples /// /// Basic usage: diff --git a/src/bytes/borsh.rs b/src/bytes/borsh.rs index ad4eaa5..d25cfff 100644 --- a/src/bytes/borsh.rs +++ b/src/bytes/borsh.rs @@ -9,7 +9,7 @@ use crate::Backend; #[cfg(test)] mod tests; -impl<'borrow, B: Backend> BorshDeserialize for HipByt<'borrow, B> { +impl BorshDeserialize for HipByt<'_, B> { fn deserialize_reader(reader: &mut R) -> io::Result { let len = u32::deserialize_reader(reader)? as usize; if len == 0 { @@ -17,8 +17,8 @@ impl<'borrow, B: Backend> BorshDeserialize for HipByt<'borrow, B> { } else { let mut result = Self::with_capacity(len); let slice = result.spare_capacity_mut(); - for i in 0..len { - slice[i].write(u8::deserialize_reader(reader)?); + for byte in slice.iter_mut().take(len) { + byte.write(u8::deserialize_reader(reader)?); } unsafe { result.set_len(len); @@ -28,7 +28,7 @@ impl<'borrow, B: Backend> BorshDeserialize for HipByt<'borrow, B> { } } -impl<'borrow, B: Backend> BorshSerialize for HipByt<'borrow, B> { +impl BorshSerialize for HipByt<'_, B> { fn serialize(&self, writer: &mut W) -> io::Result<()> { self.as_slice().serialize(writer) } diff --git a/src/bytes/raw.rs b/src/bytes/raw.rs index 94a6400..98440dd 100644 --- a/src/bytes/raw.rs +++ b/src/bytes/raw.rs @@ -48,9 +48,10 @@ pub type Inline = inline::Inline; /// /// # Examples /// -/// You can create a `HipStr` from a [byte slice (&`[u8]`)][slice], an owned byte string -/// ([`Vec`], [`Box<[u8]>`][std::boxed::Box]), or a clone-on-write smart pointer -/// ([`Cow<[u8]>`][std::borrow::Cow]) with [`From`]: +/// You can create a `HipStr` from a [byte slice (&`[u8]`)][slice], an owned +/// byte string ([`Vec`], [`Box<[u8]>`][std::boxed::Box]), or a +/// clone-on-write smart pointer ([`Cow<[u8]>`][std::borrow::Cow]) with +/// [`From`]: /// /// ``` /// # use hipstr::HipByt; @@ -65,7 +66,8 @@ pub type Inline = inline::Inline; /// let world = HipByt::from(vec); /// ``` /// -/// To borrow a string slice, you can also use the no-copy constructor [`HipByt::borrowed`]: +/// To borrow a string slice, you can also use the no-copy constructor +/// [`HipByt::borrowed`]: /// /// ``` /// # use hipstr::HipByt; @@ -79,6 +81,21 @@ pub type Inline = inline::Inline; /// * borrow /// * inline string /// * shared heap allocated string +/// +/// # Notable features +/// +/// `HipByt` dereferences through the [`Deref`] trait to either `&[u8]` ot +/// [`&bstr::BStr`] if the feature flag `bstr` is set. [`bstr`] allows for +/// efficient string-like manipulation on non-guaranteed UTF-8 data. +/// +/// In the same manner, [`HipByt::mutate`] returns a mutable handle [`RefMut`] +/// to a `Vec<[u8]>` or a [`bstr::BString`] if the flag `bstr` is set. +/// +/// [`bstr`]: https://crates.io/crates/bstr +/// [`&bstr::BStr`]: https://docs.rs/bstr/latest/bstr/struct.BStr.html +/// [`bstr::BString`]: https://docs.rs/bstr/latest/bstr/struct.BString.html +/// [`Deref`]: core::ops::Deref +/// [`RefMut`]: super::RefMut #[repr(C)] pub struct HipByt<'borrow, B: Backend> { pivot: Pivot, @@ -189,7 +206,7 @@ impl<'borrow, B: Backend> HipByt<'borrow, B> { /// Retrieves a mutable reference on the union. #[inline] - pub(super) fn union_mut(&mut self) -> &mut Union<'borrow, B> { + pub(super) const fn union_mut(&mut self) -> &mut Union<'borrow, B> { let raw_ptr: *mut _ = &mut self.pivot; let union_ptr: *mut Union<'borrow, B> = raw_ptr.cast(); // SAFETY: same layout and same niche hopefully, same mutability @@ -260,7 +277,7 @@ impl<'borrow, B: Backend> HipByt<'borrow, B> { /// Splits this raw into its possible representation. #[inline] - pub(super) fn split_mut(&mut self) -> SplitMut<'_, 'borrow, B> { + pub(super) const fn split_mut(&mut self) -> SplitMut<'_, 'borrow, B> { let tag = self.tag(); let union = self.union_mut(); match tag { @@ -333,40 +350,7 @@ impl<'borrow, B: Backend> HipByt<'borrow, B> { } } - /// Extracts a slice of the entire `HipByt`. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// # use hipstr::HipByt; - /// let s = HipByt::from(b"foobar"); - /// - /// assert_eq!(b"foobar", s.as_slice()); - /// ``` - #[inline] - #[must_use] - pub const fn as_slice(&self) -> &[u8] { - match self.split() { - Split::Inline(inline) => inline.as_slice(), - Split::Allocated(heap) => heap.as_slice(), - Split::Borrowed(borrowed) => borrowed.as_slice(), - } - } - - /// Returns a pointer to the start of the raw byte string. - #[inline] - #[must_use] - pub const fn as_ptr(&self) -> *const u8 { - match self.split() { - Split::Inline(inline) => inline.as_ptr(), - Split::Allocated(heap) => heap.as_ptr(), - Split::Borrowed(borrowed) => borrowed.as_ptr(), - } - } - - /// Slices the raw byte string. + /// Slices the raw byte sequence. /// /// # Safety /// @@ -456,52 +440,6 @@ impl<'borrow, B: Backend> HipByt<'borrow, B> { result } - /// Extracts a mutable slice of the entire `HipByt` if possible. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// # use hipstr::HipByt; - /// let mut s = HipByt::from(b"foo"); - /// let slice = s.as_mut_slice().unwrap(); - /// slice.copy_from_slice(b"bar"); - /// assert_eq!(b"bar", slice); - /// ``` - #[inline] - #[must_use] - pub fn as_mut_slice(&mut self) -> Option<&mut [u8]> { - match self.split_mut() { - SplitMut::Inline(inline) => Some(inline.as_mut_slice()), - SplitMut::Allocated(allocated) => allocated.as_mut_slice(), - SplitMut::Borrowed(_) => None, - } - } - - /// Returns a mutable slice of the underlying string. - /// - /// # Safety - /// - /// This `HipByt` should not be shared or borrowed. - #[inline] - pub(super) unsafe fn as_mut_slice_unchecked(&mut self) -> &mut [u8] { - match self.split_mut() { - SplitMut::Inline(inline) => inline.as_mut_slice(), - SplitMut::Allocated(allocated) => unsafe { allocated.as_mut_slice_unchecked() }, - SplitMut::Borrowed(_) => { - #[cfg(debug_assertions)] - { - panic!("mutable slice of borrowed string"); - } - #[cfg(not(debug_assertions))] - { - unsafe { unreachable_unchecked() } - } - } - } - } - /// Takes a vector representation of this raw byte string. /// /// Will only allocate if needed. @@ -529,7 +467,7 @@ impl<'borrow, B: Backend> HipByt<'borrow, B> { /// /// Returns `None` if this byte string is not allocated. #[inline] - pub(super) fn take_allocated(&mut self) -> Option> { + pub(super) const fn take_allocated(&mut self) -> Option> { match self.split() { Split::Allocated(&allocated) => { // Takes a copy of allocated diff --git a/src/bytes/raw/allocated.rs b/src/bytes/raw/allocated.rs index 315cf8b..f6278ec 100644 --- a/src/bytes/raw/allocated.rs +++ b/src/bytes/raw/allocated.rs @@ -134,7 +134,7 @@ impl Allocated { /// Returns a reference to the owner. /// - /// This function buses the [`Copy`]-ness of [`Allocated`] to get a copy + /// This function abuses the [`Copy`]-ness of [`Allocated`] to get a copy /// (and not a clone) of the [`Smart`] reference wrapped in [`ManuallyDrop`] /// to ensure it's not dropped. fn owner(&self) -> impl Deref, B>> { @@ -143,7 +143,7 @@ impl Allocated { /// Returns a mutable reference to the owner. /// - /// This function buses the [`Copy`]-ness of [`Allocated`] to get a copy + /// This function abuses the [`Copy`]-ness of [`Allocated`] to get a copy /// (and not a clone) of the [`Smart`] reference wrapped in [`ManuallyDrop`] /// to ensure it's not dropped. /// @@ -213,7 +213,31 @@ impl Allocated { self.ptr } - /// Returns a mutable slice if possible (unique non-static reference). + /// Returns a mutable raw pointer to the first element if this sequence is + /// not shared, `None` otherwise. + #[inline] + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn as_mut_ptr(&mut self) -> Option<*mut u8> { + if self.is_unique() { + Some(self.ptr.cast_mut()) + } else { + None + } + } + + /// Returns a mutable raw pointer to the first element. + /// + /// # Safety + /// + /// The caller must ensure that the `Allocated` is actually uniquely shared. + #[inline] + #[allow(clippy::needless_pass_by_ref_mut)] + pub unsafe fn as_mut_ptr_unchecked(&mut self) -> *mut u8 { + debug_assert!(self.is_unique()); + self.ptr.cast_mut() + } + + /// Returns a mutable slice if possible (unique owned reference). #[inline] pub fn as_mut_slice(&mut self) -> Option<&mut [u8]> { if self.is_unique() { diff --git a/src/bytes/raw/borrowed.rs b/src/bytes/raw/borrowed.rs index 005f631..b8cea5a 100644 --- a/src/bytes/raw/borrowed.rs +++ b/src/bytes/raw/borrowed.rs @@ -8,7 +8,7 @@ use super::TAG_BORROWED; #[cfg(test)] mod tests; -const TAG_NZ: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(TAG_BORROWED) }; +const TAG_NZ: NonZeroU8 = NonZeroU8::new(TAG_BORROWED).unwrap(); /// Borrowed slice representation. /// diff --git a/src/bytes/raw/inline.rs b/src/bytes/raw/inline.rs index 13dfa4b..de47a64 100644 --- a/src/bytes/raw/inline.rs +++ b/src/bytes/raw/inline.rs @@ -146,6 +146,20 @@ impl Inline { self.len.decode() } + /// Returns an immutable raw pointer to the start of this inline sequence. + #[inline] + pub const fn as_ptr(&self) -> *const u8 { + debug_assert!(self.is_valid()); + self.data.as_ptr().cast() + } + + /// Returns a mutable raw pointer to the start of this inline sequence. + #[inline] + pub const fn as_mut_ptr(&mut self) -> *mut u8 { + debug_assert!(self.is_valid()); + self.data.as_mut_ptr().cast() + } + /// Returns an immutable view of this inline string. #[inline] pub const fn as_slice(&self) -> &[u8] { @@ -160,21 +174,11 @@ impl Inline { unsafe { core::slice::from_raw_parts(data.cast(), len) } } - /// Returns an immutable raw pointer of this inline string. - #[inline] - pub const fn as_ptr(&self) -> *const u8 { - debug_assert!(self.is_valid()); - self.data.as_ptr().cast() - } - /// Returns a mutable view of this inline string. #[inline] pub const fn as_mut_slice(&mut self) -> &mut [u8] { - debug_assert!(self.is_valid()); - // HACK could be done without less unsafe: maybe_uninit_slice - // and const-ly: const_mut_refs, const_slice_index - let data = self.data.as_mut_ptr(); + let data = self.as_mut_ptr(); let len = self.len(); // SAFETY: type invariant (the first `len`-th bytes are initialized) diff --git a/src/bytes/tests.rs b/src/bytes/tests.rs index bb83eb8..2522867 100644 --- a/src/bytes/tests.rs +++ b/src/bytes/tests.rs @@ -42,6 +42,51 @@ fn test_new_default() { assert!(new.is_empty()); } +#[test] +fn test_ptr() { + let mut h = H::inline(ABCDEF); + let p_const = h.as_ptr(); + let p_mut = h.as_mut_ptr().unwrap(); + assert_eq!(p_mut, unsafe { h.as_mut_ptr_unchecked() }); + assert_eq!(p_const, h.as_slice().as_ptr()); + assert_eq!(p_mut, h.as_mut_slice().unwrap().as_mut_ptr()); + + let mut h = H::from(MEDIUM); + let p_const = h.as_ptr(); + let p_mut = h.as_mut_ptr().unwrap(); + assert_eq!(p_mut, unsafe { h.as_mut_ptr_unchecked() }); + assert_eq!(p_const, h.as_slice().as_ptr()); + assert_eq!(p_mut, h.as_mut_slice().unwrap().as_mut_ptr()); + let _h2 = h.clone(); + assert!(h.as_mut_ptr().is_none()); + + let abcdef = ABCDEF; + let mut h = H::borrowed(ABCDEF); + assert_eq!(h.as_ptr(), abcdef.as_ptr()); + assert_eq!(h.as_mut_ptr(), None); +} + +/// Tests that `as_mut_ptr_unchecked` panics when the sequence is shared in debug. +#[test] +#[cfg(debug_assertions)] +#[should_panic] +fn test_ptr_panic_shared() { + let mut h = H::from(MEDIUM); + let _h2 = h.clone(); + assert!(h.as_mut_ptr().is_none()); + let _ = unsafe { h.as_mut_ptr_unchecked() }; +} + +/// Tests that `as_mut_ptr_unchecked` panics when the sequence is borrowed in debug. +#[test] +#[cfg(debug_assertions)] +#[should_panic] +fn test_ptr_panic_borrowed() { + let mut h = H::borrowed(MEDIUM); + assert!(h.as_mut_ptr().is_none()); + let _ = unsafe { h.as_mut_ptr_unchecked() }; +} + #[test] fn test_with_capacity() { let h = H::with_capacity(0); diff --git a/src/os_string.rs b/src/os_string.rs index f9630bf..87e0644 100644 --- a/src/os_string.rs +++ b/src/os_string.rs @@ -109,8 +109,18 @@ where /// Creates a new `HipOsStr` with the given capacity. /// - /// The returned `HipOsStr` will be able to hold at least `capacity` bytes - /// without reallocating or changing representation. + /// The final capacity depends on the representation and is not guaranteed + /// to be exact. However, the returned `HipOsStr` will be able to hold at + /// least `capacity` bytes without reallocating or changing representation. + /// + /// # Representation + /// + /// If the capacity is less or equal to the inline capacity, the + /// representation will be *inline*. + /// + /// Otherwise, it will be *allocated*. + /// + /// The representation is **not normalized**. /// /// # Examples /// @@ -131,9 +141,18 @@ where Self(HipByt::with_capacity(cap)) } - /// Creates a new `HipOsStr` from a static os string slice without copying the slice. + /// Creates a new `HipOsStr` from an OS string slice without copying the + /// slice. /// - /// Requires only `impl AsRef`: it accepts `&str`, `&OsStr`, and `&Path` for instance. + /// Requires only `impl AsRef`: it accepts `&str`, `&OsStr`, and + /// `&Path` for instance. + /// + /// To create a `HipOsStr` from a `'static` string slice `const`-ly, see + /// [`HipOsStr::from_static`]. + /// + /// # Representation + /// + /// The created `HipOsStr` is _borrowed_. /// /// # Examples /// @@ -218,11 +237,12 @@ where self.0.is_allocated() } - /// Converts `self` into a static string slice if this `HipOsStr` is backed by a static borrow. + /// Converts `self` into a string slice with the `'borrow` lifetime if this + /// `HipOsStr` is backed by a borrow. /// /// # Errors /// - /// Returns `Err(self)` if this `HipOsStr` is not a static borrow. + /// Returns `Err(self)` if this `HipOsStr` is not borrowed. /// /// # Examples /// @@ -328,7 +348,7 @@ where /// Converts a `HipOsStr` into a `HipByt`. /// /// It consumes the `HipOsStr` without copying the content - /// (if [shared][Self::is_allocated] or [static][Self::is_borrowed]). + /// (if [shared][Self::is_allocated] or [borrowed][Self::is_borrowed]). /// /// # Examples /// @@ -414,7 +434,7 @@ where /// /// This operation may reallocate a new string if either: /// - /// - the representation is not an allocated buffer (inline array or static borrow), + /// - the representation is not an allocated buffer (inline array or borrow), /// - the underlying buffer is shared. /// /// # Examples @@ -653,7 +673,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -679,7 +699,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -699,9 +719,12 @@ impl HipOsStr<'static, B> where B: Backend, { - /// Creates a new `HipOsStr` from a static string slice without copying the slice. + /// Creates a new `HipOsStr` from a `'static` string slice without copying + /// the slice. + /// + /// # Representation /// - /// Handy shortcut to make a `HipOsStr<'static, _>` out of a `&'static str`. + /// The created `HipOsStr` is _borrowed_. /// /// # Examples /// diff --git a/src/path.rs b/src/path.rs index 9fcdcaa..44b03b1 100644 --- a/src/path.rs +++ b/src/path.rs @@ -107,9 +107,18 @@ where Self(HipOsStr::new()) } - /// Creates a new `HipPath` from a static os string slice without copying the slice. + /// Creates a new `HipPath` from an OS string slice without copying the + /// slice. /// - /// Requires only `impl AsRef`: it accepts `&str`, `&OsStr`, and `&Path` for instance. + /// Requires only `impl AsRef`: it accepts `&str`, `&OsStr`, and + /// `&Path` for instance. + /// + /// To create a `HipPath` from a `'static` string slice `const`-ly, see + /// [`HipPath::from_static`]. + /// + /// # Representation + /// + /// The created `HipPath` is _borrowed_. /// /// # Examples /// @@ -196,11 +205,12 @@ where self.0.is_allocated() } - /// Converts `self` into a static string slice if this `HipPath` is backed by a static borrow. + /// Converts `self` into a path slice with the `'borrow` lifetime if this + /// `HipPath` is backed by a borrow. /// /// # Errors /// - /// Returns `Err(self)` if this `HipPath` is not a static borrow. + /// Returns `Err(self)` if this `HipPath` is not borrowed. /// /// # Examples /// @@ -366,7 +376,7 @@ where /// /// This operation may reallocate a new buffer if either: /// - /// - the representation is not an allocated buffer (inline array or static borrow), + /// - the representation is not an allocated buffer (inline array or borrow), /// - the underlying buffer is shared. /// /// # Examples @@ -482,7 +492,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -510,7 +520,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -533,7 +543,9 @@ where { /// Creates a new `HipPath` from a static string slice without copying the slice. /// - /// Handy shortcut to make a `HipPath<'static, _>` out of a `&'static str`. + /// # Representation + /// + /// The created `HipPath` is _borrowed_. /// /// # Examples /// diff --git a/src/smart.rs b/src/smart.rs index dc44735..58324b6 100644 --- a/src/smart.rs +++ b/src/smart.rs @@ -269,7 +269,7 @@ where /// /// Any caller should check the uniqueness first with [`Self::is_unique`]. #[inline] - pub unsafe fn as_mut_unchecked(&mut self) -> &mut T { + pub const unsafe fn as_mut_unchecked(&mut self) -> &mut T { // SAFETY: uniqueness precondition unsafe { &mut self.0.as_mut().value } } @@ -281,7 +281,7 @@ where /// - Any caller should check the uniqueness first with [`Self::is_unique`]. /// - The referenced value must outlive `'a`. #[inline] - pub(crate) unsafe fn as_mut_unchecked_extended<'a>(&mut self) -> &'a mut T + pub(crate) const unsafe fn as_mut_unchecked_extended<'a>(&mut self) -> &'a mut T where Self: 'a, { diff --git a/src/string.rs b/src/string.rs index 16eaec9..08bf7a1 100644 --- a/src/string.rs +++ b/src/string.rs @@ -103,8 +103,9 @@ where /// Creates a new `HipStr` with the given capacity. /// - /// The returned `HipStr` will be able to hold at least `capacity` bytes - /// without reallocating or changing representation. + /// The final capacity depends on the representation and is not guaranteed + /// to be exact. However, the returned `HipStr` will be able to hold at + /// least `capacity` bytes without reallocating or changing representation. /// /// # Representation /// @@ -134,8 +135,15 @@ where } /// Creates a new `HipStr` from a string slice. + /// /// No heap allocation is performed. - /// **The string is not copied.** + /// **The string slice is borrowed and not copied.** + /// + /// See also [`HipStr::from_static`] to ensure the borrow is `'static`. + /// + /// # Representation + /// + /// The created `HipStr` is _borrowed_. /// /// # Examples /// @@ -146,6 +154,7 @@ where /// let s = HipStr::borrowed("hello"); /// assert_eq!(s.len(), 5); /// ``` + #[inline] #[must_use] pub const fn borrowed(value: &'borrow str) -> Self { Self(HipByt::borrowed(value.as_bytes())) @@ -174,7 +183,7 @@ where self.0.is_inline() } - /// Returns `true` if this `HipStr` is a static string borrow, `false` otherwise. + /// Returns `true` if this `HipStr` is a string borrow, `false` otherwise. /// /// # Examples /// @@ -220,11 +229,12 @@ where self.0.is_allocated() } - /// Converts `self` into a static string slice if this `HipStr` is backed by a static borrow. + /// Converts `self` into a string slice with the `'borrow` lifetime if this + /// `HipStr` is backed by a borrow. /// /// # Errors /// - /// Returns `Err(self)` if this `HipStr` is not a static borrow. + /// Returns `Err(self)` if this `HipStr` is not a borrow. /// /// # Examples /// @@ -319,6 +329,88 @@ where self.0.is_empty() } + /// Extracts a raw pointer out of the `HipStr`. + /// + /// As strings are ultimately sequence of bytes, the raw pointer points to a + /// [`u8`]. This pointer will be pointing to the first byte of the string. + /// + /// It is your responsibility to make sure that: + /// + /// - This `HipStr` outlives the pointer this function returns. + /// + /// - This `HipStr` is not modified in another way before the use of the + /// pointer. Modifying the byte sequence may change representation or + /// reallocate, which would invalid the returned pointer. + /// + /// - The returned pointer is never written to. If you need to mutate the + /// contents of the string slice, use [`as_mut_ptr`]. + /// + /// [`as_mut_ptr`]: Self::as_mut_ptr + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipStr; + /// let s = HipStr::from("Hello"); + /// let ptr = s.as_ptr(); + /// ``` + #[inline] + #[must_use] + pub const fn as_ptr(&self) -> *const u8 { + self.0.as_ptr() + } + + /// Extracts a mutable raw pointer out of this `HipStr` if it is not shared + /// or borrowed, `None` otherwise. + /// + /// As strings are ultimately sequence of bytes, the raw pointer points to + /// a [`u8`]. This pointer will be pointing to the first byte of the string. + /// + /// It is your responsibility to make sure that: + /// + /// - This `HipStr` outlives the pointer this function returns. + /// + /// - This `HipStr` is not modified in another way before the use of the + /// pointer. Modifying the byte sequence may change representation or + /// reallocate, which would invalid the returned pointer. + /// + /// - The byte sequence gets modified in a way that it remains valid UTF-8. + #[inline] + #[must_use] + pub fn as_mut_ptr(&mut self) -> Option<*mut u8> { + self.0.as_mut_ptr() + } + + /// Extracts a mutable raw pointer out of this `HipStr`. + /// + /// As strings are ultimately sequence of bytes, the raw pointer points to + /// a [`u8`]. This pointer will be pointing to the first byte of the string. + /// + /// It is your responsibility to make sure that: + /// + /// - This `HipStr` outlives the pointer this function returns. + /// + /// - This `HipStr` is not modified in another way before the use of the + /// pointer. Modifying the byte sequence may change representation or + /// reallocate, which would invalid the returned pointer. + /// + /// - The byte sequence gets modified in a way that it remains valid UTF-8. + /// + /// # Safety + /// + /// The caller must ensure the sequence is actually unique: not shared and + /// not borrowed. + /// + /// # Panics + /// + /// In debug mode, this function panics if the sequence is borrowed or + /// shared. + #[inline] + #[must_use] + pub unsafe fn as_mut_ptr_unchecked(&mut self) -> *mut u8 { + unsafe { self.0.as_mut_ptr_unchecked() } + } + /// Converts a `HipStr` into a `HipByt`. /// /// It consumes the `HipStr` without copying the content @@ -335,7 +427,6 @@ where /// /// assert_eq!(&[104, 101, 108, 108, 111][..], &b[..]); /// ``` - #[allow(clippy::missing_const_for_fn)] // cannot const it for now, clippy bug #[must_use] pub fn into_bytes(self) -> HipByt<'borrow, B> { self.0 @@ -398,7 +489,7 @@ where /// assert_eq!("FOO", slice); /// ``` #[inline] - #[must_use] + #[doc(alias = "make_mut")] pub fn to_mut_str(&mut self) -> &mut str { let slice = self.0.to_mut_slice(); // SAFETY: type invariant @@ -691,8 +782,8 @@ where /// [`from_utf8_unchecked`]: HipStr::from_utf8_unchecked /// [`into_bytes`]: HipStr::into_bytes #[inline] - pub fn from_utf8(bytes: HipByt<'borrow, B>) -> Result> { - match core::str::from_utf8(&bytes) { + pub const fn from_utf8(bytes: HipByt<'borrow, B>) -> Result> { + match core::str::from_utf8(bytes.as_slice()) { Ok(_) => { // SAFETY: checked above Ok(unsafe { Self::from_utf8_unchecked(bytes) }) @@ -888,7 +979,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -914,7 +1005,7 @@ where /// # Representation stability /// /// The allocated representation may change to *inline* if the required - /// capacity is smaller thant the inline capacity. + /// capacity is smaller than the inline capacity. /// /// # Examples /// @@ -1545,7 +1636,7 @@ where /// Returns a string with the prefix removed. /// /// If the string starts with the pattern `prefix`, returns substring after the prefix, wrapped - /// in `Some`. Unlike `trim_start_matches`, this method removes the prefix exactly once. + /// in `Some`. Unlike `trim_start_matches`, this method removes the prefix exactly once. /// /// If the string does not start with `prefix`, returns `None`. /// @@ -1736,10 +1827,14 @@ impl HipStr<'static, B> where B: Backend, { - /// Creates a new `HipStr` from a static string slice without copying the slice. + /// Creates a new `HipStr` from a `'static` string slice without copying the slice. /// /// Handy shortcut to make a `HipStr<'static, _>` out of a `&'static str`. /// + /// # Representation + /// + /// The created `HipStr` is _borrowed_. + /// /// # Examples /// /// Basic usage: diff --git a/src/string/borsh.rs b/src/string/borsh.rs index 8491728..5ffea56 100644 --- a/src/string/borsh.rs +++ b/src/string/borsh.rs @@ -12,9 +12,9 @@ use crate::Backend; #[cfg(test)] mod tests; -impl<'borrow, B: Backend> BorshDeserialize for HipStr<'borrow, B> { +impl BorshDeserialize for HipStr<'_, B> { fn deserialize_reader(reader: &mut R) -> io::Result { - let bytes: HipByt<'borrow, B> = HipByt::deserialize_reader(reader)?; + let bytes: HipByt = HipByt::deserialize_reader(reader)?; Self::try_from(bytes).map_err(|err| { let msg = err.to_string(); Error::new(ErrorKind::InvalidData, msg) @@ -22,7 +22,7 @@ impl<'borrow, B: Backend> BorshDeserialize for HipStr<'borrow, B> { } } -impl<'borrow, B: Backend> BorshSerialize for HipStr<'borrow, B> { +impl BorshSerialize for HipStr<'_, B> { fn serialize(&self, writer: &mut W) -> io::Result<()> { self.as_bytes().serialize(writer) } diff --git a/src/string/bstr.rs b/src/string/bstr.rs index a130b43..71a035a 100644 --- a/src/string/bstr.rs +++ b/src/string/bstr.rs @@ -57,7 +57,7 @@ symmetric_ord! { [B] [where B: Backend] (&BString, HipStr<'_, B>) = bstr_cmp; } -impl<'a, B> TryFrom for HipStr<'a, B> +impl TryFrom for HipStr<'_, B> where B: Backend, { diff --git a/src/string/tests.rs b/src/string/tests.rs index 179fbd9..1d9e9ea 100644 --- a/src/string/tests.rs +++ b/src/string/tests.rs @@ -279,6 +279,51 @@ fn test_as_mut_str() { let _ = a.as_str(); } +#[test] +fn test_ptr() { + let mut h = H::from(ABCDEF); + let p_const = h.as_ptr(); + let p_mut = h.as_mut_ptr().unwrap(); + assert_eq!(p_mut, unsafe { h.as_mut_ptr_unchecked() }); + assert_eq!(p_const, h.as_str().as_ptr()); + assert_eq!(p_mut, h.as_mut_str().unwrap().as_mut_ptr()); + + let mut h = H::from(MEDIUM); + let p_const = h.as_ptr(); + let p_mut = h.as_mut_ptr().unwrap(); + assert_eq!(p_mut, unsafe { h.as_mut_ptr_unchecked() }); + assert_eq!(p_const, h.as_str().as_ptr()); + assert_eq!(p_mut, h.as_mut_str().unwrap().as_mut_ptr()); + let _h2 = h.clone(); + assert!(h.as_mut_ptr().is_none()); + + let abcdef = ABCDEF; + let mut h = H::borrowed(ABCDEF); + assert_eq!(h.as_ptr(), abcdef.as_ptr()); + assert_eq!(h.as_mut_ptr(), None); +} + +/// Tests that `as_mut_ptr_unchecked` panics when the string is shared in debug. +#[test] +#[cfg(debug_assertions)] +#[should_panic] +fn test_ptr_panic_shared() { + let mut h = H::from(MEDIUM); + let _h2 = h.clone(); + assert!(h.as_mut_ptr().is_none()); + let _ = unsafe { h.as_mut_ptr_unchecked() }; +} + +/// Tests that `as_mut_ptr_unchecked` panics when the string is borrowed in debug. +#[test] +#[cfg(debug_assertions)] +#[should_panic] +fn test_ptr_panic_borrowed() { + let mut h = H::borrowed(MEDIUM); + assert!(h.as_mut_ptr().is_none()); + let _ = unsafe { h.as_mut_ptr_unchecked() }; +} + #[test] fn test_to_mut_str_static() { let mut a = H::borrowed(ABC);