Returning References
Abstractions · Lesson 36
A function can return a reference only if it points into something that will still exist after the function returns. Usually that means into one of its inputs.
Coming from GoGo lets you return &s for a local s: the compiler sees that s escapes the function and allocates it on the heap. (A Go function would normally just return the string.) Rust has no garbage collector, so you choose: return an owned String, or return a &str that points into one of the inputs.
Coming from KotlinKotlin returns object references, and the garbage collector keeps whatever is still referenced alive. In Rust you choose: an owned String that the caller now owns, or a &str borrowed from an input, which works only while that input is still around.
Coming from PythonPython returns an object and keeps it alive for as long as anything refers to it. In Rust you return either an owned String, or a &str that borrows from an input and is valid only while that input lives.
Coming from TypeScriptIn TypeScript an object you return stays alive for as long as something refers to it. In Rust a returned &str must point into one of the inputs; anything made inside the function has to be returned as an owned String.
Coming from JavaJava returns object references, and the garbage collector keeps whatever is still referenced alive. In Rust a returned &str must point into one of the inputs; anything made inside the function has to be returned as an owned String: ownership moves to the caller, and the text is not copied.
Coming from C#A returned &str is closest to a ReadOnlySpan<char> that points into the input. C# already refuses to return a span of a local or a ref to a local (CS8352, CS8168); Rust applies that rule to every reference, not only to ref structs. Anything the function makes itself is returned as an owned String.
Coming from Cgcc warns when a function returns a pointer to a local array, but the program still builds, and using the pointer is undefined behaviour. In Rust it does not compile. Return an owned String, which frees itself, or a &str that points into an input.
Coming from C++A returned &str is closest to a std::string_view into the input. Returning a reference to a local compiles in C++ (g++ warns) and is undefined behaviour when used; in Rust it is an error. Return a String, which is what returning a std::string by value is, and the usual fix in C++ too.
The quick comparison
// returns a NEW String: the caller owns itfn make_greeting(name: &str) -> String { format!("hello {name}")}
// returns a piece of the input: no copy, borrowed from `text`fn first_word(text: &str) -> &str { text.split(' ').next().unwrap_or("")}
fn main() { let g = make_greeting("rust"); let w = first_word(&g); println!("{g} / {w}");}hello rust / hello
package main
import ( "fmt" "strings")
// a Go function would normally return the string itself; `&s` is the shape Rust rejectsfunc makeGreeting(name string) *string { s := "hello " + name return &s // fine in Go: s escapes, so the compiler allocates it on the heap}
// returns a piece of the input: no copyfunc firstWord(text string) string { word, _, _ := strings.Cut(text, " ") return word}
func main() { g := makeGreeting("rust") w := firstWord(*g) fmt.Println(*g, "/", w)}hello rust / hello
fun makeGreeting(name: String): String = "hello $name"
fun firstWord(text: String): String = text.substringBefore(' ')
fun main() { val g = makeGreeting("rust") val w = firstWord(g) println("$g / $w")}hello rust / hello
def make_greeting(name: str) -> str: return f"hello {name}"
def first_word(text: str) -> str: # returns a new string return text.split(" ")[0]
g = make_greeting("rust")w = first_word(g)print(f"{g} / {w}")hello rust / hello
// returns a new stringfunction makeGreeting(name: string): string { return `hello ${name}`;}
// returns part of the input (also a new string: JS strings are immutable)function firstWord(text: string): string { return text.split(" ")[0] ?? "";}
const g = makeGreeting("rust");const w = firstWord(g);console.log(`${g} / ${w}`);hello rust / hello
public class Main { static String makeGreeting(String name) { return "hello " + name; }
// returns a new String holding the first word static String firstWord(String text) { return text.substring(0, text.indexOf(' ')); }
public static void main(String[] args) { var g = makeGreeting("rust"); var w = firstWord(g); System.out.println(g + " / " + w); }}hello rust / hello
var g = MakeGreeting("rust");var w = FirstWord(g);Console.WriteLine($"{g} / {w}");
// returns a NEW stringstatic string MakeGreeting(string name) => $"hello {name}";
// returns a view into the input: no copystatic ReadOnlySpan<char> FirstWord(ReadOnlySpan<char> text){ var space = text.IndexOf(' '); return space < 0 ? text : text[..space];}hello rust / hello
#include <stdio.h>#include <stdlib.h>#include <string.h>
// returns NEW memory: the caller owns it and must free itchar *make_greeting(const char *name) { size_t n = strlen("hello ") + strlen(name) + 1; char *s = malloc(n); snprintf(s, n, "hello %s", name); return s;}
// returns a pointer into the input: the end of the first word, no copyconst char *first_word_end(const char *text) { const char *space = strchr(text, ' '); return space ? space : text + strlen(text);}
int main(void) { char *g = make_greeting("rust"); const char *end = first_word_end(g); printf("%s / %.*s\n", g, (int)(end - g), g); free(g); return 0;}hello rust / hello
#include <format>#include <iostream>#include <string>#include <string_view>
// returns a NEW std::string: the caller owns itstd::string make_greeting(std::string_view name) { return std::format("hello {}", name);}
// returns a view into the input: no copy, only valid while `text` livesstd::string_view first_word(std::string_view text) { return text.substr(0, text.find(' '));}
int main() { std::string g = make_greeting("rust"); std::string_view w = first_word(g); std::cout << g << " / " << w << "\n";}hello rust / hello
What's different
- Rule of thumb: if you made it inside the function, return it owned (
String,Vec). If it is a piece of an input, you can return a reference (&str,&[T]). first_wordreturns a&strthat points intog. Rust remembers that link, sowcan't outliveg.- This link is what Rust calls a lifetime. With one borrowed input, Rust fills it in by itself. With two, you must write it:
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str. The'asays "the result comes from one of these inputs, so use it only while both are alive".
Questions people ask
Do people really write lifetimes all over their code?
Not in every function. The compiler fills in the common cases (one reference in, a reference out; a method that returns a piece of &self), and a struct that owns its data (String, Vec, Arc<T>) needs no lifetime at all.
The two most common places for a lifetime you write by hand are a function that returns a reference and has more than one reference parameter, and a struct that holds a reference. The lifetimes lesson covers both, and more.
If your own code starts to feel like pointer gymnastics, returning an owned value is often a way out. Making an owned copy of borrowed data costs an allocation and a copy; that is usually fine until a measurement says otherwise.
Returning a reference to something made inside the function
Looks fine: in Go, Kotlin, Python, TypeScript, Java and C# it runs fine; in C and C++ it crashes at runtime. Rust won't compile it.
Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.Broken in C. Rust catches it.Broken in C++. Rust catches it.fn make_greeting(name: &str) -> &str { let s = format!("hello {name}"); &s}
fn main() { println!("{}", make_greeting("rust"));}package main
import "fmt"
func makeGreeting(name string) *string { s := "hello " + name return &s}
func main() { fmt.Println(*makeGreeting("rust"))}hello rust
fun makeGreeting(name: String): String { val s = "hello $name" return s}
fun main() { println(makeGreeting("rust"))}hello rust
def make_greeting(name: str) -> str: s = f"hello {name}" return s
print(make_greeting("rust"))hello rust
function makeGreeting(name: string): string { const s = `hello ${name}`; return s;}
console.log(makeGreeting("rust"));hello rust
public class Main { static String makeGreeting(String name) { String s = "hello " + name; return s; }
public static void main(String[] args) { System.out.println(makeGreeting("rust")); }}hello rust
Console.WriteLine(MakeGreeting("rust"));
static string MakeGreeting(string name){ var s = $"hello {name}"; return s;}hello rust
#include <stdio.h>
char *make_greeting(const char *name) { char s[32]; snprintf(s, sizeof s, "hello %s", name); return s;}
int main(void) { printf("%s\n", make_greeting("rust")); return 0;}main.c: In function ‘make_greeting’:
main.c:6:12: warning: function returns address of local variable [-Wreturn-local-addr]
6 | return s;
| ^AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x············ (pc 0x············ bp 0x············ sp 0x············ T0)
==1==The signal is caused by a READ memory access.
==1==Hint: address points to the zero page.
#0 0x············ in __sanitizer::internal_strlen(char const*) ../../../../src/libsanitizer/sanitizer_common/sanitizer_libc.cpp:176
#1 0x············ in puts ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1236
#2 0x············ in main main.c:10
#3 0x············ (/lib/aarch64-linux-gnu/libc.so.6+0x22258)
#4 0x············ in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338)
#5 0x············ in _start (main+0xaac)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../../../../src/libsanitizer/sanitizer_common/sanitizer_libc.cpp:176 in __sanitizer::internal_strlen(char const*)
==1==ABORTING#include <iostream>#include <string>
const std::string& make_greeting(const std::string& name) { std::string s = "hello " + name; return s;}
int main() { std::cout << make_greeting("rust") << "\n";}main.cpp: In function ‘const std::string& make_greeting(const std::string&)’:
main.cpp:6:12: warning: reference to local variable ‘s’ returned [-Wreturn-local-addr]
6 | return s;
| ^
main.cpp:5:17: note: declared here
5 | std::string s = "hello " + name;
| ^AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x············ (pc 0x············ bp 0x············ sp 0x············ T0)
==1==The signal is caused by a READ memory access.
==1==Hint: address points to the zero page.
#0 0x············ in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (/lib/aarch64-linux-gnu/libstdc++.so.6+0x······)
#1 0x············ in main main.cpp:10
#2 0x············ (/lib/aarch64-linux-gnu/libc.so.6+0x22258)
#3 0x············ in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338)
#4 0x············ in _start (main+0x232c)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib/aarch64-linux-gnu/libstdc++.so.6+0x······) in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
==1==ABORTINGWhat does the compiler say? Guess first, then open.
error[E0515]: cannot return reference to local variable `s` --> src/main.rs:3:5 | 3 | &s | ^^ returns a reference to data owned by the current function
Go runs this and prints hello rust. Go sees that s outlives the function and allocates it on the heap, so the pointer stays valid.
Kotlin prints hello rust. Objects stay alive while something refers to them, so returning one made inside a function just works.
Python prints hello rust. The function returns the string object itself, and Python keeps it alive for as long as anything refers to it.
Prints hello rust. The garbage collector keeps the string alive for as long as anything refers to it.
Java prints hello rust. Objects stay alive while something refers to them, so returning one made inside a method just works.
Strings live on the garbage-collected heap, so returning one made inside the method works and prints hello rust.
gcc warns that the function returns the address of a local array, then returns NULL in its place. Printing it crashes, and AddressSanitizer reports a read from the zero page (a null pointer). That substitution is gcc 14's; undefined behaviour could as well print garbage or appear to work.
g++ warns about returning a reference to a local, but builds it. It even swaps the reference for a null one, so AddressSanitizer reports a SEGV when cout reads it. Returning std::string by value, which is what -> String is, is the usual fix in C++ too.
s is owned by make_greeting, so it is freed at the closing }. A reference to it would point at memory that no longer holds the string.
Rust has no garbage collector to keep s alive, and it does not move values to the heap on its own. s is freed at the closing }, so a reference to it would really dangle. That is the bug the C and C++ versions below get from this same code, and Rust turns it into a compile error. Nothing is wrong with the habit in a garbage-collected language; here you say who owns the result: return the String itself, which moves ownership to the caller without copying the text, or return a &str that points into an input.
The fix
Compiles and runs.
fn make_greeting(name: &str) -> String { let s = format!("hello {name}"); s}
fn main() { println!("{}", make_greeting("rust"));}hello rust
The toolkit
The problem: a reference to something that is gone
A function that returns a reference hands its caller an address. The caller uses that address after the function has finished, so whatever sits there has to still exist. Values made inside a function are dropped at its closing } (the ownership lesson showed when). So this cannot work:
fn shout(name: &str) -> &str { let upper = name.to_uppercase(); &upper}
fn main() { println!("{}", shout("rust"));}error[E0515]: cannot return reference to local variable `upper` --> src/main.rs:3:5 | 3 | &upper | ^^^^^^ returns a reference to data owned by the current function
E0515 says the function “returns a reference to data owned by the current function”. upper is a String made inside shout. Its bytes are freed when shout returns, and &upper would point at freed memory. This is the rule from the borrowing lesson (E0597 there), applied across a function boundary.
A function with no references to borrow from at all gets a different error:
fn greeting() -> &str { "hello"}
fn main() { println!("{}", greeting());}error[E0106]: missing lifetime specifier --> src/main.rs:1:18 | 1 | fn greeting() -> &str { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` | 1 | fn greeting() -> &'static str { | +++++++ help: instead, you are more likely to want to return an owned value | 1 - fn greeting() -> &str { 1 + fn greeting() -> String {
E0106, “missing lifetime specifier”. A lifetime is how long a reference stays valid; the lifetimes lesson has the notation. In practice E0106 means: the compiler cannot find which value the returned reference points into. Here the help: says there is “no value for it to be borrowed from”, and offers two ways out. Both come up below.
So a returned reference can point into one of the function’s arguments, or at something that lasts for the whole run. A result that is neither is returned as an owned value. There are three ways to get a result out.
Fix 1: return an owned value
If the function makes something new, return it. The caller then owns it, and it is freed when the caller’s variable goes out of scope.
fn shout(name: &str) -> (String, usize) { let upper = name.to_uppercase(); let addr_inside = upper.as_ptr() as usize; (upper, addr_inside) // `upper` moves out to the caller}
fn main() { let (s, addr_inside) = shout("rust"); println!("s: {s}"); println!( "same buffer inside and outside: {}", s.as_ptr() as usize == addr_inside ); println!("len {}, capacity {}", s.len(), s.capacity());}s: RUST same buffer inside and outside: true len 4, capacity 4
upper moved its three fields (pointer, length, capacity) to the caller. The four bytes did not move: the buffer's address inside and outside the function is the same.Returning the String did not copy the text. The cost was the allocation to_uppercase made: the function allocated, whether or not the caller needed its own copy. For text a function builds, such as format! output or a converted string, it is the natural choice.
Fix 2: return a reference into an input
If the result is a piece of something the caller passed in, return a reference to that piece. Nothing is allocated or copied.
fn first_word(text: &str) -> &str { text.split(' ').next().unwrap_or("")}
fn main() { let text = String::from("hello world"); let w = first_word(&text); let start = text.as_ptr() as usize; println!("w: {w:?}"); println!("w starts {} bytes into text", w.as_ptr() as usize - start); println!( "w.len(): {}, text.len(): {}, text.capacity(): {}", w.len(), text.len(), text.capacity() ); let again = first_word(&text[6..]); println!( "again: {again:?}, starts {} bytes into text", again.as_ptr() as usize - start );}w: "hello" w starts 0 bytes into text w.len(): 5, text.len(): 11, text.capacity(): 11 again: "world", starts 6 bytes into text
text owns.The signature fn first_word(text: &str) -> &str does not say what the result borrows from, and it does not need to. With exactly one lifetime in the parameters (a &str or a &[String] has one; a &[&str] has two), the compiler fills in the obvious: the result borrows from it. This is called lifetime elision; the lifetimes lesson gives the rules.
The price is that the result keeps the input borrowed for as long as the result is used. That is what stops it dangling:
fn first_word(text: &str) -> &str { text.split(' ').next().unwrap_or("")}
fn main() { let mut text = String::from("hello world"); let w = first_word(&text); text.clear(); println!("w: {w}");}error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutable --> src/main.rs:8:5 | 7 | let w = first_word(&text); | ----- immutable borrow occurs here 8 | text.clear(); | ^^^^^^^^^^^^ mutable borrow occurs here 9 | println!("w: {w}"); | - immutable borrow later used here
E0502: text.clear() needs to change text while w still points into it. The fixes are the ones from the mutable borrows lesson: finish with w first, or copy it into a String of its own (w.to_owned()) before changing text. Copying at the point where you need it to outlive the input is Fix 1 again, done by the caller.
Fix 3: let the caller supply the buffer
A function that runs in a loop may not want to allocate a new String on every call. Instead it can take &mut String and append to it. The caller owns the buffer and can reuse it:
fn write_greeting(name: &str, out: &mut String) { out.push_str("hello "); out.push_str(name);}
fn main() { let mut out = String::with_capacity(64); let start = out.as_ptr() as usize; for name in ["ana", "bo", "cy"] { out.clear(); write_greeting(name, &mut out); println!("{out}"); } println!("same buffer: {}", out.as_ptr() as usize == start); println!("capacity: {}", out.capacity());}hello ana hello bo hello cy same buffer: true capacity: 64
The one String::with_capacity(64) served all three calls: in this run the buffer’s address was the same afterwards and the capacity was still 64 (clear empties a String but keeps its capacity). The standard library uses this shape, for example in Read::read_to_string, which appends to a String the caller provides. The cost is a clumsier signature: the caller must create the buffer. Reach for it when you have a loop and a reason to avoid allocating, not by default.
Choosing. Made inside the function: Fix 1. A piece of an input: Fix 2. Appending to something the caller already has, in a loop: Fix 3. When unsure, start with the owned value, which is the easiest for callers, and change to a reference when you see a reason, such as a big piece of an input you would otherwise copy.
A lookup that may find nothing
A function returning a piece of a collection often returns Option<&T>, the same shape as get on a Vec or a map (Option). With one borrowed input, elision works as before:
fn longest(words: &[String]) -> Option<&str> { let mut best: Option<&str> = None; for w in words { match best { Some(b) if b.len() >= w.len() => {} _ => best = Some(w), } } best}
fn main() { let words = vec![ String::from("fig"), String::from("banana"), String::from("kiwi"), ]; println!("longest: {:?}", longest(&words)); println!("empty: {:?}", longest(&[])); let owned: Option<String> = longest(&words).map(str::to_owned); println!("owned copy: {owned:?}");}longest: Some("banana")
empty: None
owned copy: Some("banana")longest borrows from words, so the &str it returns is only valid while words is. To keep the answer longer, .map(str::to_owned) turned it into an owned Option<String>. A function can also return an iterator that borrows its input (iterators).
String literals and static items
A string literal’s text is stored in the program itself, so it stays valid for as long as the program runs. A function with no inputs may return one, and the type is written with 'static:
fn language() -> &'static str { "rust"}
fn main() { let name = language(); println!("language: {name}");}language: rust
'static is a lifetime name meaning “valid for the whole run”. A literal is the common case, but not the only one: a static item and a leaked allocation (Box::leak, which gives a value away for the rest of the run) also compile:
static NAMES: [&str; 2] = ["ana", "bo"];
fn first_name() -> &'static str { NAMES[0]}
fn leaked() -> &'static str { // gives the String away for the rest of the run; it is never freed Box::leak(String::from("never freed").into_boxed_str())}
fn main() { println!("first_name: {}", first_name()); println!("leaked: {}", leaked());}first_name: ana leaked: never freed
The help: for E0106 above suggests 'static too, with the warning that it is uncommon unless you are “returning a borrowed value from a const or a static”. Its other suggestion is an owned String.
Two inputs: the compiler asks which one
Elision is a fixed rule, so you can predict it:
| Signature | The result borrows from | Compiles? |
|---|---|---|
fn f(a: &str) -> &str |
a |
yes |
fn f(words: &[String]) -> &str |
words |
yes |
fn f(a: &str, n: usize) -> &str |
a, the only borrowed input |
yes |
fn f(&self, sep: &str) -> &str (in an impl) |
self |
yes |
fn f(a: &str, b: &str) -> &str |
unclear | E0106 |
fn f(words: &[&str]) -> &str |
unclear: one parameter, two lifetimes | E0106 |
fn f() -> &str |
unclear | E0106 |
Two reference parameters leave the compiler unable to choose:
fn longer(a: &str, b: &str) -> &str { if a.len() >= b.len() { a } else { b }}
fn main() { println!("{}", longer("ab", "abc"));}error[E0106]: missing lifetime specifier --> src/main.rs:1:32 | 1 | fn longer(a: &str, b: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b` help: consider introducing a named lifetime parameter | 1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { | ++++ ++ ++ ++
“The signature does not say whether it is borrowed from a or b”. We know the result is one of them, but a caller needs to know how long it stays valid. The help shows the fix, which names a lifetime 'a and puts it on both inputs and the output:
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { println!("longer: {}", longer("ab", "abc"));}longer: abc
Read 'a here as “the result comes from one of the inputs, so use it only while both are alive”. What 'a means and what it can and cannot do is the job of the lifetimes lesson. The alternatives are to return an owned String (Fix 1) or to restructure so there is one source.
In the method row the result is tied to self, even when other references are passed in. Methods are a very common source of borrowed results (fn name(&self) -> &str), and this rule keeps most of those signatures free of lifetime marks.
Going deeper
The signature is the contract
Does the compiler need the annotation when the body is obvious? This function returns a every time:
fn first_of(a: &str, b: &str) -> &str { println!("ignoring {b:?}"); a}
fn main() { println!("{}", first_of("ab", "abc"));}error[E0106]: missing lifetime specifier --> src/main.rs:1:34 | 1 | fn first_of(a: &str, b: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b` help: consider introducing a named lifetime parameter | 1 | fn first_of<'a>(a: &'a str, b: &'a str) -> &'a str { | ++++ ++ ++ ++
It is still E0106. Each function is checked against the signatures of the others, not their bodies (the borrowing lesson says why). A caller must know what the result is tied to without reading the body, and the body can change later while the signature, and every call, keeps compiling. Elision is the compiler writing the signature for you where there is only one reasonable answer. Where there are two, you decide.
Returning a &mut
A function can return a mutable reference into its input. While the result is alive, nothing else may touch the input.
fn largest(scores: &mut [i32]) -> &mut i32 { let mut best = 0; for i in 1..scores.len() { if scores[i] > scores[best] { best = i; } } &mut scores[best]}
fn main() { let mut scores = vec![3, 9, 4]; *largest(&mut scores) += 100; println!("scores: {scores:?}");}scores: [3, 109, 4]
fn largest(scores: &mut [i32]) -> &mut i32 { let mut best = 0; for i in 1..scores.len() { if scores[i] > scores[best] { best = i; } } &mut scores[best]}
fn main() { let mut scores = vec![3, 9, 4]; let top = largest(&mut scores); println!("length: {}", scores.len()); *top += 1;}error[E0502]: cannot borrow `scores` as immutable because it is also borrowed as mutable --> src/main.rs:14:28 | 13 | let top = largest(&mut scores); | ----------- mutable borrow occurs here 14 | println!("length: {}", scores.len()); | ^^^^^^ immutable borrow occurs here 15 | *top += 1; | --------- mutable borrow later used here
The returned &mut keeps scores borrowed, even for reading, until its last use. That is the mutable borrows rule. last_mut on a Vec and HashMap::get_mut have this shape.
Compared with where you come from
Practice
Try it
Each exercise is a small program with checks in main. Open it in the Playground, replace the todo!(), and run it until it prints all checks passed. The signature is part of the first two exercises, and the checks look at what you return: a leaked or copied string will not pass.
Build a banner
Return the text == NAME == with the name in upper case. The text is made inside the function, so the starter’s -> &str cannot work: change the return type (Fix 1) and read the error you get first.
/// Build the text "== NAME ==" with `name` in upper case (all Unicode letters, not just ASCII)./// The text is made inside the function, so the caller has to own it: fix the return type.fn banner(name: &str) -> &str { todo!("build the text with format!, then change -> &str so the caller owns it")}
/// True when `T` is `String` (a `&str`, even a leaked one, is not).fn is_string<T>(_: &T) -> bool { std::any::type_name::<T>() == "alloc::string::String"}
fn main() { let b = banner("rust"); assert_eq!(b, "== RUST =="); assert!(is_string(&b), "return an owned String");
// Not English: to_ascii_uppercase would leave the é alone. assert_eq!(banner("café"), "== CAFÉ =="); assert_eq!(banner(""), "== ==");
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// Build the text "== NAME ==" with `name` in upper case (all Unicode letters, not just ASCII)./// The text is made inside the function, so the caller has to own it: fix the return type.fn banner(name: &str) -> String { format!("== {} ==", name.to_uppercase())}
/// True when `T` is `String` (a `&str`, even a leaked one, is not).fn is_string<T>(_: &T) -> bool { std::any::type_name::<T>() == "alloc::string::String"}
fn main() { let b = banner("rust"); assert_eq!(b, "== RUST =="); assert!(is_string(&b), "return an owned String");
// Not English: to_ascii_uppercase would leave the é alone. assert_eq!(banner("café"), "== CAFÉ =="); assert_eq!(banner(""), "== ==");
println!("all checks passed");}all checks passed
Text after a marker
Return the part of text after the first marker, as a slice of text. The starter returns a String, which copies. Change the signature so the result borrows from text, and only from text: the check drops marker before it uses the result.
/// Return the part of `text` after the first `marker`, or "" when `marker` is not in `text`./// The result must be a slice of `text`, not a copy. The signature is part of the exercise:/// say which input the result is borrowed from, so the caller can drop `marker` early.fn after(text: &str, marker: &str) -> String { todo!("find the marker, slice text after it; change -> String so the result borrows from text")}
/// True when `inner` is a slice of `outer`: it points inside the same bytes.fn is_slice_of(outer: &str, inner: &str) -> bool { let start = outer.as_ptr() as usize; let end = start + outer.len(); let at = inner.as_ptr() as usize; at >= start && at + inner.len() <= end}
fn main() { let text = String::from("key: value: more"); let rest; { let marker = String::from(": "); rest = after(&text, &marker); } // `marker` is dropped here; `rest` is still used below assert_eq!(rest, "value: more"); assert!( is_slice_of(&text, &rest), "return a slice of text, not a copy" );
// The marker is not there. assert_eq!(after("no colon", ": "), "");
// Not English: the marker is three bytes long. assert_eq!(after("é→b", "→"), "b");
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// Return the part of `text` after the first `marker`, or "" when `marker` is not in `text`./// The result must be a slice of `text`, not a copy. The signature is part of the exercise:/// say which input the result is borrowed from, so the caller can drop `marker` early.fn after<'a>(text: &'a str, marker: &str) -> &'a str { match text.find(marker) { Some(i) => &text[i + marker.len()..], None => "", }}
/// True when `inner` is a slice of `outer`: it points inside the same bytes.fn is_slice_of(outer: &str, inner: &str) -> bool { let start = outer.as_ptr() as usize; let end = start + outer.len(); let at = inner.as_ptr() as usize; at >= start && at + inner.len() <= end}
fn main() { let text = String::from("key: value: more"); let rest; { let marker = String::from(": "); rest = after(&text, &marker); } // `marker` is dropped here; `rest` is still used below assert_eq!(rest, "value: more"); assert!( is_slice_of(&text, &rest), "return a slice of text, not a copy" );
// The marker is not there. assert_eq!(after("no colon", ": "), "");
// Not English: the marker is three bytes long. assert_eq!(after("é→b", "→"), "b");
println!("all checks passed");}all checks passed
Join into a buffer
Write the parts into a String the caller supplies, with a separator between them (Fix 3). What the buffer already holds must stay, and the check makes sure you wrote into the caller’s String rather than replacing it.
/// Append `parts` to `out`, with `sep` between them (not before the first or after the last)./// What `out` already holds stays.fn join_into(parts: &[&str], sep: &str, out: &mut String) { todo!("loop over the parts; push_str the separator before every part but the first")}
fn main() { let mut out = String::with_capacity(32); out.push('>'); let buffer = out.as_ptr();
join_into(&["a", "b", "c"], ", ", &mut out); assert_eq!(out, ">a, b, c"); assert_eq!( out.as_ptr(), buffer, "write into the caller's String, don't replace it" ); assert_eq!(out.capacity(), 32);
// One part: no separator. No parts: nothing added. out.clear(); join_into(&["solo"], ", ", &mut out); assert_eq!(out, "solo"); join_into(&[], ", ", &mut out); assert_eq!(out, "solo");
// Not English, and a separator that is more than one byte. out.clear(); join_into(&["é", "ü"], "·", &mut out); assert_eq!(out, "é·ü"); assert_eq!(out.as_ptr(), buffer);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// Append `parts` to `out`, with `sep` between them (not before the first or after the last)./// What `out` already holds stays.fn join_into(parts: &[&str], sep: &str, out: &mut String) { for (i, part) in parts.iter().enumerate() { if i > 0 { out.push_str(sep); } out.push_str(part); }}
fn main() { let mut out = String::with_capacity(32); out.push('>'); let buffer = out.as_ptr();
join_into(&["a", "b", "c"], ", ", &mut out); assert_eq!(out, ">a, b, c"); assert_eq!( out.as_ptr(), buffer, "write into the caller's String, don't replace it" ); assert_eq!(out.capacity(), 32);
// One part: no separator. No parts: nothing added. out.clear(); join_into(&["solo"], ", ", &mut out); assert_eq!(out, "solo"); join_into(&[], ", ", &mut out); assert_eq!(out, "solo");
// Not English, and a separator that is more than one byte. out.clear(); join_into(&["é", "ü"], "·", &mut out); assert_eq!(out, "é·ü"); assert_eq!(out.as_ptr(), buffer);
println!("all checks passed");}all checks passed
Read more
- The Rust Book, chapter 4.2: References and Borrowing, the dangling reference section.
- The Rust Book, chapter 10.3: Validating References with Lifetimes, including lifetime elision.
- Rust by Example: Lifetimes in functions and elision.
- The Rust Reference: Lifetime elision, the exact rules.
Read::read_to_string, which appends to a buffer you provide, andstr::split_once, which returns two pieces of the input.- Error index: E0106, E0515, E0716, and this site’s errors page.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.