How to index a String in Rust
Yes, indexing into a string is not available in Rust. The reason for this is that Rust strings are encoded in UTF-8 internally, so the concept of indexing itself would be ambiguous, and people would misuse it: byte indexing is fast, but almost always incorrect (when your text contains non-ASCII symbols, byte indexing may leave you inside a character, which is really bad if you need text processing), while char indexing is not free because UTF-8 is a variable-length encoding, so you have to traverse the entire string to find the required code point.
If you are certain that your strings contain ASCII characters only, you can use the as_bytes()
method on &str
which returns a byte slice, and then index into this slice:
let num_string = num.to_string();
// ...
let b: u8 = num_string.as_bytes()[i];
let c: char = b as char; // if you need to get the character as a unicode code point
If you do need to index code points, you have to use the char()
iterator:
num_string.chars().nth(i).unwrap()
As I said above, this would require traversing the entire iterator up to the i
th code element.
Finally, in many cases of text processing, it is actually necessary to work with grapheme clusters rather than with code points or bytes. With the help of the unicode-segmentation crate, you can index into grapheme clusters as well:
use unicode_segmentation::UnicodeSegmentation
let string: String = ...;
UnicodeSegmentation::graphemes(&string, true).nth(i).unwrap()
Naturally, grapheme cluster indexing has the same requirement of traversing the entire string as indexing into code points.
The correct approach to doing this sort of thing in Rust is not indexing but iteration. The main problem here is that Rust's strings are encoded in UTF-8, a variable-length encoding for Unicode characters. Being variable in length, the memory position of the nth character can't determined without looking at the string. This also means that accessing the nth character has a runtime of O(n)!
In this special case, you can iterate over the bytes, because your string is known to only contain the characters 0–9 (iterating over the characters is the more general solution but is a little less efficient).
Here is some idiomatic code to achieve this (playground):
fn is_palindrome(num: u64) -> bool {
let num_string = num.to_string();
let half = num_string.len() / 2;
num_string.bytes().take(half).eq(num_string.bytes().rev().take(half))
}
We go through the bytes in the string both forwards (num_string.bytes().take(half)
) and backwards (num_string.bytes().rev().take(half)
) simultaneously; the .take(half)
part is there to halve the amount of work done. We then simply compare one iterator to the other one to ensure at each step that the nth and nth last bytes are equivalent; if they are, it returns true; if not, false.