soft_ascii_string/
soft_str.rs

1use std::ops::{
2    Index,  Range,
3    RangeFrom, RangeTo,
4    RangeFull,
5};
6use std::cmp::PartialEq;
7use std::default::Default;
8use std::fmt::{self, Display};
9use std::borrow::{ToOwned, Cow};
10use std::ffi::{OsString, OsStr};
11use std::path::Path;
12use std::net::{ToSocketAddrs, SocketAddr};
13use std::str::{self, FromStr, EncodeUtf16};
14use std::{vec, io};
15use std::iter::{Iterator, DoubleEndedIterator};
16
17// this import will become unused in future rust versions
18// but won't be removed for now for supporting current
19// rust versions
20#[allow(warnings)]
21use std::ascii::AsciiExt;
22
23use error::FromSourceError;
24use soft_char::SoftAsciiChar;
25use soft_string::SoftAsciiString;
26
27/// A `str` wrapper adding a "is us-ascii" soft constraint.
28///
29/// This means that it should be ascii but is not guaranteed to be
30/// ascii. Which means non ascii chars _are not a safety issue_ just
31/// a potential bug.
32///
33/// This is useful for situations where:
34///   1. you would have many unsafe from str conversions/"unnecessary" checks with a
35///      strict AsciiStr
36///   2. you rarely have to strictly rely on the value being ascii
37///
38///
39/// # Note
40/// Some functions which should be implemented directly
41/// on `SoftAsciiStr` like e.g. `trim_matches` are only
42/// provided through `.as_str()`. This
43/// is because the Pattern API and SliceIndex API is unstable
44/// i.e. can only be implemented in unstable for now.
45/// Once it gets stabilized (rust #27721/#35729) implementations
46/// can be added
47#[derive(Debug,  PartialEq, Eq, PartialOrd, Ord, Hash)]
48// `repr(transparent)` ensures that the internal layout of
49// `SoftAsciiStr` is same as that of `str`.
50// Without this, `from_unchecked` and `from_unchecked_mut`
51// are unsound.
52#[repr(transparent)]
53pub struct SoftAsciiStr(str);
54
55
56impl SoftAsciiStr {
57
58    #[inline(always)]
59    pub fn from_unchecked(s: &str) -> &SoftAsciiStr {
60        unsafe { &*( s as *const str as *const SoftAsciiStr) }
61    }
62
63    #[inline(always)]
64    #[deprecated(since = "1.0.0", note="use from_unchecked")]
65    pub fn from_str_unchecked(s: &str) -> &SoftAsciiStr {
66        SoftAsciiStr::from_unchecked(s)
67    }
68
69    #[inline(always)]
70    pub fn from_unchecked_mut(s: &mut str) -> &mut SoftAsciiStr {
71        unsafe { &mut *( s as *mut str as *mut SoftAsciiStr) }
72    }
73
74    pub fn from_str(source: &str) -> Result<&Self, FromSourceError<&str>> {
75        if source.is_ascii() {
76            Ok(Self::from_unchecked(source))
77        } else {
78            Err(FromSourceError::new(source))
79        }
80    }
81
82    /// reruns checks if the "is us-ascii" soft constraint is still valid
83    pub fn revalidate_soft_constraint(&self) -> Result<&Self, FromSourceError<&str>> {
84        if self.is_ascii() {
85            Ok(self)
86        } else {
87            Err(FromSourceError::new(self.as_str()))
88        }
89    }
90
91    #[inline(always)]
92    pub fn as_str(&self) -> &str {
93        &self.0
94    }
95
96    pub fn into_soft_ascii_string(self: Box<SoftAsciiStr>) -> SoftAsciiString {
97        //Box<SoftAsciiStr> -> Box<str> -> String -> SoftAsciiString
98        //Safe: basicaly coerces Box<SoftAsciiStr> to Box<str>
99        let as_str = Self::into_boxed_str(self);
100        let string = str::into_string(as_str);
101        SoftAsciiString::from_unchecked(string)
102    }
103
104    pub fn from_boxed_str(bs: Box<str>) -> Box<SoftAsciiStr> {
105        unsafe { Box::from_raw(Box::into_raw(bs) as *mut SoftAsciiStr) }
106    }
107
108    #[inline]
109    pub fn into_boxed_str(self: Box<SoftAsciiStr>) -> Box<str> {
110        unsafe { Box::from_raw(Box::into_raw(self) as *mut str) }
111    }
112
113    #[inline]
114    pub fn lines(&self) -> SoftAsciiLines {
115        SoftAsciiLines::from(self)
116    }
117
118    #[inline]
119    pub fn split_whitespace(&self) -> SoftAsciiSplitWhitespace {
120        SoftAsciiSplitWhitespace::from(self)
121    }
122
123    #[inline]
124    pub fn char_indices(&self) -> SoftAsciiCharIndices {
125        SoftAsciiCharIndices::from(self)
126    }
127
128    #[inline]
129    pub fn chars(&self) -> SoftAsciiChars {
130        SoftAsciiChars::from(self)
131    }
132
133    pub fn split_at(&self, mid: usize) -> (&SoftAsciiStr, &SoftAsciiStr) {
134        let (left, right) = self.as_str().split_at(mid);
135        (SoftAsciiStr::from_unchecked(left), SoftAsciiStr::from_unchecked(right))
136    }
137
138    #[deprecated(since="1.1.0", note="deprecated in std")]
139    pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &SoftAsciiStr {
140        #[allow(deprecated)]
141        SoftAsciiStr::from_unchecked(self.as_str().slice_unchecked(begin, end))
142    }
143
144    /// Proxy of [`std::str::get_unchecked`].
145    ///
146    /// Currently limited to the various range types:
147    ///
148    /// - `Range<usize>`
149    /// - `RangeInclusive<usize>`
150    /// - `RangeFrom<usize>`
151    /// - `RangeTo<usize>`
152    /// - `RangeToInclusive<usize>`
153    /// - `RangeFull`
154    ///
155    /// Once all methods on `SliceIndex` are stable this
156    /// can be implemented using `SliceIndex<SoftAsciiStr>`
157    /// bounds.
158    ///
159    /// [`std::str::get_unchecked`]: https://doc.rust-lang.org/std/primitive.str.html#method.get_unchecked
160    pub unsafe fn get_unchecked<I>(&self, index: I) -> &SoftAsciiStr
161    where
162        I: hidden::TempSliceIndexHelper
163    {
164        SoftAsciiStr::from_unchecked(self.as_str().get_unchecked::<I>(index))
165    }
166
167
168
169    /// returns a mutable `str` reference to the inner buffer
170    ///
171    /// # Soft Constraint
172    /// be aware that it is very easy to introduce bugs when
173    /// directly editing a `SoftAsciiStr` as an `str`. Still
174    /// compared to a AsciiStr implementation this won't
175    /// introduce unsafety, just possible brakeage of the
176    /// soft constraint that the data should be ascii.
177    pub fn inner_str_mut(&mut self) -> &mut str {
178        &mut self.0
179    }
180
181    pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>
182         where F: FromStr
183    {
184        self.as_str().parse::<F>()
185    }
186}
187
188mod hidden {
189    use std::slice::SliceIndex;
190    use std::ops::{Range, RangeFrom, RangeTo, RangeFull, RangeToInclusive, RangeInclusive};
191
192    /// This is a workaround to be able to provide `get_unchecked` by now on stable.
193    ///
194    /// The problem is that we can't yet implement `SliceIndex<SoftAsciiStr>` as the
195    /// methods of that trait are unstable. We also can't use `SliceIndex<SoftAsciiStr>`
196    /// as where bound as this will lead to a braking change when we add support for
197    /// `SliceIndex<SoftAsciiStr>` in the future.
198    ///
199    /// So instead we add this "helper" trait and prevent custom implementations of
200    /// it by not re-exporting it.
201    //NIT[rustc/sealed]: When/if rust provides a mechanism for sealed traits use that instead.
202    pub trait TempSliceIndexHelper: SliceIndex<str, Output=str> {}
203
204    impl TempSliceIndexHelper for Range<usize> {}
205    impl TempSliceIndexHelper for RangeInclusive<usize> {}
206    impl TempSliceIndexHelper for RangeFrom<usize> {}
207    impl TempSliceIndexHelper for RangeTo<usize> {}
208    impl TempSliceIndexHelper for RangeToInclusive<usize> {}
209    impl TempSliceIndexHelper for RangeFull {}
210
211}
212
213//TODO FromStr with custom error
214
215macro_rules! impl_wrap_returning_string {
216    (pub > $(fn $name:ident(&self$(, $param:ident: $tp:ty)*)),*) => ($(
217        impl SoftAsciiStr {
218            #[inline]
219            pub fn $name(&self $(, $param: $tp)*) -> SoftAsciiString {
220                let as_str = self.as_str();
221                let res = str::$name(as_str $(, $param)*);
222                SoftAsciiString::from_unchecked(res)
223            }
224        }
225    )*)
226}
227
228impl_wrap_returning_string!{
229    pub >
230    fn to_lowercase(&self),
231    fn to_uppercase(&self),
232    fn repeat(&self, n: usize)
233}
234
235macro_rules! impl_wrap_returning_str {
236    (pub > $(
237        $(#[$attr:meta])*
238        fn $name:ident(&self$(, $param:ident: $tp:ty)*)
239        $(#[$inner_attr:meta])*
240    ),*) => (
241        impl SoftAsciiStr {$(
242            $(#[$attr])*
243            #[inline]
244            pub fn $name(&self $(, $param: $tp)*) -> &SoftAsciiStr {
245                let as_str = self.as_str();
246                $(#[$inner_attr])* {
247                    let res = str::$name(as_str $(, $param)*);
248                    SoftAsciiStr::from_unchecked(res)
249                }
250            }
251        )*}
252    );
253}
254
255impl_wrap_returning_str!{
256    pub >
257    #[deprecated(since="1.1.0", note="deprecated in std")]
258    fn trim_right(&self) #[allow(deprecated)],
259    #[deprecated(since="1.1.0", note="deprecated in std")]
260    fn trim_left(&self) #[allow(deprecated)],
261    fn trim_end(&self),
262    fn trim_start(&self),
263    fn trim(&self)
264}
265
266macro_rules! impl_wrapping {
267    (pub > $(fn $name:ident(&self$(, $param:ident: $tp:ty)*) -> $ret:ty),*) => (
268        impl SoftAsciiStr {$(
269            #[inline]
270            pub fn $name(&self $(, $param: $tp)*) -> $ret {
271                str::$name(self.as_str() $(, $param)*)
272            }
273        )*}
274    )
275}
276
277impl_wrapping! {
278    pub >
279    fn len(&self) -> usize,
280    fn is_empty(&self) -> bool,
281    fn is_char_boundary(&self, index: usize) -> bool,
282    fn as_ptr(&self) -> *const u8,
283    fn encode_utf16(&self) -> EncodeUtf16,
284    fn is_ascii(&self) -> bool,
285    fn as_bytes(&self) -> &[u8]
286}
287
288impl AsRef<SoftAsciiStr> for SoftAsciiStr {
289    #[inline]
290    fn as_ref(&self) -> &Self {
291        self
292    }
293}
294impl AsRef<str> for SoftAsciiStr {
295    #[inline]
296    fn as_ref(&self) -> &str {
297        &self.0
298    }
299}
300
301impl AsRef<[u8]> for SoftAsciiStr {
302    #[inline]
303    fn as_ref(&self) -> &[u8] {
304        self.as_bytes()
305    }
306}
307
308impl<'a> Default for &'a SoftAsciiStr {
309    #[inline]
310    fn default() -> &'a SoftAsciiStr {
311        SoftAsciiStr::from_unchecked(Default::default())
312    }
313}
314
315impl Display for SoftAsciiStr {
316    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
317        self.0.fmt(fter)
318    }
319}
320
321macro_rules! impl_index {
322    ($($idx:ty),*) => ($(
323        impl Index<$idx> for SoftAsciiStr {
324            type Output = SoftAsciiStr;
325            fn index(&self, index: $idx) -> &Self::Output {
326                SoftAsciiStr::from_unchecked(self.0.index(index))
327            }
328        }
329    )*);
330}
331
332impl_index! {
333    Range<usize>,
334    RangeFrom<usize>,
335    RangeTo<usize>,
336    RangeFull
337}
338
339impl ToOwned for SoftAsciiStr {
340    type Owned = SoftAsciiString;
341
342    fn to_owned(&self) -> SoftAsciiString {
343        SoftAsciiString::from_unchecked(String::from(&self.0))
344    }
345}
346
347impl PartialEq<SoftAsciiString> for SoftAsciiStr {
348    fn eq(&self, other: &SoftAsciiString) -> bool {
349        self == &**other
350    }
351}
352
353impl<'a> PartialEq<SoftAsciiString> for &'a SoftAsciiStr {
354    fn eq(&self, other: &SoftAsciiString) -> bool {
355        *self == &**other
356    }
357}
358
359impl PartialEq<SoftAsciiStr> for String {
360    fn eq(&self, other: &SoftAsciiStr) -> bool {
361        self.as_str() == other.as_str()
362    }
363}
364
365impl PartialEq<String> for SoftAsciiStr {
366    fn eq(&self, other: &String) -> bool {
367        self.as_str() == other.as_str()
368    }
369}
370
371impl<'a> PartialEq<&'a SoftAsciiStr> for String {
372    fn eq(&self, other: &&'a SoftAsciiStr) -> bool {
373        self.as_str() == other.as_str()
374    }
375}
376
377impl<'a> PartialEq<String> for &'a SoftAsciiStr {
378    fn eq(&self, other: &String) -> bool {
379        self.as_str() == other.as_str()
380    }
381}
382
383impl<'a> PartialEq<SoftAsciiStr> for str {
384    fn eq(&self, other: &SoftAsciiStr) -> bool {
385        self == other.as_str()
386    }
387}
388
389impl PartialEq<str> for SoftAsciiStr {
390    fn eq(&self, other: &str) -> bool {
391        self.as_str() == other
392    }
393}
394
395impl<'a> PartialEq<SoftAsciiStr> for Cow<'a, SoftAsciiStr> {
396    fn eq(&self, other: &SoftAsciiStr) -> bool {
397        self.as_str() == other.as_str()
398    }
399}
400
401impl<'a> PartialEq<Cow<'a, SoftAsciiStr>> for SoftAsciiStr {
402    fn eq(&self, other: &Cow<'a, SoftAsciiStr>) -> bool {
403        self.as_str() == other.as_str()
404    }
405}
406
407impl<'a, 'b> PartialEq<&'b SoftAsciiStr> for Cow<'a, SoftAsciiStr> {
408    fn eq(&self, other: &&'b SoftAsciiStr) -> bool {
409        self.as_str() == other.as_str()
410    }
411}
412
413impl<'a, 'b> PartialEq<Cow<'a, SoftAsciiStr>> for &'a SoftAsciiStr {
414    fn eq(&self, other: &Cow<'a, SoftAsciiStr>) -> bool {
415        self.as_str() == other.as_str()
416    }
417}
418
419impl<'a> PartialEq<SoftAsciiStr> for Cow<'a, str> {
420    fn eq(&self, other: &SoftAsciiStr) -> bool {
421        &*self == other.as_str()
422    }
423}
424
425impl<'a> PartialEq<Cow<'a, str>> for SoftAsciiStr {
426    fn eq(&self, other: &Cow<'a, str>) -> bool {
427        self.as_str() == &*other
428    }
429}
430
431impl<'a, 'b> PartialEq<&'b SoftAsciiStr> for Cow<'a, str> {
432    fn eq(&self, other: &&'b SoftAsciiStr) -> bool {
433        &*self == other.as_str()
434    }
435}
436
437impl<'a, 'b> PartialEq<Cow<'b, str>> for &'a SoftAsciiStr {
438    fn eq(&self, other: &Cow<'b, str>) -> bool {
439        self.as_str() == &*other
440    }
441}
442
443impl PartialEq<SoftAsciiStr> for OsString {
444    fn eq(&self, other: &SoftAsciiStr) -> bool {
445        other.as_str().eq(self)
446    }
447}
448
449impl PartialEq<OsString> for SoftAsciiStr {
450    fn eq(&self, other: &OsString) -> bool {
451        self.as_str().eq(other)
452    }
453}
454
455impl<'a> PartialEq<&'a SoftAsciiStr> for OsString {
456    fn eq(&self, other: &&'a SoftAsciiStr) -> bool {
457        other.as_str().eq(self)
458    }
459}
460
461impl<'a> PartialEq<OsString> for &'a SoftAsciiStr {
462    fn eq(&self, other: &OsString) -> bool {
463        self.as_str().eq(other)
464    }
465}
466
467impl PartialEq<SoftAsciiStr> for OsStr {
468    fn eq(&self, other: &SoftAsciiStr) -> bool {
469        other.as_str().eq(self)
470    }
471}
472impl PartialEq<OsStr> for SoftAsciiStr {
473    fn eq(&self, other: &OsStr) -> bool {
474        self.as_str().eq(other)
475    }
476}
477
478impl AsRef<OsStr> for SoftAsciiStr {
479    fn as_ref(&self) -> &OsStr {
480        self.as_str().as_ref()
481    }
482}
483
484impl AsRef<Path> for SoftAsciiStr {
485    fn as_ref(&self) -> &Path {
486        self.as_str().as_ref()
487    }
488}
489
490impl ToSocketAddrs for SoftAsciiStr {
491    type Iter = vec::IntoIter<SocketAddr>;
492
493    fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<SocketAddr>> {
494        self.as_str().to_socket_addrs()
495    }
496}
497
498/// a wrapper around `Chars` turning each char into a `SoftAsciiChar`
499///
500/// This iterator is returned by `SoftAsciiChar::chars(&self)` instead
501/// of `Chars`.
502#[derive(Debug, Clone)]
503pub struct SoftAsciiChars<'a> {
504    inner: str::Chars<'a>
505}
506
507impl<'a> From<&'a SoftAsciiStr> for SoftAsciiChars<'a> {
508    fn from(s: &'a SoftAsciiStr) -> SoftAsciiChars<'a> {
509        SoftAsciiChars {
510            inner: s.as_str().chars()
511        }
512    }
513}
514
515impl<'a> Iterator for SoftAsciiChars<'a> {
516    type Item = SoftAsciiChar;
517
518    fn next(&mut self) -> Option<Self::Item> {
519        self.inner.next()
520            .map(SoftAsciiChar::from_unchecked)
521
522    }
523
524    #[inline]
525    fn count(self) -> usize {
526        self.inner.count()
527    }
528
529    #[inline]
530    fn size_hint(&self) -> (usize, Option<usize>) {
531        self.inner.size_hint()
532    }
533
534    fn last(self) -> Option<Self::Item> {
535        self.inner.last()
536            .map(SoftAsciiChar::from_unchecked)
537    }
538}
539
540impl<'a> DoubleEndedIterator for SoftAsciiChars<'a> {
541    fn next_back(&mut self) -> Option<Self::Item> {
542        self.inner.next_back()
543            .map(SoftAsciiChar::from_unchecked)
544    }
545}
546
547/// a wrapper around `CharsIndices` turning each char into a `SoftAsciiChar`
548///
549/// This iterator is returned by `SoftAsciiChar::char_indices(&self)` instead
550/// of `CharIndices`.
551#[derive(Debug, Clone)]
552pub struct SoftAsciiCharIndices<'a> {
553    inner: str::CharIndices<'a>
554}
555
556impl<'a> From<&'a SoftAsciiStr> for SoftAsciiCharIndices<'a> {
557    fn from(s: &'a SoftAsciiStr) -> SoftAsciiCharIndices<'a> {
558        SoftAsciiCharIndices {
559            inner: s.as_str().char_indices()
560        }
561    }
562}
563
564impl<'a> Iterator for SoftAsciiCharIndices<'a> {
565    type Item = (usize, SoftAsciiChar);
566
567    fn next(&mut self) -> Option<Self::Item> {
568        self.inner.next()
569            .map(|(idx, ch)| {
570                (idx, SoftAsciiChar::from_unchecked(ch))
571            })
572    }
573
574    #[inline]
575    fn count(self) -> usize {
576        self.inner.count()
577    }
578
579    #[inline]
580    fn size_hint(&self) -> (usize, Option<usize>) {
581        self.inner.size_hint()
582    }
583
584    fn last(self) -> Option<Self::Item> {
585        self.inner.last()
586            .map(|(idx, ch)| {
587                (idx, SoftAsciiChar::from_unchecked(ch))
588            })
589    }
590}
591
592impl<'a> DoubleEndedIterator for SoftAsciiCharIndices<'a> {
593    fn next_back(&mut self) -> Option<Self::Item> {
594        self.inner.next_back()
595            .map(|(idx, ch)| {
596                (idx, SoftAsciiChar::from_unchecked(ch))
597            })
598    }
599}
600
601/// a wrapper around `Lines` turning each line into a `SoftAsciiStr`
602///
603/// This iterator is returned by `SoftAsciiChar::lines(&self)` instead
604/// of `Lines`.
605#[derive(Debug, Clone)]
606pub struct SoftAsciiLines<'a> {
607    inner: str::Lines<'a>
608}
609
610impl<'a> From<&'a SoftAsciiStr> for SoftAsciiLines<'a> {
611    fn from(s: &'a SoftAsciiStr) -> SoftAsciiLines<'a> {
612        SoftAsciiLines {
613            inner: s.as_str().lines()
614        }
615    }
616}
617
618impl<'a> Iterator for SoftAsciiLines<'a> {
619    type Item = &'a SoftAsciiStr;
620
621    fn next(&mut self) -> Option<Self::Item> {
622        self.inner.next()
623            .map(SoftAsciiStr::from_unchecked)
624    }
625
626    fn size_hint(&self) -> (usize, Option<usize>) {
627        self.inner.size_hint()
628    }
629
630}
631
632impl<'a> DoubleEndedIterator for SoftAsciiLines<'a> {
633    fn next_back(&mut self) -> Option<Self::Item> {
634        self.inner.next_back()
635            .map(SoftAsciiStr::from_unchecked)
636    }
637}
638
639/// a wrapper around `SplitWhitespace` turning each section into a `SoftAsciiStr`
640///
641/// This iterator is returned by `SoftAsciiChar::split_whitespace(&self)` instead
642/// of `SplitWhitespace`.
643#[derive(Clone)]
644pub struct SoftAsciiSplitWhitespace<'a> {
645    inner: str::SplitWhitespace<'a>
646}
647
648
649impl<'a> From<&'a SoftAsciiStr> for SoftAsciiSplitWhitespace<'a> {
650    fn from(s: &'a SoftAsciiStr) -> SoftAsciiSplitWhitespace<'a> {
651        SoftAsciiSplitWhitespace {
652            inner: s.as_str().split_whitespace()
653        }
654    }
655}
656
657impl<'a> Iterator for SoftAsciiSplitWhitespace<'a> {
658    type Item = &'a SoftAsciiStr;
659
660    fn next(&mut self) -> Option<Self::Item> {
661        self.inner.next()
662            .map(SoftAsciiStr::from_unchecked)
663    }
664}
665
666impl<'a> DoubleEndedIterator for SoftAsciiSplitWhitespace<'a> {
667    fn next_back(&mut self) -> Option<Self::Item> {
668        self.inner.next_back()
669            .map(SoftAsciiStr::from_unchecked)
670    }
671}
672
673
674#[cfg(test)]
675mod test {
676    const UTF8_STR: &str = "❤ == <3";
677    //TODO write tests for simple wrapper
678    // (use some fuzzing like test library and make sure operation on
679    //  `SoftAsciiStr` behave the same as on `str`)
680
681    mod SoftAsciiStr {
682        #![allow(non_snake_case)]
683        use super::*;
684        use super::super::SoftAsciiStr;
685        use std::ops::{Range, RangeInclusive, RangeFrom, RangeTo, RangeToInclusive, RangeFull};
686
687        #[test]
688        fn from_str() {
689            assert_eq!(
690                SoftAsciiStr::from_str("hy ho\x00\x01\x02\x03").unwrap(),
691                "hy ho\x00\x01\x02\x03"
692            );
693            assert!(SoftAsciiStr::from_str("↓").is_err());
694        }
695
696        #[test]
697        fn from_unchecked() {
698            assert_eq!(
699                SoftAsciiStr::from_unchecked(UTF8_STR),
700                UTF8_STR
701            );
702        }
703
704        #[test]
705        fn revalidate_soft_constraint() {
706            let res = SoftAsciiStr::from_unchecked(UTF8_STR).revalidate_soft_constraint();
707            assert_eq!(UTF8_STR, assert_err!(res).into_source());
708
709            let res = SoftAsciiStr::from_unchecked("hy").revalidate_soft_constraint();
710            let res: &SoftAsciiStr = assert_ok!(res);
711            assert_eq!(
712                res,
713                "hy"
714            );
715
716        }
717
718        #[test]
719        fn compile_bounds__get_unchecked() {
720            let _ = SoftAsciiStr::get_unchecked::<Range<usize>>;
721            let _ = SoftAsciiStr::get_unchecked::<RangeInclusive<usize>>;
722            let _ = SoftAsciiStr::get_unchecked::<RangeFrom<usize>>;
723            let _ = SoftAsciiStr::get_unchecked::<RangeTo<usize>>;
724            let _ = SoftAsciiStr::get_unchecked::<RangeToInclusive<usize>>;
725            let _ = SoftAsciiStr::get_unchecked::<RangeFull>;
726        }
727    }
728
729}