Skip to content

← Ownership II: Borrowing and the Borrow Checker step 1 of 24

Easy Primitives

Move or borrow: the two ways to pass a value

Implement two functions that read borrowed text:

pub fn char_count(text: &str) -> usize
pub fn shout(text: &str) -> String

char_count returns the number of Unicode scalar values (chars) in the original text. shout returns a new String containing Rust's Unicode uppercase conversion of the text followed by one additional !. Preserve spaces and existing punctuation. Neither function changes the caller's text.

Input Count Shout
"" 0 "!"
"hi" 2 "HI!"
"héllo" 5 "HÉLLO!"
"Ok!" 3 "OK!!"
"ß" 1 "SS!"

The starter counts bytes instead of scalar values and leaves shout unfinished. Keep both function signatures and fix those two bodies.

Borrowing keeps the caller's string available

Passing a String by value transfers ownership. Passing &text to these functions borrows its contents instead, so the caller can use it again:

let text = String::from("héllo");
assert_eq!(char_count(&text), 5);
assert_eq!(shout(&text), "HÉLLO!");
assert_eq!(text, "héllo");

Not every by-value argument moves an owned allocation. Values such as integers and shared references implement Copy; passing them by value copies them. Copying a &str copies its pointer and length, not the text's bytes.

Safe borrowing prevents a reference from being used after its referent is gone. References must be valid and non-null; optional borrowed data can use Option<&T>. A reference is not necessarily short-lived: string literals, for example, have type &'static str.

Why the parameters use &str

These functions need text, not the ownership or capacity of a String. &str accepts string literals, borrowed String contents and string slices:

let text = String::from("hello");
assert_eq!(char_count("hi"), 2);
assert_eq!(char_count(&text), 5);
assert_eq!(char_count(&text[1..4]), 3);

The borrow of String is coerced to a string slice at the call. Slicing uses byte offsets, so both endpoints must be valid UTF-8 boundaries; the ASCII slice above is valid. See Borrow str instead of String for more on choosing borrowed parameter types.

Changing a parameter to String would make the harness's existing &text call a type mismatch. If you also changed that call to pass text by value, subsequent use of the moved string would fail. Keep the requested borrowing interface.

Count scalar values, then build a separate output

str::len() reports bytes. "héllo" occupies six bytes but contains five scalar values. Use the chars iterator for the requested count.

Scalar values are not always the characters a reader perceives. For example, "e\u{301}" contains e followed by a combining acute accent: two scalar values, even when displayed as one accented letter. Count both; do not normalize or combine them.

Uppercasing can change length. The ß example becomes SS, while the count still describes the original input. Use str::to_uppercase to build the owned uppercase result, then append one !. ASCII-only conversion would miss non-ASCII letters.

Submissions must compile, pass the cases and pass the Rust Clippy gate.