Skip to content

Commit

Permalink
feat: add into_owned (#7)
Browse files Browse the repository at this point in the history
improve push internals
  • Loading branch information
polazarus authored Aug 25, 2023
1 parent 7b867f9 commit 7aadff4
Show file tree
Hide file tree
Showing 5 changed files with 402 additions and 251 deletions.
58 changes: 54 additions & 4 deletions src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ where
/// ```
#[inline]
pub fn to_mut_slice(&mut self) -> &mut [u8] {
self.0.to_mut_slice()
self.0.make_unique();
unsafe { self.0.as_mut_slice().unwrap_unchecked() }
}

/// Extracts a slice as its own `HipByt`.
Expand Down Expand Up @@ -434,6 +435,21 @@ where
self.0.push_slice(&[value]);
}

/// Makes the data owned, copying it if the data is actually borrowed.
///
/// ```
/// # use hipstr::HipByt;
/// let v = vec![42; 42];
/// let h = HipByt::borrowed(&v[..]);
/// // drop(v); // err, v is borrowed
/// let h = h.into_owned();
/// drop(v); // ok
/// assert_eq!(h, [42; 42]);
/// ```
pub fn into_owned(self) -> HipByt<'static, B> {
HipByt(self.0.into_owned())
}

pub(crate) fn take_vec(&mut self) -> Vec<u8> {
self.0.take_vec()
}
Expand Down Expand Up @@ -1272,11 +1288,19 @@ mod tests {

#[test]
fn test_push_slice_allocated() {
// borrowed, not unique
let mut a = HipByt::borrowed(FORTY_TWOS);
a.push_slice(b"abc");
assert_eq!(&a[0..42], FORTY_TWOS);
assert_eq!(&a[42..], b"abc");

// allocated, unique
let mut a = HipByt::from(FORTY_TWOS);
a.push_slice(b"abc");
assert_eq!(&a[0..42], FORTY_TWOS);
assert_eq!(&a[42..], b"abc");

// allocated, not unique
let mut a = HipByt::from(FORTY_TWOS);
let pa = a.as_ptr();
let b = a.clone();
Expand All @@ -1287,13 +1311,17 @@ mod tests {
assert_eq!(&a[42..], b"abc");
assert_eq!(b, FORTY_TWOS);

// allocated, unique but shifted
let mut a = {
let x = HipByt::from(FORTY_TWOS);
x.slice(1..42) // need to reallocate (do not shift)
x.slice(1..39)
};
let p = a.as_ptr();
a.push_slice(b"abc");
assert_eq!(&a[..41], &FORTY_TWOS[1..42]);
assert_eq!(&a[41..], b"abc");
assert_eq!(&a[..38], &FORTY_TWOS[1..39]);
assert_eq!(&a[38..], b"abc");
assert_eq!(a.as_ptr(), p);
// => the underlying vector is big enough
}

#[test]
Expand All @@ -1305,4 +1333,26 @@ mod tests {
a.push(b'd');
assert_eq!(a, b"abcd");
}

#[test]
fn test_to_owned() {
let b = b"abc";
let h = HipByt::from(b);
assert!(h.is_inline());
let h = h.into_owned();
assert!(h.is_inline());

let v = vec![42; 42];
let a = HipByt::borrowed(&v[0..2]);
let a = a.into_owned();
drop(v);
assert_eq!(a, [42, 42]);

let v = vec![42; 42];
let a = HipByt::from(&v[..]);
drop(v);
let p = a.as_ptr();
let a = a.into_owned();
assert_eq!(a.as_ptr(), p);
}
}
100 changes: 75 additions & 25 deletions src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,30 +221,6 @@ impl<'borrow, B: Backend> Raw<'borrow, B> {
}
}

