上QQ阅读APP看书,第一时间看更新
How to do it...
Understanding shared ownership only requires eight steps:
- In the fairly young ecosystem that is Rust, APIs and function signatures are not always the most efficient, especially when they require somewhat advanced knowledge of memory layout. So, consider a simple length function (add it to the mod tests scope):
///
/// A length function that takes ownership of the input
/// variable
///
fn length(s: String) -> usize {
s.len()
}
While unnecessary, the function requires that you pass your owned variable to the scope.
- Luckily, the clone() function is ready for you if you still need ownership after the function call. This is similar to a loop, by the way, where ownership is moved in the first iteration, which means it is gone by the second iteration—leading to a compiler error. Let's add a simple test to illustrate these moves:
#[test]
fn cloning() {
let s = "abcdef".to_owned();
assert_eq!(length(s), 6);
// s is now "gone", we can't use it anymore
// therefore we can't use it in a loop either!
// ... unless we clone s - at a cost! (see benchmark)
let s = "abcdef".to_owned();
for _ in 0..10 {
// clone is typically an expensive deep copy
assert_eq!(length(s.clone()), 6);
}
}
- This works, but creates a lot of clones of a string, only then to drop it shortly after. This leads to wasting resources and, with large enough strings, slows down the program. To establish a baseline, let's check this by adding a benchmark:
extern crate test;
use std::rc::Rc;
use test::{black_box, Bencher};
#[bench]
fn bench_string_clone(b: &mut Bencher) {
let s: String = (0..100_000).map(|_| 'a').collect();
b.iter(|| {
black_box(length(s.clone()));
});
}
- Some APIs require ownership of the input variables without a semantic meaning. For example, the length function from Step 1 pretends to require variable ownership, but unless mutability is also necessary, Rust's std::rc::Rc (short for Reference Counted) type is a great choice for avoiding heavyweight cloning or taking away ownership from the calling scope. Let's try it out by creating a better length function:
///
/// The same length function, taking ownership of a Rc
///
fn rc_length(s: Rc<String>) -> usize {
s.len() // calls to the wrapped object require no additions
}
- We can now continue to use the owned type after passing it into the function:
#[test]
fn refcounting() {
let s = Rc::new("abcdef".to_owned());
// we can clone Rc (reference counters) with low cost
assert_eq!(rc_length(s.clone()), 6);
for _ in 0..10 {
// clone is typically an expensive deep copy
assert_eq!(rc_length(s.clone()), 6);
}
}
- After we have created a baseline benchmark, we certainly want to know how well the Rc version fares:
#[bench]
fn bench_string_rc(b: &mut Bencher) {
let s: String = (0..100_000).map(|_| 'a').collect();
let rc_s = Rc::new(s);
b.iter(|| {
black_box(rc_length(rc_s.clone()));
});
}
- First, we should check whether the implementations are correct by running cargo test:
$ cargo test
Compiling sharing-ownership v0.1.0 (Rust-
Cookbook/Chapter02/sharing-ownership)
Finished dev [unoptimized + debuginfo] target(s) in 0.81s
Running target/debug/deps/sharing_ownership-f029377019c63d62
running 4 tests
test tests::cloning ... ok
test tests::refcounting ... ok
test tests::bench_string_rc ... ok
test tests::bench_string_clone ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests sharing-ownership
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- Now, we can check which variation is faster, and what the differences are:
$ cargo bench
Compiling sharing-ownership v0.1.0 (Rust-
Cookbook/Chapter02/sharing-ownership)
Finished release [optimized] target(s) in 0.54s
Running target/release/deps/sharing_ownership-68bc8eb23caa9948
running 4 tests
test tests::cloning ... ignored
test tests::refcounting ... ignored
test tests::bench_string_clone ... bench: 2,703 ns/iter (+/- 289)
test tests::bench_string_rc ... bench: 1 ns/iter (+/- 0)
test result: ok. 0 passed; 0 failed; 2 ignored; 2 measured; 0 filtered out
After we have explored shared ownership with Rc, let's go behind the scenes to understand them better.