I am attempting to index a string in Rust, but the compiler throws an error. My code (Project Euler problem 4, playground):
fn is_palindrome(num: u64) -> bool {
let num_string = num.to_string();
let num_length = num_string.len();
for i in 0 .. num_length / 2 {
if num_string[i] != num_string[(num_length - 1) - i] {
return false;
}
}
true
}
The error:
error[E0277]: the trait bound `std::string::String: std::ops::Index<usize>` is not satisfied
--> <anon>:7:12
|
7 | if num_string[i] != num_string[(num_length - 1) - i] {
| ^^^^^^^^^^^^^
|
= note: the type `std::string::String` cannot be indexed by `usize`
Is there a reason why String
can not indexed? How can I access the data then?
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.
Yes, indexing into a string was recently removed. This is done because Rust strings are UTF-8 internally, so the concept of indexing itself is ambigous, and people tend to 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 variable-length encoding.
If you are certain that your strings only contain ASCII characters only, you can use Ascii
type (using to_ascii()
method) or as_bytes()
method on &str
which returns byte slice:
let num_string = num.to_str().as_slice();
// ...
num_string.as_bytes()[i]
If you need character indexing, you should use char_at()
method:
num_string.char_at(i)
If what you are looking for is something similar to an index, you can use
.chars()
and .nth()
on a string.
.chars()
-> Returns an iterator over the char
s of a string slice.
.nth()
-> Returns the nth element of the iterator, in an Option
Now you can use the above in several ways, for example:
let s: String = String::from("abc");
//If you are sure
println!("{}", s.chars().nth(x).unwrap());
//or if not
println!("{}", s.chars().nth(x).expect("message"));