Hôm nay chúng ta sẽ tìm hiểu một thuật ngữ Copy on write trong Rust. Bạn có thể thấy thỉnh thoảng chúng ta hay sử dụng #[derive(Clone)] tại mỗi struct Đây là một trong những hình thức của thuật ngữ này trong Rust

ĐỊNH NGHĨA

Như bạn đã biết thì trong Rust khi thực hiện một phép gán, ta mặc định là move quyền sở hữu ownership sang đối tượng khác. Điều này là một trong những điểm rất khác so với ngôn ngữ khác mà mặc định sẽ là copy.

Copy on write là một kiểu thiết kế nơi mà dữ liệu gốc sẽ không bao giờ được thay đổi. Bất kỳ khi nào dữ liệu cần được thay đổi, nó sẽ được copy tới một nơi mới, thay đổi giá trị và sau đó trả về một tham chiếu tới bản copy dữ liệu này.

Ví dụ, với copy on write một mảng, thêm một phần tử sẽ trở về một mảng mới với tất cả phần tử củ và thêm một thành phần mới, và bỏ mảng cũ đi.

Trong Rust, chúng ta sử dụng #[derive(Clone)] cho thiết kế dạng này. Ngoài ra chúng ta có thể sử dụng một số kiểu dưới đây để thực hiện bằng cách thủ cộng

Trong Rust cung cấp mấy cách để

  • Rc với Arc: 2 kiểu contrỏ thông minh cung cấp nguyên lý clone on write với phương thức make_mut(). Rc is phiên bản cho single-threaded, Arc là phiên bản cho thread safe - sử dụng trong đa luồng
  • Enum Cow
pub enum Cow<'a, B> where
    B: 'a + ToOwned + ?Sized,  {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

Trong enum này chúng ta có 2 giá trị:

  • Borrowed(&'a B): Hoặc dữ liệu được được mượn
  • Dữ liệu là kiểu Owned với kiểu B là

Để trigger sang Borrowed sử dụng hàm to_mut()

THỰC HÀNH

#[derive(Clone)]
struct ListItem<T>
where
    T: Clone,
{
    data: Box<T>,
    next: Option<Box<ListItem<T>>>,
}

#[derive(Clone)]
struct SinglyLinkedList<'a, T>
where
    T: Clone,
{
    head: Cow<'a, ListItem<T>>,
}

impl<T> ListItem<T>
where
    T: Clone,
{
    fn new(data: T) -> Self {
        ListItem {
            data: Box::new(data),
            next: None,
        }
    }
    fn next(&self) -> Option<&Self> {
        if let Some(next) = &self.next {
            Some(&*next)
        } else {
            None
        }
    }
    fn mut_tail(&mut self) -> &mut Self {
        if self.next.is_some() {
            self.next.as_mut().unwrap().mut_tail()
        } else {
            self
        }
    }
    fn data(&self) -> &T {
        self.data.as_ref()
    }
}

impl<'a, T> SinglyLinkedList<'a, T>
where
    T: Clone,
{
    fn new(data: T) -> Self {
        SinglyLinkedList {
            head: Cow::Owned(ListItem::new(data)),
        }
    }
    fn append(&self, data: T) -> Self {
        let mut new_list = self.clone();
        let mut tail = new_list.head.to_mut().mut_tail();
        tail.next = Some(Box::new(ListItem::new(data)));
        new_list
    }
    fn head(&self) -> &ListItem<T> {}
}

Giải thích:

  • Đầu tiên chúng ta khởi con trỏ head bởi Cow::Owned(ListItem::new(data))
  • Để có thể copy on write sử dụng hàm to_mut()
let mut tail = new_list.head.to_mut().mut_tail();

Have fun!