#[allow(clippy::wrong_self_convention)]
#[inline]
pub fn to_mut_slice(&mut self) -> &mut [u8] {
let copy = if self.is_inline() {
// SAFETY: representation is checked
return unsafe { &mut self.inline }.as_mut_slice();
} else if self.is_allocated() {
// SAFETY: representation is checked and data stay allocated for the lifetime of self
if let Some(slice) = unsafe { self.allocated.as_mut_slice() } {
return slice;
}
// SAFETY: representation is checked
let allocated = unsafe { self.allocated };
Self::from_slice(allocated.as_slice())
} else {
// SAFETY: representation is checked
let borrowed = unsafe { self.borrowed };
Self::from_slice(borrowed.as_slice())
};
*self = copy;
debug_assert!(self.is_normalized());
self.as_mut_slice().unwrap()
}

#[inline]
pub fn push_slice(&mut self, addition: &[u8]) {
let new_len = self.len() + addition.len();
Expand All @@ -255,7 +231,7 @@ impl<'borrow, B: Backend> Raw<'borrow, B> {
*self = unsafe { Self::inline_unchecked(self.as_slice()) };
}
unsafe { self.inline.push_slice_unchecked(addition) };
} else if self.is_allocated() && unsafe { self.allocated.can_push() } {
} else if self.is_allocated() && unsafe { self.allocated.is_unique() } {
// current allocation can be pushed into
unsafe { self.allocated.push_slice_unchecked(addition) };
} else {
Expand Down Expand Up @@ -323,6 +299,80 @@ impl<'borrow, B: Backend> Raw<'borrow, B> {
}
}

/// Makes the data owned, copying it if it's not already owned.
#[inline]
pub fn into_owned(self) -> Raw<'static, B> {
// SAFETY: take ownership of the allocated
// the old value do not need to be dropped
let old = ManuallyDrop::new(self);
if old.is_borrowed() {
Raw::from_slice(old.as_slice())
} else if old.is_inline() {
// SAFETY: representation is checked above
Raw {
inline: unsafe { old.inline },
}
} else {
// => old.is_allocated()

// SAFETY: take ownership of the allocated
// the old value should not be dropped
let old = ManuallyDrop::new(old);
// SAFETY: representation is checked above
Raw {
allocated: unsafe { old.allocated },
}
}
}

/// Makes the data owned and unique, copying it if necessary.
///
/// # Safety
///
/// Must be non-unique, i.e. `is_unique()` must return `true` **beforehand**.
#[inline]
pub unsafe fn into_unique_unchecked(self) -> Self {
debug_assert!(!self.is_unique());

// SAFETY: take ownership of the allocated
// the old value do not need to be dropped
let old = ManuallyDrop::new(self);
if old.is_borrowed() {
Raw::from_slice(old.as_slice())
} else {
debug_assert!(old.is_allocated());

// SAFETY: take ownership of the allocated
// the old value should not be dropped
let old = ManuallyDrop::new(old);

// SAFETY: representation is checked above
let allocated = unsafe { old.allocated };

debug_assert!(allocated.len() > INLINE_CAPACITY);

let new = Self::allocate(allocated.as_slice());
allocated.decr_ref_count();
new
}
}

/// Makes the underlying data uniquely owned, copying if needed.
#[inline]
pub fn make_unique(&mut self) {
if !self.is_unique() {
let old = replace(self, Self::empty());
// SAFETY: non uniqueness checked above
*self = unsafe { old.into_unique_unchecked() };
}
}

/// Returns `true` if the data is uniquely owned.
#[inline]
pub fn is_unique(&self) -> bool {
self.is_inline() || (self.is_allocated() && unsafe { self.allocated.is_unique() })
}

#[inline]
pub const fn is_normalized(&self) -> bool {
self.is_inline() || self.is_borrowed() || self.len() > Self::inline_capacity()
Expand Down
26 changes: 13 additions & 13 deletions src/raw/allocated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,32 +166,32 @@ impl<B: Backend> Allocated<B> {
})
}

