Hone logo
Hone
Problems

Rust Lifetime Fundamentals: Managing Data Borrowing

This challenge focuses on understanding and implementing basic Rust lifetimes. Lifetimes are a crucial concept in Rust for ensuring memory safety by preventing dangling pointers. By successfully completing this challenge, you'll solidify your understanding of how Rust tracks the validity of references.

Problem Description

Your task is to implement several functions that involve borrowing data and explicitly annotating their lifetimes. You will need to demonstrate your understanding of how lifetimes are inferred and how to provide them when the compiler cannot automatically determine them.

Key Requirements:

  • Implement three functions:
    • longest_string: Takes two string slices and returns a reference to the longer one.
    • first_occurrence: Takes a string slice and a character, and returns an Option<&str> representing the substring from the beginning of the string slice up to (but not including) the first occurrence of the character. If the character is not found, return None.
    • split_at_first_whitespace: Takes a string slice and returns a tuple of two string slices: the part before the first whitespace character and the part after. If no whitespace is found, the second slice should be empty.
  • All functions must correctly annotate their lifetimes.
  • Ensure that the returned references are valid for as long as the input references are valid.

Expected Behavior:

The functions should behave as described, returning valid references that do not outlive the data they point to.

Edge Cases:

  • Empty input strings.
  • Input strings containing only whitespace.
  • The target character in first_occurrence appearing at the beginning or end of the string.
  • The target character in first_occurrence not being present.
  • No whitespace in split_at_first_whitespace.

Examples

Example 1: longest_string

Input:
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest_string(string1.as_str(), string2);

Output:
"xyz"

Explanation:
`string1.as_str()` is "abcd" (length 4).
`string2` is "xyz" (length 3).
"abcd" is longer, so a reference to "abcd" should be returned.
Wait, actually "xyz" is shorter! Let's re-evaluate.
"abcd" has length 4.
"xyz" has length 3.
"abcd" is longer. Therefore, a reference to "abcd" should be returned.
Okay, my bad again. The problem states to return the *longer* one.
"abcd" is length 4. "xyz" is length 3. "abcd" is longer. So we return a reference to "abcd".

Let's try again with a clearer example:
let string1 = String::from("long string is long");
let string2 = "short";
let result = longest_string(string1.as_str(), string2);

Output:
"long string is long"

Explanation:
`string1.as_str()` is "long string is long" (length 20).
`string2` is "short" (length 5).
"long string is long" is longer, so a reference to it is returned.

Example 2: first_occurrence

Input:
let text = "hello world";
let target = 'o';
let result = first_occurrence(text, target);

Output:
Some("hello")

Explanation:
The first 'o' in "hello world" is at index 4. The substring from the beginning up to index 4 is "hello".

Input:
let text = "rust programming";
let target = 'z';
let result = first_occurrence(text, target);

Output:
None

Explanation:
The character 'z' does not appear in "rust programming".

Example 3: split_at_first_whitespace

Input:
let text = "  leading and trailing  ";
let result = split_at_first_whitespace(text);

Output:
("", " leading and trailing  ")

Explanation:
The first whitespace is at index 0. The part before it is an empty string. The part after it is the rest of the string.

Input:
let text = "no-whitespace-here";
let result = split_at_first_whitespace(text);

Output:
("no-whitespace-here", "")

Explanation:
No whitespace character is found. The first slice contains the entire input string, and the second slice is empty.

Input:
let text = "word1 word2";
let result = split_at_first_whitespace(text);

Output:
("word1", " word2")

Explanation:
The first whitespace is found after "word1". The part before is "word1", and the part after is " word2".

Constraints

  • Input strings will be valid UTF-8.
  • The maximum length of any input string slice is 10,000 characters.
  • The functions should not perform any heap allocations.
  • The compiler must be able to infer lifetimes for the inputs, but you are expected to explicitly annotate them where necessary for clarity and learning.

Notes

  • Recall that string slices (&str) in Rust are references.
  • The compiler often infers lifetimes, but for functions that return references derived from multiple input references, explicit lifetime annotations are usually required.
  • Consider the 'a lifetime annotation. If a function takes references with lifetime 'a and returns a reference, that returned reference must also have a lifetime of at least 'a.
  • Think about how the validity of a returned reference is tied to the validity of the input references.
Loading editor...
rust