35

In Python, this would be final_char = mystring[-1]. How can I do the same in Rust?

I have tried

mystring[mystring.len() - 1]

but I get the error the type 'str' cannot be indexed by 'usize'

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
B B
  • 1,116
  • 2
  • 8
  • 20
  • 1
    [Please format your code carefully](https://meta.stackexchange.com/questions/55437/how-can-the-backtick-character-be-included-in-code). – user202729 Feb 06 '18 at 11:51
  • I've adjusted the error message format as requested – B B Feb 06 '18 at 11:53
  • See also [Getting a single character out of a string](https://stackoverflow.com/q/30811107/155423). – Shepmaster Feb 06 '18 at 13:03
  • 1
    By character you mean unicode codepoint? – CodesInChaos Feb 06 '18 at 14:10
  • @CodesInChaos Not sure what a unicode codepoint is, but basically, what I was trying to do was if I had `abcd` as my string, I would want to get `d` as the final char. My current use case did not involve the use of strings like `あいうえお`, but if so, then I would have liked to get the final char `お`. – B B Feb 06 '18 at 14:20
  • 1
    @BB The question you should perhaps answer, mostly for yourself, is whether the final "character" of `café` is `é` or `´`. If it should be `é`, you want to iterate by graphemes instead of `char`s. – trent Feb 06 '18 at 14:50
  • See also [Why is capitalizing the first letter of a string so convoluted in Rust?](https://stackoverflow.com/q/38406793/155423) – Shepmaster Feb 06 '18 at 14:54
  • Likewise, what do you want the second-to-last character of "Åström" and "Åström" to be? Welcome to [precomposed characters](https://en.wikipedia.org/wiki/Precomposed_character); they [have different answers](https://play.rust-lang.org/?gist=37f106fbc7b537edf0ea66b64ed8b63f&version=stable). – Shepmaster Feb 06 '18 at 15:04
  • @trentcl I would expect the final "character" of `café` to be `é`. You mentioned graphemes, and I did some googling to check it out (for potential future use cases). Looks like I may have to import an external crate to do so as mentioned here: https://users.rust-lang.org/t/how-do-you-iterate-over-grapheme-clusters-of-a-string-in-rust/11338/2 – B B Feb 06 '18 at 15:10
  • @Shepmaster it looks like using rust for text wrangling (of non ascii text) would prove to be a fairly difficult exercise. Although, I believe python also had these difficulties prior to python 3. – B B Feb 06 '18 at 15:12
  • 3
    @BB I think you are looking at it backwards. Text processing is **hard** because human languages are complicated and encoding them into computers is also complicated. Rust has a small set of opinions (e.g. that strings are UTF-8), but otherwise has to be hands-off in order to allow programmers to build the correct abstractions they need for their cases. As other people have mentioned, thinking of strings as bags of characters is ultimately incorrect because what a "character" is is poorly defined to start with in our global world. – Shepmaster Feb 06 '18 at 15:19
  • @Shepmaster That makes sense. I haven't had to do any text wrangling with "non-generic" characters in years, But I do remember the previous experiences to be fairly frustrating. – B B Feb 06 '18 at 15:22
  • 1
    @BB Python 3 made great strides in correct Unicode handling, but it still doesn't offer an easy way to work with graphemes -- Rust (with the `unicode-segmentation` crate) is actually better in this regard. If you copy and paste `"café"[-1]` into your Python interpreter, it probably won't give you `"é"`. – trent Feb 06 '18 at 15:31
  • 1
    @trentcl You're right, I didn't get `"é"`, I got `'́'`, whatever that is. – B B Feb 06 '18 at 15:33

3 Answers3

42

That is how you get the last char (which may not be what you think of as a "character"):

mystring.chars().last().unwrap();

Use unwrap only if you are sure that there is at least one char in your string.


Warning: About the general case (do the same thing as mystring[-n] in Python): UTF-8 strings are not to be used through indexing, because indexing is not a O(1) operation (a string in Rust is not an array). Please read this for more information.

However, if you want to index from the end like in Python, you must do this in Rust:

mystring.chars().rev().nth(n - 1) // Python: mystring[-n]

and check if there is such a character.

If you miss the simplicity of Python syntax, you can write your own extension:

trait StrExt {
    fn from_end(&self, n: usize) -> char;
}

impl<'a> StrExt for &'a str {
    fn from_end(&self, n: usize) -> char {
        self.chars().rev().nth(n).expect("Index out of range in 'from_end'")
    }
}

fn main() {
    println!("{}", "foobar".from_end(2)) // prints 'b'
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • `mystring.chars().last().unwrap()` looks easier to read, and is more concise. Is there a way to use it without `unwrap?`. I had the impression that using `unwrap` was discouraged. – B B Feb 06 '18 at 12:06
  • 1
    @BB [Pattern matching.](https://doc.rust-lang.org/1.6.0/book/patterns.html) – erip Feb 06 '18 at 12:11
  • 1
    @erip Haven't gotten to that part of the book yet, but thanks for the headsup! – B B Feb 06 '18 at 12:12
  • 1
    @BB Best of luck! – erip Feb 06 '18 at 12:13
  • 3
    @BB It should also be noted that this is an O(n) operation in Rust, as opposed to Python's O(1). You *should not* treat strings like arrays. – DK. Feb 06 '18 at 12:17
  • @DK Well, I was hoping to get the canonical way to do it, since I figured that getting the final element of a string was a fairly common operation. I didn't have much luck googling for the answer however. Does rust have a O*(1) version for this as well? – B B Feb 06 '18 at 12:21
  • 8
    @DK To clarify, getting the last character is an O(1) operation. Getting the nth character from either end is O(n) where n is the index. – interjay Feb 06 '18 at 12:30
  • @interjay Thanks! – B B Feb 06 '18 at 12:42
  • 6
    _"I had the impression that using `unwrap` was discouraged"_ It's a matter of whether [to `panic!` or not to `panic!`](https://doc.rust-lang.org/book/second-edition/ch09-03-to-panic-or-not-to-panic.html). `unwrap` is to express that any potential error coming from there is irrecoverable and should not happen. There still are legitimate cases for this. – E_net4 Feb 06 '18 at 13:01
  • @E_net4: I still prefer `expect`. – Matthieu M. Feb 06 '18 at 17:38
  • 1
    @MatthieuM. I've had this conversation before. :) Feel free to replace `unwrap` with `expect` above. The latter can often be better, but it's also noise if the only message you can think of is "IT SHOULD WORK!!!1". – E_net4 Feb 06 '18 at 17:44
20

One option is to use slices. Here's an example:

let len = my_str.len();
let final_str = &my_str[len-1..];

This returns a string slice from position len-1 through the end of the string. That is to say, the last byte of your string. If your string consists of only ASCII values, then you'll get the final character of your string.

The reason why this only works with ASCII values is because they only ever require one byte of storage. Anything else, and Rust is likely to panic at runtime. This is what happens when you try to slice out one byte from a 2-byte character.

For a more detailed explanation, please see the strings section of the Rust book.

nyghtly
  • 222
  • 3
  • 8
  • 1
    Saying that a solution has problems *but not stating what those problems are* creates a very poor answer. Links to external resources are great, but any pertinent information needs to be included in the answer itself. – Shepmaster Aug 13 '18 at 15:03
  • *That is, the final value contained in the string.* — this is not true unless you are dealing with ASCII-only text; which is less and less true as time progresses. Think about emoji. – Shepmaster Aug 13 '18 at 15:04
4

As @Boiethios mentioned

let last_ch = mystring.chars().last().unwrap();

Or

let last_ch = codes.chars().rev().nth(0).unwrap();

I would rather have (how hard is that!?)

let last_ch = codes.chars(-1); // Not implemented as rustc 1.56.1
ypwang
  • 89
  • 2