/// Returns `true` if the underlying vector can be mutated (easily), that
/// is, if their only one reference to the underlying vector
/// and the current slice starts at index 0 of the underlying vector.
pub fn can_push(&self) -> bool {
/// Returns `true` if there is only one reference to the underlying vector.
pub fn is_unique(&self) -> bool {
debug_assert!(self.is_valid());

// SAFETY: type invariant -> owner is valid
let is_unique = unsafe { B::raw_is_unique(self.owner) };

// SAFETY: type invariant -> owner is valid
let ptr = unsafe { B::raw_as_vec(self.owner).as_ptr() };

is_unique && ptr == self.ptr
unsafe { B::raw_is_unique(self.owner) }
}

/// Push a slice at the end of the underlying vector.
/// Pushes a slice at the end of the underlying vector.
///
/// # Safety
///
/// The reference must be unique.
pub unsafe fn push_slice_unchecked(&mut self, addition: &[u8]) {
let v = unsafe { B::raw_get_mut_unchecked(self.owner) };
v.truncate(self.len);

// SAFETY: compute the shift from within the vector range (type invariant)
#[allow(clippy::cast_sign_loss)]
let shift = unsafe { self.ptr.offset_from(v.as_ptr()) as usize };
v.truncate(shift + self.len);
v.extend_from_slice(addition);
self.len += addition.len();
self.ptr = v.as_ptr();

// SAFETY: shift to within the vector range (the shift was already
// in the old range).
self.ptr = unsafe { v.as_ptr().add(shift) };
}
}

Expand Down
57 changes: 54 additions & 3 deletions src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,21 @@ where
let s = ch.encode_utf8(&mut data);
self.0.push_slice(s.as_bytes());
}

/// Makes the string owned, copying the data if it is actually borrowed.
///
/// ```
/// # use hipstr::HipStr;
/// let s: String = ('a'..'z').collect();
/// let h = HipStr::borrowed(&s[..]);
/// // drop(s); // err, s is borrowed
/// let h = h.into_owned();
/// drop(s); // ok
/// assert_eq!(h, ('a'..'z').collect::<String>());
/// ```
pub fn into_owned(self) -> HipStr<'static, B> {
HipStr(self.0.into_owned())
}
}

impl<B> HipStr<'static, B>
Expand Down Expand Up @@ -1615,11 +1630,19 @@ mod tests {

#[test]
fn test_push_slice_allocated() {
// borrowed, not unique
let mut a = HipStr::borrowed(FORTY_TWOS);
a.push_str("abc");
assert_eq!(&a[0..42], FORTY_TWOS);
assert_eq!(&a[42..], "abc");

// allocated, unique
let mut a = HipStr::from(FORTY_TWOS);
a.push_str("abc");
assert_eq!(&a[0..42], FORTY_TWOS);
assert_eq!(&a[42..], "abc");

// allocated, not unique
let mut a = HipStr::from(FORTY_TWOS);
let pa = a.as_ptr();
let b = a.clone();
Expand All @@ -1630,13 +1653,17 @@ mod tests {
assert_eq!(&a[42..], "abc");
assert_eq!(b, FORTY_TWOS);

// allocated, unique but shifted
let mut a = {
let x = HipStr::from(FORTY_TWOS);
x.slice(1..42) // need to reallocate (do not shift)
x.slice(1..39)
};
let p = a.as_ptr();
a.push_str("abc");
assert_eq!(&a[..41], &FORTY_TWOS[1..42]);
assert_eq!(&a[41..], "abc");
assert_eq!(&a[..38], &FORTY_TWOS[1..39]);
assert_eq!(&a[38..], "abc");
assert_eq!(a.as_ptr(), p);
// => the underlying vector is big enough
}

#[test]
Expand All @@ -1650,4 +1677,28 @@ mod tests {
a.push('🦀');
assert_eq!(a, "abcd🦀");
}

#[test]
fn test_to_owned() {
let b = "abc";
let h = HipStr::from(b);
assert!(h.is_inline());
let h = h.into_owned();
assert!(h.is_inline());

let r = "*".repeat(42);

let v = r.clone();
let a = HipStr::borrowed(&v[0..2]);
let a = a.into_owned();
drop(v);
assert_eq!(a, &r[0..2]);

let v = r.clone();
let a = HipStr::from(&v[..]);
drop(v);
let p = a.as_ptr();
let a = a.into_owned();
assert_eq!(a.as_ptr(), p);
}
}
Loading

0 comments on commit 7aadff4

Please sign in to comment.