Skip to content

← Ownership III: Lifetimes, Explicitly step 6 of 22

Easy Primitives

Use lifetime elision for borrowed string results

Implement four string helpers that return borrowed slices. Remove unnecessary lifetime annotations from the first three; give trim_prefix a signature that ties its result to s without tying it to prefix.

pub fn longest_line(s: &str) -> &str
pub fn head_tail(s: &str) -> (&str, &str)
pub fn nth_field(s: &str, n: usize) -> &str
pub fn trim_prefix<'a>(s: &'a str, prefix: &str) -> &'a str

Required behavior

Function Result
longest_line The longest line by UTF-8 byte length, with the first line winning ties. Return "" for empty input.
head_tail Split at the first ASCII space, excluding that one separator. If there is no space, return (s, "").
nth_field Split on commas and return zero-based field n; return "" if it is out of range. Preserve empty fields.
trim_prefix Remove one matching prefix from the start; otherwise return s unchanged. An empty prefix leaves s unchanged.

Use str::lines semantics for longest_line: LF and CRLF end a line and are excluded from its contents. A lone carriage return is ordinary content, and a final line terminator does not add an extra trailing line. Byte length is not character count: between "éé" and "abc", the former is longer (four bytes versus three).

Other whitespace is preserved. head_tail(" a b") returns ("", "a b"); tabs are not separators. nth_field("a,,b,", 1) returns "", as does index 3. trim_prefix("abab", "ab") returns "ab", removing only one prefix. Prefix matching is exact and case-sensitive, with no Unicode normalization.

What elision supplies

Lifetime elision fills in omitted lifetime relationships using fixed rules. For these free functions, a single input reference lifetime can supply all output reference lifetimes. head_tail has two outputs but one input lifetime; nth_field also has a usize, which adds no reference lifetime. All three therefore work without a named lifetime parameter.

trim_prefix has two independently borrowed strings. An unannotated &str result would be ambiguous and produces E0106. The implementation returns bytes from s, so connect only s to the output. Elision depends on the signature, not the compiler inspecting the body to infer where you intended the result to come from. See the reference's elision rules.

Keep the caller check

The starter includes this compile-time check:

const _: () = {
    fn _pin(s: &str) -> &str {
        let prefix = String::from("//");
        trim_prefix(s, &prefix)
    }
};

Keep it in your solution. It checks that a caller can use a short-lived prefix while returning a slice of its longer-lived input. If both parameters and the result share one lifetime, this call is rejected with E0515. That signature unnecessarily restricts the caller; it does not extend the local prefix's lifetime. A separate explicit lifetime for prefix can express the same independence, but leaving it elided is simpler here.

Clippy's needless_lifetimes flags removable annotations in the first three functions. Warnings are denied by the grader. The starter also has unfinished bodies, so implement them as well as fixing the signatures. The preserved caller check tests a lifetime relationship that ordinary output tests cannot establish on their own.

Use string splitting and prefix methods to keep slice boundaries valid for UTF-8. To keep the first longest line, update the current best only for a strictly greater byte length; max_by_key on the original order would choose the last tied maximum.