konst/
string.rs

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