Rust smart pointers
last modified November 13, 2026
In this article we show how to work with smart pointers in Rust.
A smart pointer is a type that acts like a pointer but has
extra capabilities and metadata. Plain references &T only
borrow a value. Smart pointers usually own the data they point to, and they
do something extra: free heap memory, count the owners, or check borrowing
rules at run time. String and Vec<T> are
smart pointers too.
Smart pointers are usually structs that implement two traits:
Deref, so that they can be used like references, and
Drop, which runs code when the pointer goes out of scope. The
standard library provides these:
Box<T>— a single owner of a value on the heapRc<T>— shared ownership with reference counting, for one threadArc<T>— shared ownership with atomic reference counting, for several threadsRefCell<T>andCell<T>— mutation through a shared referenceWeak<T>— a non-owning reference to anRcorArcvalue
The examples were tested with Rust 1.97 and the 2024 edition. They are run as
Cargo projects with cargo run -q.
Box
Box<T> is the simplest smart pointer. It places a value
on the heap and keeps a pointer to it on the stack. When the box goes out of
scope, the value is dropped and the memory is freed.
use std::mem::size_of;
fn main() {
let n = Box::new(42);
let m = *n + 1;
println!("n = {n}, m = {m}");
let data = Box::new([0u8; 1024]);
println!("array size: {} bytes", size_of::<[u8; 1024]>());
println!("box size: {} bytes", size_of::<Box<[u8; 1024]>>());
println!("first byte: {}, length: {}", data[0], data.len());
}
We put an integer and an array into boxes.
let n = Box::new(42); let m = *n + 1;
The Box::new function moves the value to the heap. The
dereference operator * gives access to the boxed value.
Formatting macros dereference automatically, so {n} prints
42.
let data = Box::new([0u8; 1024]);
println!("array size: {} bytes", size_of::<[u8; 1024]>());
println!("box size: {} bytes", size_of::<Box<[u8; 1024]>>());
The box itself is the size of one pointer, no matter how large the boxed value is. Moving a box copies only the pointer, not the data. Methods and indexing work on the box as if it were the array.
$ cargo run -q n = 42, m = 43 array size: 1024 bytes box size: 8 bytes first byte: 0, length: 1024
A recursive type without Box
A type whose size the compiler cannot compute cannot be stored on the stack. A recursive type that contains itself directly is such a case.
enum List {
Cons(i32, List),
Nil,
}
fn main() {
let _list = List::Cons(1, List::Nil);
}
The Cons variant contains a whole List, which
contains another List, and so on. The size of the type would be
infinite. The compiler reports error E0072 and suggests adding indirection.
$ cargo run -q
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^
2 | Cons(i32, List),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Cons(i32, Box<List>),
| ++++ +
For more information about this error, try `rustc --explain E0072`.
Recursive types with Box
A box has a known size, so it can break the recursion.
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
impl List {
fn sum(&self) -> i32 {
match self {
Cons(value, rest) => value + rest.sum(),
Nil => 0,
}
}
}
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{list:?}");
println!("sum: {}", list.sum());
}
This is the cons list from functional languages. Each element
holds a value and a box with the rest of the list. The Nil
variant ends the list.
Cons(value, rest) => value + rest.sum(),
The sum method calls itself on the rest of the list. The
rest binding is a &Box<List>; the method
call goes through the box to the list automatically. Trees, linked lists,
and expression trees are built with Box in the same way.
$ cargo run -q Cons(1, Cons(2, Cons(3, Nil))) sum: 6
Trait objects
Values of different types that implement the same trait can be stored in
one collection as Box<dyn Trait>.
trait Shape {
fn area(&self) -> f64;
fn name(&self) -> &str;
}
struct Circle(f64);
struct Rect(f64, f64);
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.0 * self.0
}
fn name(&self) -> &str {
"circle"
}
}
impl Shape for Rect {
fn area(&self) -> f64 {
self.0 * self.1
}
fn name(&self) -> &str {
"rectangle"
}
}
fn make_shape(spec: &str) -> Option<Box<dyn Shape>> {
let nums: Vec<f64> = spec.split(',').filter_map(|s| s.trim().parse().ok()).collect();
match nums.as_slice() {
[r] => Some(Box::new(Circle(*r))),
[w, h] => Some(Box::new(Rect(*w, *h))),
_ => None,
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = ["2", "3, 4", "1.5", "x"]
.iter()
.filter_map(|spec| make_shape(spec))
.collect();
for shape in &shapes {
println!("{:<9} {:>7.3}", shape.name(), shape.area());
}
let total: f64 = shapes.iter().map(|s| s.area()).sum();
println!("total: {total:.3}");
}
A Circle and a Rect have different sizes, so they
cannot be stored directly in one vector. Boxed, each one is a pointer to the
data and a pointer to the method table of its type.
fn make_shape(spec: &str) -> Option<Box<dyn Shape>> {
The function decides at run time which type to create. Returning
impl Shape would not work here, because that requires a single
concrete type for all return paths. Invalid specifications such as
"x" give None and are skipped by
filter_map. Trait objects are covered in more detail in
Rust traits.
$ cargo run -q circle 12.566 rectangle 12.000 circle 7.069 total: 31.635
The Deref trait
The Deref trait defines what the * operator does
for a type. It also enables deref coercion: a reference to the
smart pointer is converted to a reference to the inner value where needed.
use std::ops::{Deref, DerefMut};
struct Wrapper<T> {
value: T,
reads: std::cell::Cell<u32>,
}
impl<T> Wrapper<T> {
fn new(value: T) -> Self {
Wrapper {
value,
reads: std::cell::Cell::new(0),
}
}
}
impl<T> Deref for Wrapper<T> {
type Target = T;
fn deref(&self) -> &T {
self.reads.set(self.reads.get() + 1);
&self.value
}
}
impl<T> DerefMut for Wrapper<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.value
}
}
fn greet(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let name = Wrapper::new(String::from("Rust"));
greet(&name);
println!("length: {}", name.len());
println!("upper: {}", name.to_uppercase());
let mut nums = Wrapper::new(vec![3, 1, 2]);
nums.push(0);
nums.sort();
println!("{:?}", *nums);
println!("reads of name: {}", name.reads.get());
}
The Wrapper type is a simple smart pointer that counts how
many times its value was accessed through Deref.
impl<T> Deref for Wrapper<T> {
type Target = T;
fn deref(&self) -> &T {
self.reads.set(self.reads.get() + 1);
&self.value
}
}
The deref method returns a reference to the inner value. The
counter is a Cell, which can be changed through
&self; we look at it later in the article.
greet(&name);
println!("length: {}", name.len());
The greet function expects a &str. The compiler
converts &Wrapper<String> to &String
with our deref, and then to &str with the
Deref implementation of String. Method calls like
len are looked up on the inner value in the same way. Each of
the three uses called our deref once.
nums.push(0); nums.sort();
The DerefMut trait does the same for mutable access, so we can
call the mutating methods of the vector.
$ cargo run -q Hello, Rust! length: 4 upper: RUST [0, 1, 2, 3] reads of name: 3
The Drop trait
The Drop trait runs code when a value goes out of scope. Smart
pointers use it to free memory or decrease reference counts; other types
close files, release locks, or end network connections.
struct Resource {
name: String,
}
impl Resource {
fn new(name: &str) -> Self {
println!("acquire {name}");
Resource {
name: name.to_string(),
}
}
}
impl Drop for Resource {
fn drop(&mut self) {
println!("release {}", self.name);
}
}
fn main() {
let _a = Resource::new("a");
let b = Resource::new("b");
{
let _c = Resource::new("c");
println!("leaving inner scope");
}
drop(b);
let _boxed = Box::new(Resource::new("d"));
println!("end of main");
}
The Resource type prints a message when it is created and when
it is dropped.
Values are dropped at the end of their scope in the reverse order of
creation: c at the end of the inner block, then
d and a at the end of main. The
drop method cannot be called directly; to drop a value early,
we pass it to the std::mem::drop function, which is in the
prelude. The boxed resource is dropped together with its box.
$ cargo run -q acquire a acquire b acquire c leaving inner scope release c release b acquire d end of main release d release a
Rc
Sometimes a value has several owners, and none of them is clearly the last
one to use it. Rc<T>, the reference-counted
pointer, keeps the value alive as long as at least one owner
exists.
use std::rc::Rc;
#[derive(Debug)]
struct Config {
name: String,
retries: u32,
}
struct Client {
id: u32,
config: Rc<Config>,
}
fn main() {
let config = Rc::new(Config {
name: "production".to_string(),
retries: 3,
});
println!("count after create: {}", Rc::strong_count(&config));
let a = Client { id: 1, config: Rc::clone(&config) };
let b = Client { id: 2, config: Rc::clone(&config) };
println!("count with two clients: {}", Rc::strong_count(&config));
for client in [&a, &b] {
println!("client {} uses {:?}", client.id, client.config);
}
println!("same allocation: {}", Rc::ptr_eq(&a.config, &b.config));
drop(a);
println!("count after drop: {}", Rc::strong_count(&config));
println!("{} retries: {}", b.config.name, b.config.retries);
}
Two clients share one configuration.
let a = Client { id: 1, config: Rc::clone(&config) };
The Rc::clone function does not copy the configuration. It
creates another pointer to the same allocation and increments the
reference count. Writing Rc::clone(&config) rather than
config.clone() makes it clear that this is a cheap pointer
copy, not a deep clone.
println!("same allocation: {}", Rc::ptr_eq(&a.config, &b.config));
The Rc::ptr_eq function checks whether two pointers point to
the same value. When a client is dropped, the count goes down; the
configuration is freed when the count reaches zero.
$ cargo run -q
count after create: 1
count with two clients: 3
client 1 uses Config { name: "production", retries: 3 }
client 2 uses Config { name: "production", retries: 3 }
same allocation: true
count after drop: 2
production retries: 3
Rc gives only shared access
The value inside an Rc is shared, and shared values are
immutable in Rust.
use std::rc::Rc;
fn main() {
let shared = Rc::new(vec![1, 2, 3]);
let other = Rc::clone(&shared);
other.push(4);
println!("{shared:?}");
}
We try to push an element to a vector through one of its owners. If this
were allowed, the other owners could hold references to the vector's
elements while it reallocates. Rc implements
Deref but not DerefMut, so the compiler stops us
with error E0596.
$ cargo run -q error[E0596]: cannot borrow data in an `Rc` as mutable --> src/main.rs:7:5 | 7 | other.push(4); | ^^^^^ cannot borrow as mutable | = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `std::rc::Rc<std::vec::Vec<i32>>` For more information about this error, try `rustc --explain E0596`.
RefCell
The borrowing rules allow either one mutable reference or any number of
shared references. Normally the compiler checks the rules. With
RefCell<T>, the checks happen at run time instead, which
allows changing the value through a shared reference. This is called
interior mutability.
use std::cell::RefCell;
struct Logger {
messages: RefCell<Vec<String>>,
}
impl Logger {
fn log(&self, msg: &str) {
self.messages.borrow_mut().push(msg.to_string());
}
fn count(&self) -> usize {
self.messages.borrow().len()
}
}
fn process(items: &[i32], logger: &Logger) -> i32 {
let mut sum = 0;
for &item in items {
if item < 0 {
logger.log(&format!("skipped negative value {item}"));
} else {
sum += item;
}
}
sum
}
fn main() {
let logger = Logger {
messages: RefCell::new(Vec::new()),
};
let sum = process(&[4, -1, 7, -3, 2], &logger);
println!("sum: {sum}");
println!("{} messages:", logger.count());
for msg in logger.messages.borrow().iter() {
println!(" {msg}");
}
}
The process function takes the logger by a shared reference.
The logger can still record messages.
fn log(&self, msg: &str) {
self.messages.borrow_mut().push(msg.to_string());
}
The borrow_mut method returns a guard of type
RefMut, which acts as a mutable reference and releases the
borrow when it is dropped, here at the end of the statement. The
borrow method returns a shared Ref guard. The
method signatures stay &self, so callers do not need to
know about the mutation.
$ cargo run -q sum: 13 2 messages: skipped negative value -1 skipped negative value -3
Borrow errors at run time
Since RefCell checks the rules at run time, breaking them is
not a compile error but a panic.
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("sky"));
let reader = cell.borrow();
println!("reading: {reader}");
match cell.try_borrow_mut() {
Ok(_) => println!("got a mutable borrow"),
Err(e) => println!("try_borrow_mut failed: {e}"),
}
let mut writer = cell.borrow_mut();
writer.push_str(" blue");
println!("{reader} {writer}");
}
We hold a shared borrow in reader while asking for a mutable
one.
match cell.try_borrow_mut() {
Ok(_) => println!("got a mutable borrow"),
Err(e) => println!("try_borrow_mut failed: {e}"),
}
The try_borrow_mut method returns an error instead of
panicking. The following borrow_mut call panics, and the
program ends with exit code 101. The number after the thread name is the
thread ID, which changes from run to run. The fix is to keep the guards short-lived,
for example by dropping reader before writing.
$ cargo run -q reading: sky try_borrow_mut failed: RefCell already borrowed thread 'main' (119137) panicked at src/main.rs:14:27: RefCell already borrowed note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace $ echo $? 101
Rc and RefCell together
Rc gives several owners; RefCell gives
mutability. Combined as Rc<RefCell<T>>, they give
several owners that can all modify the value.
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
struct Account {
owner: String,
balance: i64,
}
fn main() {
let shared = Rc::new(RefCell::new(Account {
owner: "Paul".to_string(),
balance: 100,
}));
let card = Rc::clone(&shared);
let phone = Rc::clone(&shared);
card.borrow_mut().balance -= 30;
phone.borrow_mut().balance += 55;
{
let mut acc = shared.borrow_mut();
acc.balance *= 2;
acc.owner.push_str(" Novak");
}
println!("{:?}", shared.borrow());
println!("handles: {}", Rc::strong_count(&shared));
}
Three handles share one bank account.
card.borrow_mut().balance -= 30; phone.borrow_mut().balance += 55;
Each handle borrows the account mutably for one statement. The method call
goes through the Rc to the RefCell.
{
let mut acc = shared.borrow_mut();
acc.balance *= 2;
acc.owner.push_str(" Novak");
}
For several changes, we keep the guard in a variable. The block ends the
borrow before the account is printed; without it, the
borrow call in println! would panic. All three
handles see the same balance: (100 - 30 + 55) * 2.
$ cargo run -q
Account { owner: "Paul Novak", balance: 250 }
handles: 3
Reference cycles
Reference counting cannot free values that point to each other. Such a reference cycle is a memory leak; Rust's safety guarantees do not rule out leaks.
use std::cell::RefCell;
use std::rc::Rc;
struct Node {
name: String,
next: RefCell<Option<Rc<Node>>>,
}
impl Drop for Node {
fn drop(&mut self) {
println!("dropping {}", self.name);
}
}
fn node(name: &str) -> Rc<Node> {
Rc::new(Node {
name: name.to_string(),
next: RefCell::new(None),
})
}
fn main() {
{
let a = node("a");
let b = node("b");
*a.next.borrow_mut() = Some(Rc::clone(&b));
println!("chain: a -> b, counts {} {}", Rc::strong_count(&a), Rc::strong_count(&b));
}
{
let x = node("x");
let y = node("y");
*x.next.borrow_mut() = Some(Rc::clone(&y));
*y.next.borrow_mut() = Some(Rc::clone(&x));
println!("cycle: x -> y -> x, counts {} {}", Rc::strong_count(&x), Rc::strong_count(&y));
}
println!("end of main");
}
Each node can point to a next node. The Drop implementation
shows which nodes are freed.
In the first block, a points to b. At the end of
the block, a's count drops to zero, a is freed
and releases its pointer to b, so b is freed
too.
*x.next.borrow_mut() = Some(Rc::clone(&y)); *y.next.borrow_mut() = Some(Rc::clone(&x));
In the second block, the nodes point to each other and each count is two. At the end of the block, the local variables go away and both counts drop to one. They never reach zero, so neither node is dropped, not even when the program ends.
$ cargo run -q chain: a -> b, counts 1 2 dropping a dropping b cycle: x -> y -> x, counts 2 2 end of main
Weak
A Weak<T> pointer refers to a value in an
Rc without owning it. It does not keep the value alive, so it
breaks cycles. The usual pattern is a tree: parents own their children with
Rc, and children point to their parents with
Weak.
use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Node {
name: String,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
impl Node {
fn new(name: &str) -> Rc<Node> {
Rc::new(Node {
name: name.to_string(),
parent: RefCell::new(Weak::new()),
children: RefCell::new(Vec::new()),
})
}
fn add_child(parent: &Rc<Node>, child: Rc<Node>) {
*child.parent.borrow_mut() = Rc::downgrade(parent);
parent.children.borrow_mut().push(child);
}
fn path(&self) -> String {
match self.parent.borrow().upgrade() {
Some(parent) => format!("{}/{}", parent.path(), self.name),
None => self.name.clone(),
}
}
}
impl Drop for Node {
fn drop(&mut self) {
println!("dropping {}", self.name);
}
}
fn main() {
let file = Node::new("notes.txt");
{
let root = Node::new("home");
let user = Node::new("jan");
Node::add_child(&root, Rc::clone(&user));
Node::add_child(&user, Rc::clone(&file));
println!("path: {}", file.path());
println!("user: strong = {}, weak = {}", Rc::strong_count(&user), Rc::weak_count(&user));
println!("root has {} child", root.children.borrow().len());
}
println!("path after the tree is gone: {}", file.path());
println!("parent alive: {}", file.parent.borrow().upgrade().is_some());
}
Each node has a name, a weak pointer to its parent, and strong pointers to its children.
*child.parent.borrow_mut() = Rc::downgrade(parent);
The Rc::downgrade function creates a weak pointer and
increments the weak count, not the strong count. Only the strong count
decides when the value is dropped.
match self.parent.borrow().upgrade() {
Some(parent) => format!("{}/{}", parent.path(), self.name),
None => self.name.clone(),
}
Since the value may be gone, a weak pointer cannot be dereferenced
directly. The upgrade method returns an
Option<Rc<T>>: Some if the value still
exists, None otherwise.
The user node has two strong owners (the variable and the
root's child list) and one weak reference (from the file). At the end of
the inner block, the root and the user directory are dropped, as nothing
owns them any more. The file survives because main still owns
it, but its parent pointer no longer upgrades.
$ cargo run -q path: home/jan/notes.txt user: strong = 2, weak = 1 root has 1 child dropping home dropping jan path after the tree is gone: notes.txt parent alive: false dropping notes.txt
Cell
Cell<T> is a lighter form of interior mutability. It
never hands out references to its content; values are copied in and out
with get and set. There is no borrow tracking, so
there is no run-time cost and nothing can panic.
use std::cell::Cell;
struct Counter {
name: &'static str,
hits: Cell<u32>,
}
impl Counter {
fn hit(&self) {
self.hits.set(self.hits.get() + 1);
}
}
fn main() {
let home = Counter { name: "home", hits: Cell::new(0) };
let about = Counter { name: "about", hits: Cell::new(0) };
let visits = [&home, &about, &home, &home, &about];
for page in visits {
page.hit();
}
for page in [&home, &about] {
println!("{}: {}", page.name, page.hits.get());
}
let flag = Cell::new(false);
let old = flag.replace(true);
println!("flag was {old}, now {}", flag.get());
}
Page counters are updated through shared references.
fn hit(&self) {
self.hits.set(self.hits.get() + 1);
}
The get method requires T: Copy. For other types,
replace and take swap the value out, as with
flag.replace(true), which returns the old value.
Cell fits small Copy values such as counters and
flags; RefCell is needed for strings, vectors, and other
values that must be borrowed.
$ cargo run -q home: 3 about: 2 flag was false, now true
Rc in threads
The reference count of Rc is a plain integer. If two threads
changed it at the same time, the count could get corrupted, so
Rc cannot be sent to another thread.
use std::rc::Rc;
use std::thread;
fn main() {
let data = Rc::new(vec![1, 2, 3]);
let copy = Rc::clone(&data);
let handle = thread::spawn(move || {
println!("{copy:?}");
});
handle.join().unwrap();
}
We try to move a clone of an Rc into a new thread. The
thread::spawn function requires its closure to be
Send, and Rc is not. The compiler reports error
E0277.
$ cargo run -q
error[E0277]: `std::rc::Rc<std::vec::Vec<i32>>` cannot be sent between threads safely
--> src/main.rs:8:32
|
8 | let handle = thread::spawn(move || {
| ------------- ^------
| | |
| __________________|_____________within this `{closure@src/main.rs:8:32: 8:39}`
| | |
| | required by a bound introduced by this call
9 | | println!("{copy:?}");
10 | | });
| |_____^ `std::rc::Rc<std::vec::Vec<i32>>` cannot be sent between threads safely
|
= help: within `{closure@src/main.rs:8:32: 8:39}`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::vec::Vec<i32>>`
note: required because it's used within this closure
--> src/main.rs:8:32
|
8 | let handle = thread::spawn(move || {
| ^^^^^^^
note: required by a bound in `std::thread::spawn`
--> /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/functions.rs:128:8
|
125 | pub fn spawn<F, T>(f: F) -> JoinHandle<T>
| ----- required by a bound in this function
...
128 | F: Send + 'static,
| ^^^^ required by this bound in `spawn`
For more information about this error, try `rustc --explain E0277`.
Arc
Arc<T>, the atomically reference-counted
pointer, is the thread-safe version of Rc. It updates
the count with atomic operations, which are a bit slower. Like
Rc, it gives only shared access; for mutation across threads it
is combined with Mutex or RwLock instead of
RefCell.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let words = Arc::new(vec!["sky", "falcon", "cloud", "rock", "forest", "tree"]);
let longest = Arc::new(Mutex::new(String::new()));
let mut handles = Vec::new();
for part in 0..2 {
let words = Arc::clone(&words);
let longest = Arc::clone(&longest);
handles.push(thread::spawn(move || {
for word in words.iter().skip(part * 3).take(3) {
let mut best = longest.lock().unwrap();
if word.len() > best.len() {
*best = word.to_string();
}
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("words: {}, owners: {}", words.len(), Arc::strong_count(&words));
println!("longest: {}", longest.lock().unwrap());
}
Two threads search different halves of a word list for the longest word.
let words = Arc::clone(&words); let longest = Arc::clone(&longest);
Each thread gets its own clones of the two Arc pointers, which
are moved into the closure. The shadowing keeps the names short.
let mut best = longest.lock().unwrap();
if word.len() > best.len() {
*best = word.to_string();
}
The Mutex allows one thread at a time to change the shared
string. After all threads are joined, their clones are dropped, so the
strong count is back to one. Threads are covered in
Rust threads.
$ cargo run -q words: 6, owners: 1 longest: falcon
Cow
Cow<'a, B>, short for clone on write, is a
smart pointer that holds either borrowed or owned data. It lets a function
avoid allocating when it does not need to change its input.
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains('\t') {
Cow::Owned(input.replace('\t', " "))
} else {
Cow::Borrowed(input)
}
}
fn main() {
let lines = ["plain line", "\tindented line", "another plain line"];
for line in lines {
match normalize(line) {
Cow::Borrowed(s) => println!("borrowed: {s}"),
Cow::Owned(s) => println!("owned: {s}"),
}
}
}
The normalize function replaces tabs with spaces.
if input.contains('\t') {
Cow::Owned(input.replace('\t', " "))
} else {
Cow::Borrowed(input)
}
A new string is allocated only when the input contains a tab; otherwise the
function returns the borrowed input. Either way, the result dereferences to
&str, so callers usually do not care which variant they
got.
$ cargo run -q borrowed: plain line owned: indented line borrowed: another plain line
Source
The Rust Programming Language: Smart Pointers
std::cell module documentation
In this article we have worked with smart pointers in Rust. Ownership and borrowing are covered in Rust ownership, interior mutability in Rust mutability.
Author
List all Rust tutorials.