konst/
string.rs

1//! `const fn` equivalents of `str` methods.
2//!
3
4#[cfg(feature = "iter")]
5mod chars_methods;
6
7#[cfg(feature = "iter")]
8pub use chars_methods::*;
9
10mod concatenation;
11
12pub use concatenation::*;
13
14#[cfg(test)]
15mod priv_string_tests;
16
17mod pattern;
18
19use core::fmt::{self, Debug, Display};
20
21pub use self::pattern::Pattern;
22
23pub(crate) use self::pattern::PatternNorm;
24
25mod split_once;
26
27pub use split_once::*;
28
29#[cfg(feature = "iter")]
30mod splitting;
31
32#[cfg(feature = "iter")]
33pub use splitting::*;
34
35#[cfg(feature = "iter")]
36mod split_terminator_items;
37
38#[cfg(feature = "iter")]
39pub use split_terminator_items::*;
40
41use konst_kernel::string::__is_char_boundary_bytes;
42
43__declare_string_cmp_fns! {
44    import_path = "konst",
45    equality_fn = eq_str,
46    ordering_fn = cmp_str,
47    ordering_fn_inner = cmp_str_inner,
48}
49
50#[cfg(feature = "cmp")]
51__declare_fns_with_docs! {
52    (Option<&'a str>, (eq_option_str, cmp_option_str))
53
54    docs(default)
55
56    macro = __impl_option_cmp_fns!(
57        #[cfg_attr(feature = "docsrs", doc(cfg(feature = "cmp")))]
58        for['a,]
59        params(l, r)
60        eq_comparison = crate::cmp::CmpWrapper(l).const_eq(r),
61        cmp_comparison = crate::cmp::CmpWrapper(l).const_cmp(r),
62        parameter_copyability = copy,
63    ),
64}
65
66/// Delegates to [`core::str::from_utf8`],
67/// wrapping the error to provide a `panic` method for use in [`unwrap_ctx`]
68///
69/// # Example
70///
71/// ### Basic
72///
73/// ```rust
74/// use konst::{
75///     result::unwrap_ctx,
76///     string,
77/// };
78///
79/// const STR: &str = unwrap_ctx!(string::from_utf8(b"foo bar"));
80///
81/// assert_eq!(STR, "foo bar")
82/// ```
83///
84/// ### Compile-time error
85///
86/// ```compile_fail
87/// use konst::{
88///     result::unwrap_ctx,
89///     string,
90/// };
91///
92/// const _: &str = unwrap_ctx!(string::from_utf8(&[255, 255, 255]));
93/// ```
94///
95/// ```text
96/// error[E0080]: evaluation of constant value failed
97///  --> src/string.rs:88:17
98///   |
99/// 9 | const _: &str = unwrap_ctx!(string::from_utf8(&[255, 255, 255]));
100///   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid utf-8 sequence of 1 bytes from index 0', src/string.rs:9:17
101///   |
102///   = note: this error originates in the macro `unwrap_ctx` (in Nightly builds, run with -Z macro-backtrace for more info)
103///
104/// ```
105///
106/// [`unwrap_ctx`]: crate::result::unwrap_ctx
107pub const fn from_utf8(slice: &[u8]) -> Result<&str, Utf8Error> {
108    match core::str::from_utf8(slice) {
109        Ok(x) => Ok(x),
110        Err(e) => Err(Utf8Error(e)),
111    }
112}
113
114/// Wrapper around [`core::str::Utf8Error`]
115/// to provide a `panic` method for use in [`unwrap_ctx`],
116/// returned by [`from_utf8`](crate::string::from_utf8).
117///
118/// [`unwrap_ctx`]: crate::result::unwrap_ctx
119#[derive(Copy, Clone)]
120pub struct Utf8Error(pub core::str::Utf8Error);
121
122impl Utf8Error {
123    /// Panics with a `Display` formatted error message
124    #[track_caller]
125    pub const fn panic(self) -> ! {
126        let pvs = const_panic::StdWrapper(&self.0).to_panicvals(const_panic::FmtArg::DISPLAY);
127        const_panic::concat_panic(&[&pvs])
128    }
129}
130
131impl Debug for Utf8Error {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        Debug::fmt(&self.0, f)
134    }
135}
136
137impl Display for Utf8Error {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        Display::fmt(&self.0, f)
140    }
141}
142
143/// A const equivalent of
144/// [`str::starts_with`](https://doc.rust-lang.org/std/primitive.str.html#method.starts_with)
145/// , taking a [`Pattern`] parameter.
146///
147/// # Example
148///
149/// ```rust
150/// use konst::string;
151///
152/// assert!( string::starts_with("foo,bar,baz", "foo,"));
153///
154/// assert!(!string::starts_with("foo,bar,baz", "bar"));
155/// assert!(!string::starts_with("foo,bar,baz", "baz"));
156///
157/// ```
158///
159#[inline(always)]
160pub const fn starts_with<'a, P>(left: &str, pat: P) -> bool
161where
162    P: Pattern<'a>,
163{
164    let pat = PatternNorm::new(pat);
165    crate::slice::__bytes_start_with(left.as_bytes(), pat.as_bytes())
166}
167
168/// A const equivalent of
169/// [`str::ends_with`](https://doc.rust-lang.org/std/primitive.str.html#method.ends_with)
170/// , taking a [`Pattern`] parameter.
171///
172/// # Example
173///
174/// ```rust
175/// use konst::string;
176///
177/// assert!( string::ends_with("foo,bar,baz", ",baz"));
178/// assert!( string::ends_with("abc...z", 'z'));
179///
180/// assert!(!string::ends_with("foo,bar,baz", "bar"));
181/// assert!(!string::ends_with("foo,bar,baz", "foo"));
182/// assert!(!string::ends_with("abc", 'z'));
183///
184/// ```
185///
186#[inline(always)]
187pub const fn ends_with<'a, P>(left: &str, pat: P) -> bool
188where
189    P: Pattern<'a>,
190{
191    let pat = PatternNorm::new(pat);
192    crate::slice::__bytes_end_with(left.as_bytes(), pat.as_bytes())
193}
194
195/// A const equivalent of
196/// [`str::find`](https://doc.rust-lang.org/std/primitive.str.html#method.find)
197/// , taking a [`Pattern`] parameter.
198///
199/// # Example
200///
201/// ```rust
202/// use konst::string;
203///
204/// assert_eq!(string::find("foo-bar-baz", 'q'), None);
205/// assert_eq!(string::find("foo-bar-baz", '-'), Some(3));
206///
207/// assert_eq!(string::find("foo-bar-baz-foo", "qux"), None);
208/// assert_eq!(string::find("foo-bar-baz-foo", "foo"), Some(0));
209/// assert_eq!(string::find("foo-bar-baz-foo-bar", "bar"), Some(4));
210/// assert_eq!(string::find("foo-the-baz-foo-bar", "bar"), Some(16));
211///
212/// ```
213///
214#[inline]
215pub const fn find<'a, P>(left: &str, pat: P) -> Option<usize>
216where
217    P: Pattern<'a>,
218{
219    let pat = PatternNorm::new(pat);
220    crate::slice::__bytes_find(left.as_bytes(), pat.as_bytes())
221}
222
223/// A const equivalent of
224/// [`str::contains`](https://doc.rust-lang.org/std/primitive.str.html#method.contains)
225/// , taking a [`Pattern`] parameter.
226///
227/// # Example
228///
229/// ```rust
230/// use konst::string;
231///
232/// assert!(string::contains("foo-bar-baz", '-'));
233/// assert!(!string::contains("foo-bar-baz", 'q'));
234///
235/// assert!(string::contains("foo-bar-baz-foo", "foo"));
236///
237/// assert!( string::contains("foo-bar-baz-foo-bar", "bar"));
238/// assert!(!string::contains("foo-he-baz-foo-he", "bar"));
239///
240/// ```
241///
242#[inline]
243pub const fn contains<'a, P>(left: &str, pat: P) -> bool
244where
245    P: Pattern<'a>,
246{
247    let pat = PatternNorm::new(pat);
248    matches!(
249        crate::slice::__bytes_find(left.as_bytes(), pat.as_bytes()),
250        Some(_)
251    )
252}
253
254/// A const equivalent of
255/// [`str::rfind`](https://doc.rust-lang.org/std/primitive.str.html#method.rfind)
256/// , taking a [`Pattern`] parameter.
257///
258/// # Example
259///
260/// ```rust
261/// use konst::string;
262///
263/// assert_eq!(string::rfind("bar-baz-baz", 'q'), None);
264/// assert_eq!(string::rfind("bar-baz-baz", '-'), Some(7));
265///
266/// assert_eq!(string::rfind("bar-baz", "foo"), None);
267/// assert_eq!(string::rfind("bar-baz-foo", "foo"), Some(8));
268/// assert_eq!(string::rfind("foo-bar-baz", "foo"), Some(0));
269///
270/// ```
271///
272#[inline]
273pub const fn rfind<'a, P>(left: &str, pat: P) -> Option<usize>
274where
275    P: Pattern<'a>,
276{
277    let pat = PatternNorm::new(pat);
278    crate::slice::__bytes_rfind(left.as_bytes(), pat.as_bytes())
279}
280
281/// A const equivalent of
282/// [`str::contains`](https://doc.rust-lang.org/std/primitive.str.html#method.contains)
283/// , taking a [`Pattern`] parameter.
284///
285/// # Example
286///
287/// ```rust
288/// use konst::string;
289///
290/// assert!(string::rcontains("foo-bar-baz", '-'));
291/// assert!(!string::rcontains("foo-bar-baz", 'q'));
292///
293/// assert!(!string::rcontains("bar-baz", "foo"));
294/// assert!(string::rcontains("foo-bar", "foo"));
295///
296/// ```
297///
298#[inline(always)]
299pub const fn rcontains<'a, P>(left: &str, pat: P) -> bool
300where
301    P: Pattern<'a>,
302{
303    let pat = PatternNorm::new(pat);
304    matches!(
305        crate::slice::__bytes_rfind(left.as_bytes(), pat.as_bytes()),
306        Some(_)
307    )
308}
309
310/// A const equivalent of `&string[..len]`.
311///
312/// If `string.len() < len`, this simply returns `string` back.
313///
314/// # Panics
315///
316/// This function panics if `len` is inside the string but doesn't fall on a char boundary.
317///
318/// # Example
319///
320/// ```
321/// use konst::string::str_up_to;
322///
323/// const STR: &str = "foo bar baz";
324///
325/// const SUB0: &str = str_up_to(STR, 3);
326/// assert_eq!(SUB0, "foo");
327///
328/// const SUB1: &str = str_up_to(STR, 7);
329/// assert_eq!(SUB1, "foo bar");
330///
331/// const SUB2: &str = str_up_to(STR, 11);
332/// assert_eq!(SUB2, STR);
333///
334/// const SUB3: &str = str_up_to(STR, 100);
335/// assert_eq!(SUB3, STR);
336///
337///
338/// ```
339#[doc(inline)]
340pub use konst_kernel::string::str_up_to;
341
342/// A const equivalent of `&string[start..]`.
343///
344/// If `string.len() < start`, this simply returns an empty string` back.
345///
346/// # Panics
347///
348/// This function panics if `start` is inside the string but doesn't fall on a char boundary.
349///
350/// # Example
351///
352/// ```
353/// use konst::string::str_from;
354///
355/// const STR: &str = "foo bar baz";
356///
357/// const SUB0: &str = str_from(STR, 0);
358/// assert_eq!(SUB0, STR);
359///
360/// const SUB1: &str = str_from(STR, 4);
361/// assert_eq!(SUB1, "bar baz");
362///
363/// const SUB2: &str = str_from(STR, 8);
364/// assert_eq!(SUB2, "baz");
365///
366/// const SUB3: &str = str_from(STR, 11);
367/// assert_eq!(SUB3, "");
368///
369/// const SUB4: &str = str_from(STR, 1000);
370/// assert_eq!(SUB3, "");
371///
372///
373/// ```
374#[doc(inline)]
375pub use konst_kernel::string::str_from;
376
377/// A const equivalent of `&string[start..end]`.
378///
379/// If `start >= end ` or `string.len() < start `, this returns an empty string.
380///
381/// If `string.len() < end`, this returns the string from `start`.
382///
383/// # Alternatives
384///
385/// For a const equivalent of `&string[start..]` there's [`str_from`].
386///
387/// For a const equivalent of `&string[..end]` there's [`str_up_to`].
388///
389/// [`str_from`]: ./fn.str_from.html
390/// [`str_up_to`]: ./fn.str_up_to.html
391///
392/// # Panics
393///
394/// This function panics if either `start` or `end` are inside the string and
395/// don't fall on a char boundary.
396///
397/// # Example
398///
399/// ```
400/// use konst::string::str_range;
401///
402/// const STR: &str = "foo bar baz";
403///
404/// const SUB0: &str = str_range(STR, 0, 3);
405/// assert_eq!(SUB0, "foo");
406///
407/// const SUB1: &str = str_range(STR, 0, 7);
408/// assert_eq!(SUB1, "foo bar");
409///
410/// const SUB2: &str = str_range(STR, 4, 11);
411/// assert_eq!(SUB2, "bar baz");
412///
413/// const SUB3: &str = str_range(STR, 0, 1000);
414/// assert_eq!(SUB3, STR);
415///
416///
417/// ```
418#[doc(inline)]
419pub use konst_kernel::string::str_range;
420
421/// Const equivalent of [`str::is_char_boundary`].
422///
423/// # Example
424///
425/// ```
426/// use konst::string::is_char_boundary;
427///
428/// let string =  "锈 is 🧠";
429///
430/// // Start of "锈"
431/// assert!(is_char_boundary(string, 0));
432/// assert!(!is_char_boundary(string, 1));
433/// assert!(!is_char_boundary(string, 2));
434///
435/// // start of " "
436/// assert!(is_char_boundary(string, 3));
437///
438/// // start of "🧠"
439/// assert!(is_char_boundary(string, 7));
440/// assert!(!is_char_boundary(string, 8));
441///
442/// // end of string
443/// assert!(is_char_boundary(string, string.len()));
444///
445/// // after end of string
446/// assert!(!is_char_boundary(string, string.len() + 1));
447///
448///
449/// ```
450#[doc(inline)]
451pub use konst_kernel::string::is_char_boundary;
452
453/// Checks that the start and end are valid utf8 char boundaries
454/// when the `"debug"` feature is enabled.
455///
456/// When the `"debug"` feature is disabled,
457/// this is equivalent to calling `core::str::from_utf8_unchecled`
458///
459/// # Safety
460///
461/// The input byte slice must be a subslice of a `&str`,
462/// so that only the start and end need to be checked.
463#[doc(inline)]
464pub use konst_kernel::string::__from_u8_subslice_of_str;
465
466/// A const equivalent of `string.get(..len)`.
467///
468/// # Example
469///
470/// ```
471/// use konst::string;
472///
473/// const STR: &str = "foo bar baz";
474///
475/// const SUB0: Option<&str> = string::get_up_to(STR, 3);
476/// assert_eq!(SUB0, Some("foo"));
477///
478/// const SUB1: Option<&str> = string::get_up_to(STR, 7);
479/// assert_eq!(SUB1, Some("foo bar"));
480///
481/// const SUB2: Option<&str> = string::get_up_to(STR, 11);
482/// assert_eq!(SUB2, Some(STR));
483///
484/// const SUB3: Option<&str> = string::get_up_to(STR, 100);
485/// assert_eq!(SUB3, None);
486///
487///
488/// ```
489pub const fn get_up_to(string: &str, len: usize) -> Option<&str> {
490    let bytes = string.as_bytes();
491
492    crate::option::and_then!(
493        crate::slice::get_up_to(bytes, len),
494        |x| if __is_char_boundary_bytes(bytes, len) {
495            // Safety: __is_char_boundary_bytes checks that `len` falls on a char boundary.
496            unsafe { Some(__from_u8_subslice_of_str(x)) }
497        } else {
498            None
499        }
500    )
501}
502
503/// A const equivalent of `string.get(from..)`.
504///
505/// # Example
506///
507/// ```
508/// use konst::string;
509///
510/// const STR: &str = "foo bar baz";
511///
512/// const SUB0: Option<&str> = string::get_from(STR, 0);
513/// assert_eq!(SUB0, Some(STR));
514///
515/// const SUB1: Option<&str> = string::get_from(STR, 4);
516/// assert_eq!(SUB1, Some("bar baz"));
517///
518/// const SUB2: Option<&str> = string::get_from(STR, 8);
519/// assert_eq!(SUB2, Some("baz"));
520///
521/// const SUB3: Option<&str> = string::get_from(STR, 100);
522/// assert_eq!(SUB3, None);
523///
524///
525/// ```
526pub const fn get_from(string: &str, from: usize) -> Option<&str> {
527    let bytes = string.as_bytes();
528
529    crate::option::and_then!(
530        crate::slice::get_from(bytes, from),
531        |x| if __is_char_boundary_bytes(bytes, from) {
532            // Safety: __is_char_boundary_bytes checks that `from` falls on a char boundary.
533            unsafe { Some(__from_u8_subslice_of_str(x)) }
534        } else {
535            None
536        }
537    )
538}
539
540/// A const equivalent of [`str::split_at`]
541///
542/// If `at > string.len()` this returns `(string, "")`.
543///
544/// # Panics
545///
546/// This function panics if `at` is inside the string but doesn't fall on a char boundary.
547///
548/// # Example
549///
550/// ```rust
551/// use konst::string;
552///
553/// const IN: &str = "foo bar baz";
554///
555/// {
556///     const SPLIT0: (&str, &str) = string::split_at(IN, 0);
557///     assert_eq!(SPLIT0, ("", "foo bar baz"));
558/// }
559/// {
560///     const SPLIT1: (&str, &str) = string::split_at(IN, 4);
561///     assert_eq!(SPLIT1, ("foo ", "bar baz"));
562/// }
563/// {
564///     const SPLIT2: (&str, &str) = string::split_at(IN, 8);
565///     assert_eq!(SPLIT2, ("foo bar ", "baz"));
566/// }
567/// {
568///     const SPLIT3: (&str, &str) = string::split_at(IN, 11);
569///     assert_eq!(SPLIT3, ("foo bar baz", ""));
570/// }
571/// {
572///     const SPLIT4: (&str, &str) = string::split_at(IN, 13);
573///     assert_eq!(SPLIT4, ("foo bar baz", ""));
574/// }
575///
576/// ```
577///
578/// [`str::split_at`]: https://doc.rust-lang.org/std/primitive.str.html#method.split_at
579pub const fn split_at(string: &str, at: usize) -> (&str, &str) {
580    (str_up_to(string, at), str_from(string, at))
581}
582
583/// A const equivalent of `string.get(start..end)`.
584///
585/// # Alternatives
586///
587/// For a const equivalent of `string.get(start..)` there's [`get_from`].
588///
589/// For a const equivalent of `string.get(..end)` there's [`get_up_to`].
590///
591/// [`get_from`]: ./fn.get_from.html
592/// [`get_up_to`]: ./fn.get_up_to.html
593///
594/// # Example
595///
596/// ```
597/// use konst::string;
598///
599/// const STR: &str = "foo bar baz";
600///
601/// const SUB0: Option<&str> = string::get_range(STR, 0, 3);
602/// assert_eq!(SUB0, Some("foo"));
603///
604/// const SUB1: Option<&str> = string::get_range(STR, 0, 7);
605/// assert_eq!(SUB1, Some("foo bar"));
606///
607/// const SUB2: Option<&str> = string::get_range(STR, 4, 11);
608/// assert_eq!(SUB2, Some("bar baz"));
609///
610/// const SUB3: Option<&str> = string::get_range(STR, 0, 1000);
611/// assert_eq!(SUB3, None);
612///
613///
614/// ```
615pub const fn get_range(string: &str, start: usize, end: usize) -> Option<&str> {
616    let bytes = string.as_bytes();
617
618    crate::option::and_then!(crate::slice::get_range(bytes, start, end), |x| {
619        if __is_char_boundary_bytes(bytes, start) && __is_char_boundary_bytes(bytes, end) {
620            // Safety: __is_char_boundary_bytes checks that `start` and `end` fall on a char boundary.
621            unsafe { Some(__from_u8_subslice_of_str(x)) }
622        } else {
623            None
624        }
625    })
626}
627
628/// A const subset of [`str::strip_prefix`].
629///
630/// This takes [`Pattern`] implementors as the pattern.
631///
632/// # Example
633///
634/// ```rust
635/// use konst::string;
636///
637/// {
638///     const STRIP: Option<&str> = string::strip_prefix("--5 8", '-');
639///     assert_eq!(STRIP, Some("-5 8"));
640/// }
641/// {
642///     const STRIP: Option<&str> = string::strip_prefix("--5 8", '_');
643///     assert_eq!(STRIP, None);
644/// }
645///
646/// {
647///     const STRIP: Option<&str> = string::strip_prefix("33 5 8", "3");
648///     assert_eq!(STRIP, Some("3 5 8"));
649/// }
650/// {
651///     const STRIP: Option<&str> = string::strip_prefix("3 5 8", "hello");
652///     assert_eq!(STRIP, None);
653/// }
654///
655///
656/// ```
657///
658/// [`str::strip_prefix`]: https://doc.rust-lang.org/std/primitive.str.html#method.strip_prefix
659pub const fn strip_prefix<'a, 'p, P>(string: &'a str, pattern: P) -> Option<&'a str>
660where
661    P: Pattern<'p>,
662{
663    let pat = PatternNorm::new(pattern);
664
665    // Safety: because `pat` is a `Pattern`, removing it should result in a valid `&str`
666    unsafe {
667        crate::option::map!(
668            crate::slice::__bytes_strip_prefix(string.as_bytes(), pat.as_bytes()),
669            __from_u8_subslice_of_str,
670        )
671    }
672}
673
674/// A const subset of [`str::strip_suffix`].
675///
676/// This takes [`Pattern`] implementors as the pattern.
677///
678/// # Example
679///
680/// ```rust
681/// use konst::string;
682///
683/// {
684///     const STRIP: Option<&str> = string::strip_suffix("3 5 8--", '-');
685///     assert_eq!(STRIP, Some("3 5 8-"));
686/// }
687/// {
688///     const STRIP: Option<&str> = string::strip_suffix("3 5 8", '_');
689///     assert_eq!(STRIP, None);
690/// }
691///
692/// {
693///     const STRIP: Option<&str> = string::strip_suffix("3 5 6868", "68");
694///     assert_eq!(STRIP, Some("3 5 68"));
695/// }
696/// {
697///     const STRIP: Option<&str> = string::strip_suffix("3 5 8", "hello");
698///     assert_eq!(STRIP, None);
699/// }
700///
701///
702/// ```
703///
704pub const fn strip_suffix<'a, 'p, P>(string: &'a str, pattern: P) -> Option<&'a str>
705where
706    P: Pattern<'p>,
707{
708    let pat = PatternNorm::new(pattern);
709
710    // Safety: because `suffix` is a `&str`, removing it should result in a valid `&str`
711    unsafe {
712        crate::option::map!(
713            crate::slice::__bytes_strip_suffix(string.as_bytes(), pat.as_bytes()),
714            __from_u8_subslice_of_str,
715        )
716    }
717}
718
719/// A const subset of [`str::trim`] which only removes ascii whitespace.
720///
721/// # Const stabilization
722///
723/// The [equivalent std function](str::trim_ascii) was const-stabilized in Rust 1.80.0.
724///
725/// # Example
726///
727/// ```rust
728/// use konst::string;
729///
730/// const TRIMMED: &str = string::trim("\nhello world  ");
731///
732/// assert_eq!(TRIMMED, "hello world");
733///
734/// ```
735pub const fn trim(this: &str) -> &str {
736    let trimmed = crate::slice::bytes_trim(this.as_bytes());
737    // safety: bytes_trim only removes ascii bytes
738    unsafe { __from_u8_subslice_of_str(trimmed) }
739}
740
741/// A const subset of [`str::trim_start`] which only removes ascii whitespace.
742///
743/// # Const stabilization
744///
745/// The [equivalent std function](str::trim_ascii_start) was const-stabilized in Rust 1.80.0.
746///
747/// # Example
748///
749/// ```rust
750/// use konst::string;
751///
752/// const TRIMMED: &str = string::trim_start("\rfoo bar  ");
753///
754/// assert_eq!(TRIMMED, "foo bar  ");
755///
756/// ```
757pub const fn trim_start(this: &str) -> &str {
758    let trimmed = crate::slice::bytes_trim_start(this.as_bytes());
759    // safety: bytes_trim_start only removes ascii bytes
760    unsafe { __from_u8_subslice_of_str(trimmed) }
761}
762
763/// A const subset of [`str::trim_end`] which only removes ascii whitespace.
764///
765/// # Const stabilization
766///
767/// The [equivalent std function](str::trim_ascii_end) was const-stabilized in Rust 1.80.0.
768///
769/// # Example
770///
771/// ```rust
772/// use konst::string;
773///
774/// const TRIMMED: &str = string::trim_end("\rfoo bar  ");
775///
776/// assert_eq!(TRIMMED, "\rfoo bar");
777///
778/// ```
779///
780pub const fn trim_end(this: &str) -> &str {
781    let trimmed = crate::slice::bytes_trim_end(this.as_bytes());
782    // safety: bytes_trim_end only removes ascii bytes
783    unsafe { __from_u8_subslice_of_str(trimmed) }
784}
785
786/// A const subset of [`str::trim_matches`].
787///
788/// This takes [`Pattern`] implementors as the needle.
789///
790/// # Example
791///
792/// ```rust
793/// use konst::string;
794///
795/// const CHAR_TRIMMED: &str = string::trim_matches("---baz qux---", '-');
796/// const STR_TRIMMED: &str = string::trim_matches("<>baz qux<><><>", "<>");
797///
798/// assert_eq!(CHAR_TRIMMED, "baz qux");
799/// assert_eq!(STR_TRIMMED, "baz qux");
800///
801/// ```
802pub const fn trim_matches<'a, 'p, P>(this: &'a str, needle: P) -> &'a str
803where
804    P: Pattern<'p>,
805{
806    let needle = PatternNorm::new(needle);
807    let trimmed = crate::slice::__bytes_trim_matches(this.as_bytes(), needle.as_bytes());
808    // safety:
809    // because bytes_trim_matches was passed `&str`s casted to `&[u8]`s,
810    // it returns a valid utf8 sequence.
811    unsafe { __from_u8_subslice_of_str(trimmed) }
812}
813
814/// A const subset of [`str::trim_start_matches`].
815///
816/// This takes [`Pattern`] implementors as the needle.
817///
818/// # Example
819///
820/// ```rust
821/// use konst::string;
822///
823/// const CHAR_TRIMMED: &str = string::trim_start_matches("#####huh###", '#');
824/// const STR_TRIMMED: &str = string::trim_start_matches("#####huh###", "##");
825///
826/// assert_eq!(CHAR_TRIMMED, "huh###");
827/// assert_eq!(STR_TRIMMED, "#huh###");
828///
829/// ```
830pub const fn trim_start_matches<'a, 'p, P>(this: &'a str, needle: P) -> &'a str
831where
832    P: Pattern<'p>,
833{
834    let needle = PatternNorm::new(needle);
835    let trimmed = crate::slice::__bytes_trim_start_matches(this.as_bytes(), needle.as_bytes());
836    // safety:
837    // because bytes_trim_start_matches was passed `&str`s casted to `&[u8]`s,
838    // it returns a valid utf8 sequence.
839    unsafe { __from_u8_subslice_of_str(trimmed) }
840}
841
842/// A const subset of [`str::trim_end_matches`].
843///
844/// This takes [`Pattern`] implementors as the needle.
845///
846/// # Example
847///
848/// ```rust
849/// use konst::string;
850///
851/// const CHAR_TRIMMED: &str = string::trim_end_matches("oowowooooo", 'o');
852/// const STR_TRIMMED: &str = string::trim_end_matches("oowowooooo", "oo");
853///
854/// assert_eq!(CHAR_TRIMMED, "oowow");
855/// assert_eq!(STR_TRIMMED, "oowowo");
856///
857/// ```
858pub const fn trim_end_matches<'a, 'p, P>(this: &'a str, needle: P) -> &'a str
859where
860    P: Pattern<'p>,
861{
862    let needle = PatternNorm::new(needle);
863    let trimmed = crate::slice::__bytes_trim_end_matches(this.as_bytes(), needle.as_bytes());
864    // safety:
865    // because bytes_trim_end_matches was passed `&str`s casted to `&[u8]`s,
866    // it returns a valid utf8 sequence.
867    unsafe { __from_u8_subslice_of_str(trimmed) }
868}
869
870/// Advances `this` past the first instance of `needle`.
871///
872/// Returns `None` if no instance of `needle` is found.
873///
874/// Returns `Some(this)` if `needle` is empty.
875///
876/// This takes [`Pattern`] implementors as the needle.
877///
878/// # Example
879///
880/// ```rust
881/// use konst::string;
882///
883/// {
884///     const FOUND: Option<&str> = string::find_skip("foo bar baz", ' ');
885///     assert_eq!(FOUND, Some("bar baz"));
886/// }
887///
888/// {
889///     const FOUND: Option<&str> = string::find_skip("foo bar baz", "bar");
890///     assert_eq!(FOUND, Some(" baz"));
891/// }
892/// {
893///     const NOT_FOUND: Option<&str> = string::find_skip("foo bar baz", "qux");
894///     assert_eq!(NOT_FOUND, None);
895/// }
896/// ```
897pub const fn find_skip<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
898where
899    P: Pattern<'p>,
900{
901    let needle = PatternNorm::new(needle);
902    unsafe {
903        crate::option::map!(
904            crate::slice::__bytes_find_skip(this.as_bytes(), needle.as_bytes()),
905            // safety:
906            // because bytes_find_skip was passed `&str`s casted to `&[u8]`s,
907            // it returns a valid utf8 sequence.
908            __from_u8_subslice_of_str,
909        )
910    }
911}
912
913/// Advances `this` up to the first instance of `needle`.
914///
915/// Returns `None` if no instance of `needle` is found.
916///
917/// Returns `Some(this)` if `needle` is empty.
918///
919/// This takes [`Pattern`] implementors as the needle.
920///
921/// # Example
922///
923/// ```rust
924/// use konst::string;
925///
926/// {
927///     const FOUND: Option<&str> = string::find_keep("foo-bar-baz", '-');
928///     assert_eq!(FOUND, Some("-bar-baz"));
929/// }
930///
931/// {
932///     const FOUND: Option<&str> = string::find_keep("foo bar baz", "bar");
933///     assert_eq!(FOUND, Some("bar baz"));
934/// }
935/// {
936///     const NOT_FOUND: Option<&str> = string::find_keep("foo bar baz", "qux");
937///     assert_eq!(NOT_FOUND, None);
938/// }
939/// ```
940pub const fn find_keep<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
941where
942    P: Pattern<'p>,
943{
944    let needle = PatternNorm::new(needle);
945    unsafe {
946        crate::option::map!(
947            crate::slice::__bytes_find_keep(this.as_bytes(), needle.as_bytes()),
948            // safety:
949            // because bytes_find_keep was passed `&str`s casted to `&[u8]`s,
950            // it returns a valid utf8 sequence.
951            __from_u8_subslice_of_str,
952        )
953    }
954}
955
956/// Truncates `this` to before the last instance of `needle`.
957///
958/// Returns `None` if no instance of `needle` is found.
959///
960/// Returns `Some(this)` if `needle` is empty.
961///
962/// This takes [`Pattern`] implementors as the needle.
963///
964/// # Example
965///
966/// ```rust
967/// use konst::string;
968///
969/// {
970///     const FOUND: Option<&str> = string::rfind_skip("foo bar _ bar baz", '_');
971///     assert_eq!(FOUND, Some("foo bar "));
972/// }
973///
974/// {
975///     const FOUND: Option<&str> = string::rfind_skip("foo bar _ bar baz", "bar");
976///     assert_eq!(FOUND, Some("foo bar _ "));
977/// }
978/// {
979///     const NOT_FOUND: Option<&str> = string::rfind_skip("foo bar baz", "qux");
980///     assert_eq!(NOT_FOUND, None);
981/// }
982/// ```
983pub const fn rfind_skip<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
984where
985    P: Pattern<'p>,
986{
987    let needle = PatternNorm::new(needle);
988    unsafe {
989        crate::option::map!(
990            crate::slice::__bytes_rfind_skip(this.as_bytes(), needle.as_bytes()),
991            // safety:
992            // because bytes_rfind_skip was passed `&str`s casted to `&[u8]`s,
993            // it returns a valid utf8 sequence.
994            __from_u8_subslice_of_str,
995        )
996    }
997}
998
999/// Truncates `this` to the last instance of `needle`.
1000///
1001/// Returns `None` if no instance of `needle` is found.
1002///
1003/// Returns `Some(this)` if `needle` is empty.
1004///
1005/// This takes [`Pattern`] implementors as the needle.
1006///
1007/// # Example
1008///
1009/// ```rust
1010/// use konst::string;
1011///
1012/// {
1013///     const FOUND: Option<&str> = string::rfind_keep("foo bar _ bar baz", '_');
1014///     assert_eq!(FOUND, Some("foo bar _"));
1015/// }
1016///
1017/// {
1018///     const FOUND: Option<&str> = string::rfind_keep("foo bar _ bar baz", "bar");
1019///     assert_eq!(FOUND, Some("foo bar _ bar"));
1020/// }
1021/// {
1022///     const NOT_FOUND: Option<&str> = string::rfind_keep("foo bar baz", "qux");
1023///     assert_eq!(NOT_FOUND, None);
1024/// }
1025/// ```
1026pub const fn rfind_keep<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
1027where
1028    P: Pattern<'p>,
1029{
1030    let needle = PatternNorm::new(needle);
1031    unsafe {
1032        crate::option::map!(
1033            crate::slice::__bytes_rfind_keep(this.as_bytes(), needle.as_bytes()),
1034            // safety:
1035            // because bytes_rfind_keep was passed `&str`s casted to `&[u8]`s,
1036            // it returns a valid utf8 sequence.
1037            __from_u8_subslice_of_str,
1038        )
1039    }
1040}