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}