Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to index a String in Rust

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?

like image 718
Sam Myers Avatar asked Jul 02 '14 22:07

Sam Myers


People also ask

Can you index a string in Rust?

Yes, indexing into a string is not available in Rust.

Can you index a string?

Because strings, like lists and tuples, are a sequence-based data type, it can be accessed through indexing and slicing.

How do you slice a string in Rust?

Strings can be sliced using the index operator: let slice = &"Golden Eagle"[.. 6]; println!( "{}", slice);


2 Answers

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 ith 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.

like image 116
Vladimir Matveev Avatar answered Sep 20 '22 21:09

Vladimir Matveev


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.

like image 34
Chris Morgan Avatar answered Sep 23 '22 21:09

Chris Morgan