Rust Programming Cookbook
上QQ阅读APP看书,第一时间看更新

How to do it...

Understanding shared ownership only requires eight steps:

  1. 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. 

  1. 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);
}
}
  1. 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()));
});
}
  1. 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
}
  1. 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);
}
}
  1. 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()));
});
}
  1. 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
  1. 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.