String views and slices: C++ and Rust
std::string_view and &str solve the same problem: pass text without copying it. Both are a pointer and a length, both are cheap to copy, and both are dangerous in the same way: a view of a buffer that is destroyed is a dangling view. C++ leaves the check to the programmer, and Rust makes the lifetime part of the type.
The task in C++
first_word takes a view, finds the first space and returns a view of the part before it. It accepts a std::string and a literal without copying either, which is the reason the type exists.
The program prints the word, the sizes of the whole and the part, and then calls the function with a literal. The sizes confirm that the view is a window and not a copy.
#include <iostream>
#include <string>
#include <string_view>
std::string_view first_word(std::string_view text)
{
const std::size_t space = text.find(' ');
return text.substr(0, space);
}
int main()
{
std::string sentence = "hello world";
std::cout << first_word(sentence) << '\n';
std::cout << sentence.size() << ' ' << first_word(sentence).size() << '\n';
std::cout << first_word("borrowed literal") << '\n';
}
g++ -std=c++23 -Wall -Wextra views.cpp -o views
./views
hello
11 5
borrowed
Nothing is allocated and nothing is copied. The signature says nothing about how long the returned view is valid, and the compiler cannot check it at the call site.
The same task in Rust
The Rust function takes &str and returns &str. The elision rule gives the output the lifetime of the input, so the signature means that the result lives as long as the text it came from.
The program prints the same three lines. The match on the result of find is the only structural difference: the search may fail, and Rust makes the caller handle that.
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(index) => &text[..index],
None => text,
}
}
fn main() {
let sentence = String::from("hello world");
println!("{}", first_word(&sentence));
println!("{} {}", sentence.len(), first_word(&sentence).len());
println!("{}", first_word("borrowed literal"));
}
rustc --edition 2024 views.rs
./views
hello
11 5
borrowed
A caller that keeps the returned slice after the string is gone does not compile, because the borrow checker ties the two together. The lesson on lifetimes writes the annotation that makes that guarantee explicit.
What differs and why
Who owns the bytes: the original string in both languages, and the view borrows them. In C++ the borrow is a convention documented in a comment; in Rust it is a lifetime inferred from the signature, and every use is checked against it.
What the compiler rejects: C++ accepts a string_view returned from a local with no warning at all. Rust rejects the same function with E0515, because the returned reference would point at data owned by the function.
What happens at run time: the C++ program below builds the view, destroys the string and then reads the freed bytes. In this build it prints garbage; it may crash or print the old text, and none of those outcomes is a bug in the compiler.
The habit that breaks
The habit is to return a view of a temporary, which is the natural thing to write when a function builds a string and returns the interesting part. The C++ compiler accepts it, and the program below runs and prints whatever the freed buffer holds.
#include <iostream>
#include <string>
#include <string_view>
std::string_view make_view()
{
std::string local = "temporary";
return local;
}
int main()
{
std::string_view view = make_view();
std::cout << "view: " << view << '\n';
}
g++ -std=c++23 -Wall -Wextra view_dangling.cpp -o view_dangling
echo "status: $?"
status: 0
The build succeeds with no warning. Running it reads memory that no longer belongs to the string, and the output is not shown here because it is not a value a program can rely on.
The Rust version of the same function is rejected outright, and the message names the local whose data would escape.
fn make_view() -> &'static str {
let local = String::from("temporary");
&local
}
fn main() {
println!("view: {}", make_view());
}
rustc --edition 2024 view_dangling.rs
error[E0515]: cannot return reference to local variable `local`
--> view_dangling.rs:3:5
|
3 | &local
| ^^^^^^ returns a reference to data owned by the current function
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0515`.
The C++ lesson for this ground is std::string_view, and the Rust lesson is &str, UTF-8 and characters.
The C++ programs were built with g++ 15.2.0 and the Rust programs with rustc 1.96.1, and both were run on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev