It could slice on bytes and return a slice of bytes since the String type is a wrapper over Vec<u8>.
I suppose one could have it return StrWithInvalidSurrounds, where just the first (at most) 3 and last (at most) 3 bytes might be invalid, which would then allow for O(1) revalidation to a &str, and even other operations like continuing to slice... But this is even more clunky for actual use!
I think a moderately less clunky API might have been to not use integers for byte indexing, but instead some ByteIndex wrapper type that string operations return, meaning one can't just write `s[..5]` in an attempt to get the first 5 characters of the string.
(Also, there's str::get that returns an Option: https://doc.rust-lang.org/std/primitive.str.html#method.get )