Either Rc
or Arc
is the replacement for shared_ptr
. Which you choose depends on what level of thread-safety you need for the shared data; Rc
is for non-threaded cases and Arc
is when you need threads:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
let b = a.clone();
println!("{{{}, {}}}", a, b);
}
Like shared_ptr
, this does not copy the String
itself. It only increases a reference counter at runtime when clone
is called and decreases the counter when each copy goes out of scope.
Unlike shared_ptr
, Rc
and Arc
have better thread semantics. shared_ptr
is semi-thread-safe. shared_ptr
's reference counter itself is thread-safe, but the shared data is not "magically" made thread safe.
If you use shared_ptr
in a threaded program, you still have more work to do to ensure it's safe. In a non-threaded program, you are paying for some thread-safety you don't need.
If you wish to allow mutating the shared value, you will need to switch to runtime borrow checking as well. This is provided by types like Cell
, RefCell
, Mutex
etc. RefCell
is appropriate for String
and Rc
:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let a = Rc::new(RefCell::new(String::from("ping")));
let b = a.clone();
println!("{{{}, {}}}", a.borrow(), b.borrow());
a.borrow_mut().push_str("pong");
println!("{{{}, {}}}", a.borrow(), b.borrow());
}
we can count the number of references to each object during the compilation time and call free only when the last reference goes out of the scope.
That's almost exactly what Rust does with references. It doesn't actually use a counter, but it only lets you use references to a value while that value is guaranteed to remain at the same memory address.
C++'s shared_ptr
does not do this at compile-time. shared_ptr
, Rc
, and Arc
are all runtime constructs that maintain a counter.
Is it possible to make a reference to the object without invalidate the first reference?
That's exactly what Rust does with references, and what you've already done:
fn main() {
let a = String::from("ping");
let b = &a;
println!("{{{}, {}}}", a, b);
}
Even better, the compiler will stop you from using b
as soon as a
is no longer valid.
because Rust's variables are copied by reference instead of by value
This is not true. When you assign a value, ownership of the value is transferred to the new variable. Semantically, the memory address of the variable has changed and thus reading that address could lead to memory unsafety.
should we take the habit to only work with a reference
Yes, using references, when possible, is the most idiomatic choice. These require zero runtime overhead and the compiler will tell you about errors, as opposed to encountering them at runtime.
There's certainly times where Rc
or Arc
are useful. Often they are needed for cyclic data structures. You shouldn't feel bad about using them if you cannot get plain references to work.
with a reference of a reference?
This is a bit of a downside, as the extra indirection is unfortunate. If you really needed to, you can reduce it. If you don't need to modify the string, you can switch to an Rc<str>
instead:
use std::rc::Rc;
fn main() {
let a: Rc<str> = Rc::from("ping");
let b = a.clone();
println!("{{{}, {}}}", a, b);
}
If you need to keep the ability to modify the String
sometimes, you can also explicitly convert a &Rc<T>
to a &T
:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
let b = a.clone();
let a_s: &str = &*a;
let b_s: &str = &*b;
println!("{{{}, {}}}", a_s, b_s);
}
See also: