Skip to content

Commit a2ce156

Browse files
committed
feat: add truncate() methods
1 parent c2219da commit a2ce156

File tree

4 files changed

+218
-69
lines changed

4 files changed

+218
-69
lines changed

src/lib.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,49 @@ impl LeanString {
769769
self.0.insert_str(idx, string)
770770
}
771771

772+
/// Shortens a [`LeanString`] to the specified length.
773+
///
774+
/// If `new_len` is greater than or equal to the string's current length, this has no effect.
775+
///
776+
/// # Panics
777+
///
778+
/// Panics if **any** of the following conditions is met:
779+
///
780+
/// 1. `new_len` does not lie on a [`char`] boundary.
781+
/// 2. The system is out-of-memory when cloning the [`LeanString`].
782+
///
783+
/// For 2, If you want to handle such a problem manually, use [`LeanString::try_truncate()`].
784+
///
785+
/// # Examples
786+
///
787+
/// ```
788+
/// # use lean_string::LeanString;
789+
/// let mut s = LeanString::from("hello");
790+
/// s.truncate(2);
791+
/// assert_eq!(s, "he");
792+
///
793+
/// // Truncating to a larger length does nothing:
794+
/// s.truncate(10);
795+
/// assert_eq!(s, "he");
796+
/// ```
797+
#[inline]
798+
pub fn truncate(&mut self, new_len: usize) {
799+
self.try_truncate(new_len).unwrap_with_msg()
800+
}
801+
802+
/// Fallible version of [`LeanString::truncate()`].
803+
///
804+
/// This method won't panic if the system is out-of-memory, but return an [`ReserveError`].
805+
/// Otherwise it behaves the same as [`LeanString::truncate()`].
806+
///
807+
/// # Panics
808+
///
809+
/// This method still panics if `new_len` does not lie on a [`char`] boundary.
810+
#[inline]
811+
pub fn try_truncate(&mut self, new_len: usize) -> Result<(), ReserveError> {
812+
self.0.truncate(new_len)
813+
}
814+
772815
/// Reduces the length of the [`LeanString`] to zero.
773816
///
774817
/// If the [`LeanString`] is unique, this method will not change the capacity.

src/repr.rs

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -353,46 +353,10 @@ impl Repr {
353353
// SAFETY: We know this is a valid length which falls on a char boundary
354354
let new_len = self.len() - ch.len_utf8();
355355

356-
if self.is_heap_buffer() {
357-
// SAFETY: We just checked that `self` is HeapBuffer
358-
let heap = unsafe { self.as_heap_buffer_mut() };
359-
360-
if !heap.is_len_on_heap() {
361-
// Since len is inlined and we don't modify the buffer by popping a char, it is ok
362-
// to just set the new length.
363-
// SAFETY: `new_len <= len <= capacity`
364-
unsafe { heap.set_len(new_len) };
365-
} else {
366-
// See `reverse` method for the explanation of the ordering.
367-
if heap.reference_count().fetch_sub(1, Release) == 1 {
368-
// `heap` is unique, we can set the new length in place.
369-
370-
// See `reverse` method for the explanation of the ordering.
371-
heap.reference_count().fetch_add(1, Acquire);
372-
373-
// SAFETY: `heap` is unique, we can reallocate in place.
374-
unsafe { heap.set_len(new_len) };
375-
} else {
376-
// SAFETY: `ptr` is valid for `len` bytes, and `HeapBuffer` contains valid UTF-8.
377-
let str = unsafe {
378-
let ptr = self.0 as *mut u8;
379-
let slice = slice::from_raw_parts_mut(ptr, new_len);
380-
str::from_utf8_unchecked_mut(slice)
381-
};
382-
*self = Repr::from_str(str)?;
383-
}
384-
}
385-
} else if self.is_static_buffer() {
386-
// SAFETY:
387-
// - We just checked that `self` is StaticBuffer
388-
// - `new_len <= len <= capacity`
389-
unsafe { self.as_static_buffer_mut().set_len(new_len) };
390-
} else {
391-
// SAFETY:
392-
// - The number of types of buffer is 3, and the remaining is InlineBuffer.
393-
// - From `#Safety`, `new_len <= MAX_INLINE_SIZE` is true.
394-
unsafe { self.as_inline_buffer_mut().set_len(new_len) };
395-
}
356+
// SAFETY:
357+
// - `new_len` is less than `len()` because we calculated it from `len() - ch.len_utf8()`.
358+
// - `new_len` is a valid char boundary because `ch` is a valid char.
359+
unsafe { self.truncate_unchecked(new_len) }?;
396360

397361
Ok(Some(ch))
398362
}
@@ -516,6 +480,74 @@ impl Repr {
516480
Ok(())
517481
}
518482

