String slice in a const function

65 Views Asked by At

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
}
2

There are 2 best solutions below

4
drewtato On BEST ANSWER

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.

0
Stargateur On

There is not a lot of const fn in &str for now. If you want you can use array and from_utf8() that is const and test how robust is rustc using some recursive:

// cause brute force is sometime more simple
const fn cya_first_char_aux(inc: &[u8]) -> &str {
    match inc {
        [_, rest @ ..] => match std::str::from_utf8(rest) {
            Ok(s) => s,
            _ => cya_first_char_aux(rest),
        },
        _ => "",
    }
}

const fn cya_first_char(inc: &str) -> &str {
    cya_first_char_aux(inc.as_bytes())
}

fn main() {
    println!("{}", cya_first_char("Hello world!"));
    println!("{}", cya_first_char("老虎 Léopard"));
    println!("{}", cya_first_char(""));
}