Search code examples
rustconstantsslice

String slice in a const function


How do you do a string slice in a const function?

const fn cya_first_char(inc: &str) -> &str {
    &inc[1..]
    //  ~~~~~ Error: cannot call non-const operator in constant functions
}

Solution

  • str doesn't have too many const methods, but as_bytes is, and [u8] has a const split_at method. Then you can turn that back into str with std::str::from_utf8.

    const fn take_first_bytes_of_str(s: &str, i: usize) -> &str {
        let Ok(s) = std::str::from_utf8(s.as_bytes().split_at(i).1) else {
            panic!("first character was more than one byte");
        };
        s
    }
    
    const fn cya_first_char(inc: &str) -> &str {
        take_first_bytes_of_str(inc, 1)
    }
    

    This will scan the entire rest of the string for validity every time, so it will detrimentally affect performance if called on long strings or when called many times. If that's what you need, then it'll probably be worth reimplementing is_char_boundary in const and using that with from_utf8_unchecked.

    Here is one that takes the first character instead of the first byte.

    const fn take_first_char_of_str(s: &str) -> &str {
        let mut i = 1;
    
        while i < 5 {
            let (first, rest) = s.as_bytes().split_at(i);
            if std::str::from_utf8(first).is_ok() {
                // SAFETY: if `first` is valid UTF-8, then `rest` is valid UTF-8
                unsafe {
                    return std::str::from_utf8_unchecked(rest);
                }
            }
            i += 1;
        }
        unreachable!()
    }
    
    const fn cya_first_char(inc: &str) -> &str {
        take_first_char_of_str(inc)
    }
    

    This one gets around the performance issue by only checking the first character for validity, and assuming the rest. This way, it only has to check at most 1 + 2 + 3 + 4 = 10 bytes for any length of string.

    This is still not ideal, but will run in constant time.