From 85dd68191e66af1dc1530e6d7d4c07d4537e4167 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Sun, 3 Dec 2023 15:18:21 +0100 Subject: [PATCH] core: introduce checked_split_at{,_mut} Introduce checked_split_at and checked_split_at_mut methods to slices types (including str) which are non-panicking versions of split_at and split_at_mut. This addition is analogous to how get method is non-panicking version of indexing. --- library/core/src/lib.rs | 2 + library/core/src/slice/mod.rs | 115 +++++++++++++++++++++++++++--- library/core/src/str/mod.rs | 128 +++++++++++++++++++++++++++++----- 3 files changed, 215 insertions(+), 30 deletions(-) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 07720f235989b..3870790ded8fb 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -183,8 +183,10 @@ #![feature(ptr_alignment_type)] #![feature(ptr_metadata)] #![feature(set_ptr_value)] +#![feature(slice_checked_split_at)] #![feature(slice_ptr_get)] #![feature(slice_split_at_unchecked)] +#![feature(str_checked_split_at)] #![feature(str_internals)] #![feature(str_split_inclusive_remainder)] #![feature(str_split_remainder)] diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index a7b36fe7d2957..3481a5d490ac9 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -1837,7 +1837,8 @@ impl [T] { /// /// # Panics /// - /// Panics if `mid > len`. + /// Panics if `mid > len`. For non-panicking alternative see + /// [`checked_split_at`](slice::checked_split_at). /// /// # Examples /// @@ -1864,15 +1865,15 @@ impl [T] { /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_slice_split_at_not_mut", since = "1.71.0")] - #[rustc_allow_const_fn_unstable(slice_split_at_unchecked)] + #[rustc_allow_const_fn_unstable(slice_checked_split_at)] #[inline] #[track_caller] #[must_use] pub const fn split_at(&self, mid: usize) -> (&[T], &[T]) { - assert!(mid <= self.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `split_at_unchecked`. - unsafe { self.split_at_unchecked(mid) } + match self.checked_split_at(mid) { + Some(pair) => pair, + None => panic!("mid > len"), + } } /// Divides one mutable slice into two at an index. @@ -1883,7 +1884,8 @@ impl [T] { /// /// # Panics /// - /// Panics if `mid > len`. + /// Panics if `mid > len`. For non-panicking alternative see + /// [`checked_split_at_mut`](slice::checked_split_at_mut). /// /// # Examples /// @@ -1902,10 +1904,10 @@ impl [T] { #[must_use] #[rustc_const_unstable(feature = "const_slice_split_at_mut", issue = "101804")] pub const fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) { - assert!(mid <= self.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_mut_unchecked(mid) } + match self.checked_split_at_mut(mid) { + Some(pair) => pair, + None => panic!("mid > len"), + } } /// Divides one slice into two at an index, without doing bounds checking. @@ -2024,6 +2026,97 @@ impl [T] { unsafe { (from_raw_parts_mut(ptr, mid), from_raw_parts_mut(ptr.add(mid), len - mid)) } } + /// Divides one slice into two at an index returning `None` if slice is too + /// short. + /// + /// The first will contain all indices from `[0, mid)` (excluding + /// the index `mid` itself) and the second will contain all + /// indices from `[mid, len)` (excluding the index `len` itself). + /// + /// Returns `None` if `mid > len`. + /// + /// # Examples + /// + /// ``` + /// #![feature(slice_checked_split_at)] + /// + /// let v = [1, 2, 3, 4, 5, 6]; + /// + /// { + /// let (left, right) = v.checked_split_at(0).unwrap(); + /// assert_eq!(left, []); + /// assert_eq!(right, [1, 2, 3, 4, 5, 6]); + /// } + /// + /// { + /// let (left, right) = v.checked_split_at(2).unwrap(); + /// assert_eq!(left, [1, 2]); + /// assert_eq!(right, [3, 4, 5, 6]); + /// } + /// + /// { + /// let (left, right) = v.checked_split_at(6).unwrap(); + /// assert_eq!(left, [1, 2, 3, 4, 5, 6]); + /// assert_eq!(right, []); + /// } + /// + /// assert_eq!(None, v.checked_split_at(7)); + /// ``` + #[unstable(feature = "slice_checked_split_at", reason = "new API", issue = "118578")] + #[rustc_const_unstable(feature = "slice_checked_split_at", issue = "118578")] + #[inline] + #[track_caller] + #[must_use] + pub const fn checked_split_at(&self, mid: usize) -> Option<(&[T], &[T])> { + if mid <= self.len() { + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `split_at_unchecked`. + Some(unsafe { self.split_at_unchecked(mid) }) + } else { + None + } + } + + /// Divides one mutable slice into two at an index. + /// + /// The first will contain all indices from `[0, mid)` (excluding + /// the index `mid` itself) and the second will contain all + /// indices from `[mid, len)` (excluding the index `len` itself). + /// + /// Returns `None` if `mid > len`. + /// + /// # Examples + /// + /// ``` + /// #![feature(slice_checked_split_at)] + /// + /// let mut v = [1, 0, 3, 0, 5, 6]; + /// + /// if let Some((left, right)) = v.checked_split_at_mut(2) { + /// assert_eq!(left, [1, 0]); + /// assert_eq!(right, [3, 0, 5, 6]); + /// left[1] = 2; + /// right[1] = 4; + /// } + /// assert_eq!(v, [1, 2, 3, 4, 5, 6]); + /// + /// assert_eq!(None, v.checked_split_at_mut(7)); + /// ``` + #[unstable(feature = "slice_checked_split_at", reason = "new API", issue = "118578")] + #[rustc_const_unstable(feature = "slice_checked_split_at", issue = "118578")] + #[inline] + #[track_caller] + #[must_use] + pub const fn checked_split_at_mut(&mut self, mid: usize) -> Option<(&mut [T], &mut [T])> { + if mid <= self.len() { + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `split_at_unchecked`. + Some(unsafe { self.split_at_mut_unchecked(mid) }) + } else { + None + } + } + /// Divides one slice into an array and a remainder slice at an index. /// /// The array will contain all indices from `[0, N)` (excluding diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 27178328be5c1..e1e49171cf880 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -641,8 +641,9 @@ impl str { /// /// # Panics /// - /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is - /// past the end of the last code point of the string slice. + /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is past + /// the end of the last code point of the string slice. For non-panicking + /// alternative see [`checked_split_at`](str::checked_split_at). /// /// # Examples /// @@ -658,13 +659,7 @@ impl str { #[must_use] #[stable(feature = "str_split_at", since = "1.4.0")] pub fn split_at(&self, mid: usize) -> (&str, &str) { - // is_char_boundary checks that the index is in [0, .len()] - if self.is_char_boundary(mid) { - // SAFETY: just checked that `mid` is on a char boundary. - unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) } - } else { - slice_error_fail(self, 0, mid) - } + self.checked_split_at(mid).unwrap_or_else(|| slice_error_fail(self, 0, mid)) } /// Divide one mutable string slice into two at an index. @@ -681,8 +676,9 @@ impl str { /// /// # Panics /// - /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is - /// past the end of the last code point of the string slice. + /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is past + /// the end of the last code point of the string slice. For non-panicking + /// alternative see [`checked_split_at_mut`](str::checked_split_at_mut). /// /// # Examples /// @@ -702,20 +698,114 @@ impl str { pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str) { // is_char_boundary checks that the index is in [0, .len()] if self.is_char_boundary(mid) { - let len = self.len(); - let ptr = self.as_mut_ptr(); // SAFETY: just checked that `mid` is on a char boundary. - unsafe { - ( - from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, mid)), - from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr.add(mid), len - mid)), - ) - } + unsafe { self.split_at_mut_unchecked(mid) } } else { slice_error_fail(self, 0, mid) } } + /// Divide one string slice into two at an index. + /// + /// The argument, `mid`, should be a valid byte offset from the start of the + /// string. It must also be on the boundary of a UTF-8 code point. The + /// method returns `None` if that’s not the case. + /// + /// The two slices returned go from the start of the string slice to `mid`, + /// and from `mid` to the end of the string slice. + /// + /// To get mutable string slices instead, see the [`checked_split_at_mut`] + /// method. + /// + /// [`checked_split_at_mut`]: str::checked_split_at_mut + /// + /// # Examples + /// + /// ``` + /// #![feature(str_checked_split_at)] + /// + /// let s = "Per Martin-Löf"; + /// + /// let (first, last) = s.checked_split_at(3).unwrap(); + /// assert_eq!("Per", first); + /// assert_eq!(" Martin-Löf", last); + /// + /// assert_eq!(None, s.checked_split_at(13)); // Inside “ö” + /// assert_eq!(None, s.checked_split_at(16)); // Beyond the string length + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "str_checked_split_at", reason = "new API", issue = "118578")] + pub fn checked_split_at(&self, mid: usize) -> Option<(&str, &str)> { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(mid) { + // SAFETY: just checked that `mid` is on a char boundary. + Some(unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) }) + } else { + None + } + } + + /// Divide one mutable string slice into two at an index. + /// + /// The argument, `mid`, should be a valid byte offset from the start of the + /// string. It must also be on the boundary of a UTF-8 code point. The + /// method returns `None` if that’s not the case. + /// + /// The two slices returned go from the start of the string slice to `mid`, + /// and from `mid` to the end of the string slice. + /// + /// To get immutable string slices instead, see the [`checked_split_at`] method. + /// + /// [`checked_split_at`]: str::checked_split_at + /// + /// # Examples + /// + /// ``` + /// #![feature(str_checked_split_at)] + /// + /// let mut s = "Per Martin-Löf".to_string(); + /// if let Some((first, last)) = s.checked_split_at_mut(3) { + /// first.make_ascii_uppercase(); + /// assert_eq!("PER", first); + /// assert_eq!(" Martin-Löf", last); + /// } + /// assert_eq!("PER Martin-Löf", s); + /// + /// assert_eq!(None, s.checked_split_at_mut(13)); // Inside “ö” + /// assert_eq!(None, s.checked_split_at_mut(16)); // Beyond the string length + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "str_checked_split_at", reason = "new API", issue = "118578")] + pub fn checked_split_at_mut(&mut self, mid: usize) -> Option<(&mut str, &mut str)> { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(mid) { + // SAFETY: just checked that `mid` is on a char boundary. + Some(unsafe { self.split_at_mut_unchecked(mid) }) + } else { + None + } + } + + /// Divide one string slice into two at an index. + /// + /// # Safety + /// + /// The caller must ensure `mid` is a byte offset at a UTF-8 character + /// boundary. + unsafe fn split_at_mut_unchecked(&mut self, mid: usize) -> (&mut str, &mut str) { + let len = self.len(); + let ptr = self.as_mut_ptr(); + // SAFETY: caller guarantees `mid` is on a char boundary. + unsafe { + ( + from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, mid)), + from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr.add(mid), len - mid)), + ) + } + } + /// Returns an iterator over the [`char`]s of a string slice. /// /// As a string slice consists of valid UTF-8, we can iterate through a