How to get the last character of a &str?
Asked Answered
H

5

40

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'

Hallux answered 6/2, 2018 at 11:46 Comment(14)
Please format your code carefully.Bercy
I've adjusted the error message format as requestedHallux
See also Getting a single character out of a string.Emaciated
By character you mean unicode codepoint?Goodygoody
@Goodygoody 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 .Hallux
@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 chars.Alonsoalonzo
See also Why is capitalizing the first letter of a string so convoluted in Rust?Emaciated
Likewise, what do you want the second-to-last character of "Åström" and "Åström" to be? Welcome to precomposed characters; they have different answers.Emaciated
@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: users.rust-lang.org/t/…Hallux
@Emaciated 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.Hallux
@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.Emaciated
@Emaciated 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.Hallux
@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 "é".Alonsoalonzo
@trentcl You're right, I didn't get "é", I got '́', whatever that is.Hallux
T
45

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'
}
Testamentary answered 6/2, 2018 at 11:54 Comment(11)
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.Hallux
@BB Pattern matching.Tune
@Tune Haven't gotten to that part of the book yet, but thanks for the headsup!Hallux
@BB Best of luck!Tune
@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.Agueweed
@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?Hallux
@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.Antimicrobial
@Antimicrobial Thanks!Hallux
"I had the impression that using unwrap was discouraged" It's a matter of whether to panic! or not to panic!. unwrap is to express that any potential error coming from there is irrecoverable and should not happen. There still are legitimate cases for this.Joiner
@E_net4: I still prefer expect.Comportment
@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".Joiner
A
22

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.

Archimedes answered 13/8, 2018 at 14:45 Comment(2)
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.Emaciated
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.Emaciated
C
5

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
Cupric answered 23/11, 2021 at 4:32 Comment(0)
T
0

The optimal way is .chars().next_back().

The .chars().last() would consume the whole string, so has linear complexity.

Thunderbolt answered 14/7 at 8:1 Comment(0)
E
-1

There is another method, depending on context. If you just want to assess what the last character of a string is, you can simply use the ends_with method. For example, say I would like to remove '/', if this is the last character of a user-inputted String path. Then, I can simply do:

if path.ends_with('/') {
    path.pop();
}
Ere answered 18/5 at 22:5 Comment(3)
This is not answering the question. The question was how to retrieve the last unknown character in a string (giving a Python code that that's what it does), while your answer explains how to check if a string ends with something.Euphrates
As I said, it depends on context... someone might want to retrieve the last character of a string to make an assessment - in which case, I think my response would be helpful.Ere
Mentioning .pop() is a good addition I say. It fits the title just with the side-effect of also removing it from the String. Some may be interested in that.Njord

© 2022 - 2024 — McMap. All rights reserved.