483+
#[inline]
484+
pub(crate) fn truncate(&mut self, new_len: usize) -> Result<(), ReserveError> {
485+
if new_len >= self.len() {
486+
return Ok(());
487+
}
488+
489+
let str = self.as_str();
490+
assert!(
491+
str.is_char_boundary(new_len),
492+
"index is not a char boundary or out of bounds (index: {new_len})",
493+
);
494+
495+
// SAFETY: We just checked that `new_len < len()` and `new_len` is a valid char
496+
unsafe { self.truncate_unchecked(new_len) }
497+
}
498+
499+
/// # Safety
500+
///
501+
/// - `new_len` must be less than or equal to `len()`
502+
/// - `new_len` must be a valid char boundary.
503+
unsafe fn truncate_unchecked(&mut self, new_len: usize) -> Result<(), ReserveError> {
504+
debug_assert!(new_len <= self.len());
505+
debug_assert!(self.as_str().is_char_boundary(new_len));
506+
507+
if self.is_heap_buffer() {
508+
// SAFETY: We just checked that `self` is HeapBuffer
509+
let heap = unsafe { self.as_heap_buffer_mut() };
510+
511+
if !heap.is_len_on_heap() {
512+
// Since len is inlined and we don't modify the buffer by popping a char, it is ok
513+
// to just set the new length.
514+
// SAFETY: `new_len <= len <= capacity`
515+
unsafe { heap.set_len(new_len) };
516+
} else {
517+
// See `reverse` method for the explanation of the ordering.
518+
if heap.reference_count().fetch_sub(1, Release) == 1 {
519+
// `heap` is unique, we can set the new length in place.
520+
521+
// See `reverse` method for the explanation of the ordering.
522+
heap.reference_count().fetch_add(1, Acquire);
523+
524+
// SAFETY: `heap` is unique, we can reallocate in place.
525+
unsafe { heap.set_len(new_len) };
526+
} else {
527+
// SAFETY: `ptr` is valid for `len` bytes, and `HeapBuffer` contains valid UTF-8.
528+
let str = unsafe {
529+
let ptr = self.0 as *mut u8;
530+
let slice = slice::from_raw_parts_mut(ptr, new_len);
531+
str::from_utf8_unchecked_mut(slice)
532+
};
533+
*self = Repr::from_str(str)?;
534+
}
535+
}
536+
} else if self.is_static_buffer() {
537+
// SAFETY:
538+
// - We just checked that `self` is StaticBuffer
539+
// - `new_len <= len <= capacity`
540+
unsafe { self.as_static_buffer_mut().set_len(new_len) };
541+
} else {
542+
// SAFETY:
543+
// - The number of types of buffer is 3, and the remaining is InlineBuffer.
544+
// - From `#Safety`, `new_len <= MAX_INLINE_SIZE` is true.
545+
unsafe { self.as_inline_buffer_mut().set_len(new_len) };
546+
}
547+
548+
Ok(())
549+
}
550+
519551
#[inline]
520552
pub(crate) fn is_unique(&self) -> bool {
521553
if self.is_heap_buffer() {

tests/alloc_string.rs

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -341,37 +341,37 @@ fn test_pop() {
341341
// assert_eq!(nihon.capacity(), orig_capacity);
342342
// }
343343

344-
// #[test]
345-
// fn test_str_truncate() {
346-
// let mut s = String::from("12345");
347-
// s.truncate(5);
348-
// assert_eq!(s, "12345");
349-
// s.truncate(3);
350-
// assert_eq!(s, "123");
351-
// s.truncate(0);
352-
// assert_eq!(s, "");
353-
//
354-
// let mut s = String::from("12345");
355-
// let p = s.as_ptr();
356-
// s.truncate(3);
357-
// s.push_str("6");
358-
// let p_ = s.as_ptr();
359-
// assert_eq!(p_, p);
360-
// }
344+
#[test]
345+
fn test_str_truncate() {
346+
let mut s = LeanString::from("12345");
347+
s.truncate(5);
348+
assert_eq!(s, "12345");
349+
s.truncate(3);
350+
assert_eq!(s, "123");
351+
s.truncate(0);
352+
assert_eq!(s, "");
361353

362-
// #[test]
363-
// fn test_str_truncate_invalid_len() {
364-
// let mut s = String::from("12345");
365-
// s.truncate(6);
366-
// assert_eq!(s, "12345");
367-
// }
354+
let mut s = LeanString::from("12345");
355+
let p = s.as_ptr();
356+
s.truncate(3);
357+
s.push_str("6");
358+
let p_ = s.as_ptr();
359+
assert_eq!(p_, p);
360+
}
368361

369-
// #[test]
370-
// #[should_panic]
371-
// fn test_str_truncate_split_codepoint() {
372-
// let mut s = String::from("\u{FC}"); // ü
373-
// s.truncate(1);
374-
// }
362+
#[test]
363+
fn test_str_truncate_invalid_len() {
364+
let mut s = LeanString::from("12345");
365+
s.truncate(6);
366+
assert_eq!(s, "12345");
367+
}
368+
369+
#[test]
370+
#[should_panic(expected = "index is not a char boundary or out of bounds (index: 1)")]
371+
fn test_str_truncate_split_codepoint() {
372+
let mut s = LeanString::from("\u{FC}"); // ü
373+
s.truncate(1);
374+
}
375375

376376
#[test]
377377
fn test_str_clear() {

tests/handmade.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ fn pop_share_buffer() {
202202

203203
// buffer is shared
204204
assert_eq!(s.as_ptr(), s2.as_ptr());
205+
206+
// modify makes a new buffer
207+
s2.push('0');
208+
assert_ne!(s.as_ptr(), s2.as_ptr());
205209
}
206210

207211
#[test]
@@ -368,6 +372,76 @@ fn insert_fail() {
368372
s.insert(7, 'a');
369373
}
370374

375+
#[test]
376+
fn truncate_keep_capacity() {
377+
let mut inline = LeanString::from("abcdef");
378+
379+
assert!(!inline.is_heap_allocated());
380+
inline.truncate(3);
381+
assert_eq!(inline, "abc");
382+
assert_eq!(inline.len(), 3);
383+
assert!(!inline.is_heap_allocated());
384+
assert_eq!(inline.capacity(), INLINE_LIMIT);
385+
386+
let mut heap = LeanString::from("a".repeat(INLINE_LIMIT + 10).as_str());
387+
let original_capacity = heap.capacity();
388+
assert!(heap.is_heap_allocated());
389+
390+
heap.truncate(INLINE_LIMIT + 1);
391+
assert_eq!(heap, "a".repeat(INLINE_LIMIT + 1));
392+
assert_eq!(heap.len(), INLINE_LIMIT + 1);
393+
assert!(heap.is_heap_allocated());
394+
assert_eq!(heap.capacity(), original_capacity);
395+
396+
heap.truncate(1);
397+
assert_eq!(heap, "a");
398+
assert_eq!(heap.len(), 1);
399+
assert!(heap.is_heap_allocated());
400+
assert_eq!(heap.capacity(), original_capacity);
401+
}
402+
403+
#[test]
404+
fn truncate_from_static() {
405+
let mut static_ = LeanString::from_static_str("abcdefghijklmnopqrstuvwxyz");
406+
assert_eq!(static_.len(), 26);
407+
assert!(!static_.is_heap_allocated());
408+
409+
static_.truncate(20);
410+
assert_eq!(static_, "abcdefghijklmnopqrst");
411+
assert_eq!(static_.len(), 20);
412+
assert_eq!(static_.capacity(), 20);
413+
assert!(!static_.is_heap_allocated());
414+
}
415+
416+
#[test]
417+
fn truncate_share_buffer() {
418+
// s is inlined
419+
let mut s = LeanString::from("abcdefgh");
420+
assert_eq!(s.len(), 8);
421+
422+
let mut s1 = s.clone();
423+
s1.truncate(4);
424+
assert_eq!(s1, "abcd");
425+
assert_eq!(s1.len(), 4);
426+
427+
// s is not changed
428+
assert_eq!(s, "abcdefgh");
429+
430+
// s into heap
431+
s.push_str("ijklmnopqrstuvwxyz");
432+
assert_eq!(s.len(), 26);
433+
assert!(s.is_heap_allocated());
434+
435+
// buffer is shared
436+
let mut s2 = s.clone();
437+
s2.truncate(20);
438+
assert_eq!(s.as_ptr(), s2.as_ptr());
439+
440+
// modify makes a new buffer
441+
s2.push('0');
442+
assert_ne!(s.as_ptr(), s2.as_ptr());
443+
}
444+
371445
#[test]
372446
fn convert_static_to_inline_with_reserve() {
373447
let s: &'static str = "1234567890ABCDEFGHIJ";

0 commit comments

Comments
 (0)