未命名文章Rustlings Part20: — smart_pointers in Rust

109 阅读7分钟

LearningOS/rust-based-os-comp2023

Rustlings Part20: — smart_pointers in Rust

You can do it

ExerciseBook Chapter
variables§3.1
functions§3.3
if§3.5
primitive_types§3.2, §4.3
vecs§8.1
move_semantics§4.1-2
structs§5.1, §5.3
enums§6, §18.3
strings§8.2
modules§7
hashmaps§8.3
options§10.1
error_handling§9
generics§10
traits§10.2
tests§11.1
lifetimes§10.3
iterators§13.2-4
threads§16.1-3
smart_pointers§15, §16.3
macros§19.6
clippy§21.4
conversionsn/a

Smart Pointers

In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. Smart pointers in Rust often own the data they point to, while references only borrow data.

Further Information

box1.rs

Many libraries have their own smart pointers, and you can even write your own. We’ll cover the most common smart pointers in the standard library:

  • Box for allocating values on the heap
  • Rc, a reference counting type that enables multiple ownership
  • Ref and RefMut, accessed through RefCell, a type that enforces the borrowing rules at runtime instead of compile tim
  1. What is Cons()?

stackoverflow.com/questions/2…

Rust needs to know at compile time how much space a type takes up. One kind of type whose size can’t be known at compile time is a recursive type where a value can have as part of itself another value of the same type. This nesting of values could theoretically continue infinitely, so Rust doesn’t know how much space a value of a recursive type needs. Boxes have a known size, however, so by inserting a box in a recursive type definition, we are allowed to have recursive types.

  // box1.rs
//
// At compile time, Rust needs to know how much space a type takes up. This
// becomes problematic for recursive types, where a value can have as part of
// itself another value of the same type. To get around the issue, we can use a
// `Box` - a smart pointer used to store data on the heap, which also allows us
// to wrap a recursive type.
//
// The recursive type we're implementing in this exercise is the `cons list` - a
// data structure frequently found in functional programming languages. Each
// item in a cons list contains two elements: the value of the current item and
// the next item. The last item is a value called `Nil`.
//
// Step 1: use a `Box` in the enum definition to make the code compile
// Step 2: create both empty and non-empty cons lists by replacing `todo!()`
//
// Note: the tests should not be changed
//
// Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint.


#[derive(PartialEq, Debug)]
pub enum List {
    //Cons(i32, List),
    Cons(i32, Box<List>),
    //---- recursive without indirection
    // `Box` - a smart pointer used to store data on the heap, which also allows us
   // to wrap a recursive type.
    Nil,
}

fn main() {
    println!("This is an empty cons list: {:?}", create_empty_list());
    println!(
        "This is a non-empty cons list: {:?}",
        create_non_empty_list()
    );
}

//enum
pub fn create_empty_list() -> List {
    //todo!()
    List::Nil
}

pub fn create_non_empty_list() -> List {
    //todo!() 
    // Step 2: Create a non-empty list with Cons and Box
    let tail = Box::new(List::Nil);
    List::Cons(42, tail)

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_empty_list() {
        assert_eq!(List::Nil, create_empty_list())
    }

    #[test]
    fn test_create_non_empty_list() {
        assert_ne!(create_empty_list(), create_non_empty_list())
    }
}

// arc1.rs
//
// In this exercise, we are given a Vec of u32 called "numbers" with values
// ranging from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] We would like to use this
// set of numbers within 8 different threads simultaneously. Each thread is
// going to get the sum of every eighth value, with an offset.
//
// The first thread (offset 0), will sum 0, 8, 16, ...
// The second thread (offset 1), will sum 1, 9, 17, ...
// The third thread (offset 2), will sum 2, 10, 18, ...
// ...
// The eighth thread (offset 7), will sum 7, 15, 23, ...
//
// Because we are using threads, our values need to be thread-safe.  Therefore,
// we are using Arc.  We need to make a change in each of the two TODOs.
//
// Make this code compile by filling in a value for `shared_numbers` where the
// first TODO comment is, and create an initial binding for `child_numbers`
// where the second TODO comment is. Try not to create any copies of the
// `numbers` Vec!
//
// Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint.


#![forbid(unused_imports)] // Do not change this, (or the next) line.
use std::sync::Arc;
use std::thread;

fn main() {
  let numbers: Vec<_> = (0..100u32).collect();
 //let shared_numbers = // TODO
  let shared_numbers = Arc::new(numbers); // Fill in the value for shared_numbers using Arc

  let mut joinhandles = Vec::new();

  for offset in 0..8 {
      //let child_numbers = // TODO
      let child_numbers = shared_numbers.clone(); // Create a clone of shared_numbers
      joinhandles.push(thread::spawn(move || {
          let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
          println!("Sum of offset {} is {}", offset, sum);
      }));
  }
  for handle in joinhandles.into_iter() {
      handle.join().unwrap();
  }
}

###rc1.rs

// rc1.rs
//
// In this exercise, we want to express the concept of multiple owners via the
// Rc<T> type. This is a model of our solar system - there is a Sun type and
// multiple Planets. The Planets take ownership of the sun, indicating that they
// revolve around the sun.
//
// Make this code compile by using the proper Rc primitives to express that the
// sun has multiple owners.
//
// Execute `rustlings hint rc1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

use std::rc::Rc;

#[derive(Debug)]
struct Sun {}

#[derive(Debug)]
enum Planet {
  Mercury(Rc<Sun>),
  Venus(Rc<Sun>),
  Earth(Rc<Sun>),
  Mars(Rc<Sun>),
  Jupiter(Rc<Sun>),
  Saturn(Rc<Sun>),
  Uranus(Rc<Sun>),
  Neptune(Rc<Sun>),
}

impl Planet {
  fn details(&self) {
      println!("Hi from {:?}!", self)
  }
}

fn main() {
  let sun = Rc::new(Sun {});
  println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

  let mercury = Planet::Mercury(Rc::clone(&sun));
  println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
  mercury.details();

  let venus = Planet::Venus(Rc::clone(&sun));
  println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
  venus.details();

  let earth = Planet::Earth(Rc::clone(&sun));
  println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
  earth.details();

  let mars = Planet::Mars(Rc::clone(&sun));
  println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
  mars.details();

  let jupiter = Planet::Jupiter(Rc::clone(&sun));
  println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
  jupiter.details();

  // TODO
  //let saturn = Planet::Saturn(Rc::new(Sun {}));
  let saturn = Planet::Saturn(Rc::clone(&sun)); // Use Rc::clone to share the same Sun instance
  println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
  saturn.details();

  // TODO
  //let uranus = Planet::Uranus(Rc::new(Sun {}));
  let uranus = Planet::Uranus(Rc::clone(&sun));

  println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
  uranus.details();

  // TODO
 // let neptune = Planet::Neptune(Rc::new(Sun {}));
  let neptune = Planet::Uranus(Rc::clone(&sun));
  println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
  neptune.details();

  assert_eq!(Rc::strong_count(&sun), 9);

  drop(neptune);
  println!("reference count = {}", Rc::strong_count(&sun)); // 8 references

  drop(uranus);
  println!("reference count = {}", Rc::strong_count(&sun)); // 7 references

  drop(saturn);
  println!("reference count = {}", Rc::strong_count(&sun)); // 6 references

  drop(jupiter);
  println!("reference count = {}", Rc::strong_count(&sun)); // 5 references

  drop(mars);
  println!("reference count = {}", Rc::strong_count(&sun)); // 4 references

  // TODO
  drop(earth);
  println!("reference count = {}", Rc::strong_count(&sun)); // 3 references

  // TODO
  drop(venus);
  println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

  // TODO
  drop(mercury);
  println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

  assert_eq!(Rc::strong_count(&sun), 1);
}

doc.rust-lang.org/std/borrow/…

-blog.frognew.com/2020/07/rus… 1.Cow的定义 rust语言基础学习: 写时克隆智能指针Cow

Cow::from(slice);

Cow::from(slice) 的作用是将一个借用的数据(如切片 slice)包装成一个 Cow(Clone On Write)类型。Cow 是 Rust 的标准库中的一个智能指针,它允许在需要时延迟克隆数据,以减少不必要的复制。

