We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ground Rules: Values, Types, Control Flow step 24 of 24
UTF-8 strings: bytes, scalar values and slicing
Return a string's UTF-8 byte length, its Unicode scalar-value count, and a new string with those scalar values in reverse order.
pub fn char_stats(text: &str) -> (usize, usize, String)
| Input | Output |
|---|---|
"héllo" |
(6, 5, "olléh") |
"abc" |
(3, 3, "cba") |
"" |
(0, 0, "") |
Preserve every scalar value, including spaces, control characters and
combining marks. Do not normalize the input or group grapheme clusters.
For example, "e\u{301}" becomes "\u{301}e", with byte length three
and scalar count two.
Choose the view you need
A str stores valid UTF-8. One Unicode scalar value occupies between one
and four bytes, so a byte position and a scalar position are different:
fn main() {
let text = "héllo";
assert_eq!(text.len(), 6);
assert_eq!(text.chars().count(), 5);
assert_eq!(text.as_bytes()[1], 0xC3);
assert_eq!(text.chars().nth(1), Some('é'));
assert_eq!(text.char_indices().collect::<Vec<_>>(),
[(0, 'h'), (1, 'é'), (3, 'l'), (4, 'l'), (5, 'o')]);
}
len() reads the stored byte length in constant time. chars().count()
scans the string. chars().nth(k) advances an iterator to the requested
scalar position and returns None if it does not exist. Repeatedly creating
a fresh iterator to find successive positions can repeat the same work;
use one iterator when traversing the whole string.
char_indices() pairs each scalar value with its starting byte offset.
These offsets are useful when you need a substring from the original text.
For repeated random access, storing offsets or collecting scalar values is
another design option, with its own setup and storage costs.
Integer indexing is not supported for str
The starter uses text[i], which fails with E0277 because str does not
implement integer indexing. Even ASCII-only input does not make that
expression valid. Choose a byte-oriented or scalar-oriented operation
explicitly rather than replacing the index with a cast.
For a byte, text.as_bytes().get(i) returns Option<&u8>. For a scalar,
text.chars().nth(i) returns Option<char>. The starter also returns the
byte length twice; change the second result to a scalar count.
String slices use byte offsets
&text[start..end] borrows the half-open byte interval without copying it.
It requires start <= end <= text.len() and both endpoints to be UTF-8
boundaries. Otherwise, indexing panics. For "héllo", byte offsets one
and three surround é, while offset two lies inside its encoding.
Use get when an invalid range should return None instead of panicking:
fn main() {
let text = "héllo";
assert_eq!(&text[1..3], "é");
assert_eq!(text.get(1..3), Some("é"));
assert_eq!(text.get(0..2), None);
assert_eq!(text.get(0..100), None);
assert_eq!(text.get(text.len()..text.len()), Some(""));
assert!(!text.is_char_boundary(2));
assert!(text.is_char_boundary(text.len()));
}
is_char_boundary is true at the start and end of a string and false beyond
its length. Checking endpoints alone does not establish that the start
precedes the end; get checks the whole range. A slice failure can come
from an out-of-bounds range, reversed endpoints or a split UTF-8 encoding.
Reverse scalar values, then encode them again
text.chars().rev().collect::<String>() iterates from the end and builds a
valid UTF-8 string. Reversing individual bytes instead does not preserve
UTF-8 encoding when the input contains multibyte scalar values.
Scalar reversal is the contract for this exercise. It need not preserve the groups a reader perceives as characters:
fn main() {
let text = "e\u{301}";
let reversed: String = text.chars().rev().collect();
assert_eq!(reversed, "\u{301}e");
assert_eq!(reversed.len(), text.len());
assert_eq!(reversed.chars().count(), 2);
}
Grapheme clusters may combine a base letter with marks, or several scalar values into an emoji. The standard library has no built-in grapheme iterator. Applications that need grapheme segmentation can use a suitable Unicode library; that is a different operation from the reversal requested here.
Clippy's default iter_nth_zero warning suggests next() instead of
nth(0). Its chars_next_cmp warning suggests a prefix check such as
starts_with('x') instead of chars().next() == Some('x'). Submissions
must compile, pass the tests and pass Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.