具体来说,Cow 可以包含两种可能的值:

  1. Cow::Borrowed(&T): 当数据是不可变的并且可以被共享时,Cow 将包含一个对借用数据的引用。
  2. Cow::Owned(T): 当需要修改数据或独占数据时,Cow 将拥有数据的所有权,这时会进行克隆,使数据独占。

Cow::from(slice) 中,如果 slice 是一个可共享的不可变数据,Cow 将包含一个对 slice 的借用,这样不会进行克隆操作,而是共享现有数据。如果在后续需要修改数据,Cow 可以使用 .to_mut() 方法将借用数据转换为拥有数据,进行克隆操作以确保数据的可修改性。

这使得 Cow 成为在函数中接受不可变数据,但需要在必要时进行修改或拥有数据的一种方便方式,同时尽量减少不必要的复制。

pub enum Cow<'a, B>
where
  B: 'a + ToOwned + ?Sized,
{
  Borrowed(&'a B),
  Owned(<B as ToOwned>::Owned),
    
    
    // cow1.rs
//
// This exercise explores the Cow, or Clone-On-Write type. Cow is a
// clone-on-write smart pointer. It can enclose and provide immutable access to
// borrowed data, and clone the data lazily when mutation or ownership is
// required. The type is designed to work with general borrowed data via the
// Borrow trait.
//
// This exercise is meant to show you what to expect when passing data to Cow.
// Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the
// TODO markers.
//
// Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

use std::borrow::Cow;

fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
  for i in 0..input.len() {
      let v = input[i];
      if v < 0 {
          // Clones into a vector if not already owned.
          input.to_mut()[i] = -v;
          //如果在后续需要修改数据,Cow 可以使用 .to_mut() 方法将借用数据转换为拥有数据,进行克隆操作以确保数据的可修改性。
      }
  }
  input
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn reference_mutation() -> Result<(), &'static str> {
      // Clone occurs because `input` needs to be mutated.
      let slice = [-1, 0, 1];
      //Cow::from Creates a Borrowed variant of Cow from a slice.
      let mut input = Cow::from(&slice[..]);
      match abs_all(&mut input) {
          Cow::Owned(_) => Ok(()),
          _ => Err("Expected owned value"),
      }
  }

  #[test]
  fn reference_no_mutation() -> Result<(), &'static str> {
      // No clone occurs because `input` doesn't need to be mutated.
      let slice = [0, 1, 2];
      let mut input = Cow::from(&slice[..]);
      match abs_all(&mut input) {
          // TODO
          Cow::Borrowed(_) => Ok(()),
          _ => Err("Expected borrowed value"),
      }
  }

  #[test]
  fn owned_no_mutation() -> Result<(), &'static str> {
      // We can also pass `slice` without `&` so Cow owns it directly. In this
      // case no mutation occurs and thus also no clone, but the result is
      // still owned because it was never borrowed or mutated.

       // We can also pass `slice` without `&` so Cow owns it directly. In this
      // case no mutation occurs and thus also no clone, but the result is
      // still owned because it was never borrowed or mutated.

      // 我们也可以传递不带“&”的“slice”,因此 Cow 直接拥有它。 在这个
       // 如果没有发生突变,因此也没有克隆,但结果是
       // 仍然拥有,因为它从未被借用或变异。
      let slice = vec![0, 1, 2];
      let mut input = Cow::from(slice);
      match abs_all(&mut input) {
          Cow::Owned(_) => Ok(()), // Check for Cow::Borrowed(_)
          _ => Err("Expected borrowed value"),
      }
  }

  #[test]
  fn owned_mutation() -> Result<(), &'static str> {
      // Of course this is also the case if a mutation does occur. In this
      // case the call to `to_mut()` returns a reference to the same data as
      // before.
      let slice = vec![-1, 0, 1];
      let mut input = Cow::from(slice); //Cow::Owned(T)
      match abs_all(&mut input) {
          // TODO
          Cow::Owned(_) => Ok(()),
          _ => Err("Expected owned value"),
      }
  }
}

// 使用 match 语句检查 abs_all 的返回值是否为 Cow::Owned(_)。如果是,那么测试函数返回 Ok(()),
// 表示测试通过。如果不是,那么测试函数返回一个包含错误信息的 Result,指示测试失败。

}



本文由[mdnice](https://mdnice.com/?platform=2)多平台发布