The body of the

Bullshit stage

The Vec of Rust is a dynamic array. Dynamic arrays are built into many languages, such as JavaScript and Python, and have been chosen by the standard library for memory-controlling languages like Rust.

Basic usage

let mut v1 : Vec<i32> = vec![]; dbg! (v1.len(), v1.capacity());for i in 1.10{ v1.push(i); } dbg! (&v1);Copy the code

layout

Now write an intuitive implementation of Rust dynamic arrays

pub struct MyVec<T> {
  ptr: *mut T,
  cap: usize,
  len: usize,}Copy the code

Naked pointer *mut T *mut *mut *mut *mut *mut *mut *mut Drop Check is helped by its internal PhantomData.

Unique

could encapsulate the naked pointer

  • TIs variable
  • Can be donedropcheck
  • TTo achieve theSend/Sync, the pointer is also availableSend/SyncFeature, equal to talking about thread safety
  • Pointers are nonnull

Send/Sync: Send/Sync: Send/Sync

use std::marker::PhantomData;

struct MyUnique<T: ?Sized> {
  ptr: *const T,
  _marker: PhantomData<T>,
}

unsafe impl<T: Send+?Sized> Send for MyUnique<T> {}

unsafe impl<T: Sync+?Sized> Sync for MyUnique<T> {}

impl<T: ?Sized> MyUnique<T> {
  #[inline]
  pub fn new(ptr: *mut T) -> Option<Self> {
    if! ptr.is_null() {Some(unsafe {
        MyUnique {
          ptr: ptr as _,
          _marker: PhantomData,
        }
      })
    } else {
      None}}#[inline]
  pub const fn as_ptr(&self) - > *mut T {
    self.ptr as *mut T
  }
}
Copy the code

We see a? The trait bound that specifies the size of generics is compile-time Sized. By default T ever Sized. Adding a question mark relaxed the constraint, and compile-time sizes were accepted. The attribute #[inline] is used to indicate inline functions. Since these functions may be used a lot, inlining can speed things up a bit.

Memory allocation

Then we need to initialize the container. If the container has something in it, it will open up memory, but the container should be empty. Since it is empty, it will not allocate memory, so we need to create an empty object with MyUnique

imp<T: ?Sized> MyUnique<T> {
  #[inline]
  pub const unsafe fn new_unchecked(ptr: *mut T) -> Self {
    MyUnique {
      ptr: ptr as _,
      _marker: PhantomData,
    }
  }
}

impl<T> MyUnique<T> {
  pub const fn empty() - >Self {
    unsafe { MyUnique::new_unchecked(mem::align_of::<T>() as *mut T) }
  }
}

pub struct MyVec<T> {
  ptr: MyUnique<T>,
  cap: usize,
  len: usize,}impl<T> MyVec<T> {
  fn new() - >Self {
    assert_ne!(mem::size_of::<T>(), 0."We're not ready to handle ZSTs");
    MyVec {
      ptr: MyUnique::empty(),
      len: 0,
      cap: 0,}}}Copy the code

I’m going to write some code for memory allocation, and since we need to allocate memory, there’s really nothing here, just read the document for memory allocation and use it, and of course think about alignment. This is actually a capacity expansion process

use std::alloc::{handle_alloc_error, realloc, Layout};
use std::mem;

impl<T> MyVec<T> {
  fn grow(&self) - >Self {
    unsafe {
      let (new_cap, ptr) = if self.cap == 0 {
        let ptr = alloc(Layout::array::<T>(1).unwrap());
        (1, ptr)
      } else {
        let new_cap = self.cap * 2;
        let layout = Layout::array::<T>(self.cap).unwrap();
        let ptr = realloc(self.ptr.as_ptr() as *mut _, layout, layout.size());
        if ptr.is_null() {
          handle_alloc_error(Layout::from_size_align_unchecked(
            new_cap * elem_size,
            mem::align_of::<T>(),
          ));
        }
        (new_cap, ptr)
      };

      Self {
        ptr: MyUnique::new_unchecked(ptr as *mut _),
        cap: new_cap,
        len: self.len,
      }
    }
  }
}
Copy the code

push & pop

The above has been able to allocate memory, the next natural is to achieve the basic function.

We’ll start with a push that adds elements to a dynamic array. If the array is full, we’ll need to reallocate memory. This is just a call to grow. The write behavior is handled by STD :: PTR’s write function, and the corresponding pop is well understood as long as the last one is read out and the length is -1

use std::{mem, ptr};

impl<T> MyVec<T> {
  pub fn ptr(&self) - > *mut T {
    self.ptr.as_ptr()
  }

  pub fn push(&mut self, element: T) {
    if self.len == self.cap {
      self.grow();
    }
    unsafe {
      ptr::write(self.ptr().add(self.len), element);
    }
    self.len += 1;
  }

  pub fn pop(&mut self) - >Option<T> {
    if self.len == 0 {
      None
    } else {
      self.len -= 1;
      unsafe { Some(ptr::read(self.ptr().add(self.len))) }
    }
  }
}
Copy the code

recycling

Rust’s mechanism makes dealing with this very simple, just implementing trait Drops

impl<T> Drop for MyVec<T> {
  fn drop(&mut self) {
    let elem_size = mem::size_of::<T>();
    ifelem_size ! =0 {
      while let Some(_) = self.pop() {}
      unsafe {
        dealloc(self.ptr() as *mut _, Layout::array::<T>(self.cap).unwrap()); }}}}Copy the code

Reference solution

So far, we have implemented a simple data structure, but we can’t communicate with Slice yet. Real Vec doesn’t work that way, so we should implement automatic dereference now. Once we have implemented these things, we can use the interface provided by Slice

use std::ops::{Deref, DerefMut};

impl<T> Deref for MyVec<T> {
  type Target = [T];

  fn deref(&self) -> &[T] {
    unsafe { std::slice::from_raw_parts(self.ptr(), self.len) }
  }
}

impl<T> DerefMut for MyVec<T> {
  fn deref_mut(&mut self) - > &mut [T] {
    unsafe { std::slice::from_raw_parts_mut(self.ptr(), self.len) }
  }
}
Copy the code

Insert and delete

insert

Insert moves all elements to the right after the current position. For example, in an array [1, 2, 3], I want to insert 10 at index 1 (element 2), so the index of 10 is 1, and the indexes of elements 2 and 3 are 2 and 3. So it’s going to be 1, 10, 2, 3.

impl<T> MyVec<T> {
  // ...
  pub fn insert(&mut self, index: usize, element: T) {
    if self.cap == self.len {
      self.grow();
    }
    unsafe {
      if index < self.len {
        ptr::copy(self.ptr().add(index), self.ptr().add(index + 1), self.len - index);
      }
      ptr::write(self.ptr().add(index), element);
      self.len += 1; }}}Copy the code

delete

Remove the array [1, 10, 2, 3] by moving all subscripts to the left. For example, delete the array [1, 10, 2, 3] by moving all subscripts to the left. The index of 10 is 1. And then minus one is done.

impl<T> MyVec<T> {
  // ...
  pub fn remove(&mut self, index: usize) -> T {
    assert!(index < self.len, "index out of bounds");
    unsafe {
      self.len -= 1;
      let result = ptr::read(self.ptr().add(index));
      ptr::copy(self.ptr().add(index + 1), self.ptr().add(index), self.len - index);
      result
    }
  }
}
Copy the code

IntoIter

Let’s start with Vec iterators. You can use slice iter and iter_mut as long as you implement auto-dereference traits, but slice doesn’t have into_iter, so we’ll have to implement that. Now the question is, why implement the IntoIter when we already have the iterator functionality for Slice? We can see that a Vec can be iterated directly with for, because as long as a custom type implements IntoIter, it has the ability to be iterated by for

let v = vec![1.2.3];
for i inv { dbg! (i); }Copy the code

We can now handle iterator operations with two Pointers, one at the beginning and one after the end, indicating the end of the iteration as long as the beginning pointer has the same address as the pointer after the end.

[1, 2, 3, 4, 5, sth]
 ^              ^
start           end
Copy the code

Now let’s create an iterator structure that looks something like this

struct IntoIter<T> {
  start: *const T,
  end: *const T,
}
Copy the code

Of course, we will have to deal with memory later, so we should save the space information allocated by Vec and of course convert MyVec into IntoIter

struct IntoIter<T> {
  start: *const T,
  end: *const T,
  buf: MyUniuqe<T>,
  cap: usize,}impl<T> MyVec<T> {
  fn into_iter(self) -> IntoIter<T> {
    let MyVec { ptr, cap, len } = self;
    mem::forget(self);
    unsafe {
      IntoIter {
        buf: ptr,
        cap,
        start: ptr.as_ptr(),
        end: if cap == 0 { ptr.as_ptr() } else { ptr.as_ptr().add(len) },
      }
    }
  }
}
Copy the code

Another iterator, size_hint, is a copy of the standard library and is used to indicate a lower or upper bound on the number of iterable elements left


impl<T> Iterator for IntoIter<T> {
  type Item = T;
  fn next(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        let result = ptr::read(self.start);
        self.start = self.start.offset(1);
        Some(result)
      }
    }
  }

  fn size_hint(&self) - > (usize.Option<usize>) {
    let len = (self.end as usize - self.start as usize) / mem::size_of::<T>();
    (len, Some(len))
  }
}
Copy the code

There is also the next_back operation


impl<T> DoubleEndedIterator for IntoIter<T> {
  fn next_back(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        self.end = self.end.offset(-1);
        Some(ptr::read(self.end))
      }
    }
  }
}
Copy the code

To handle the memory-related, we’ll implement the Drop trait for IntoIter

impl<T> Drop for IntoIter<T> {
  fn drop(&mut self) {
    if self.cap ! =0 {
      for _ in &mut *self {}
      unsafe {
        dealloc(self.buf.as_ptr() as *mut _, Layout::array::<T>(self.cap).unwrap()); }}}}Copy the code

RawVec

Now to continue refactoring the code, since we have implemented the Drop for IntoIter and MyVec, it is necessary to refactor the code

pub struct MyVec<T> {
  buf: RawVec<T>,
  len: usize,}impl<T> MyVec<T> {
  pub fn push(&mut self, element: T) {
    if self.len == self.cap() {
      self.buf.grow();
    }
    unsafe {
      ptr::write(self.ptr().add(self.len), element);
    }
    self.len += 1;
  }

  pub fn pop(&mut self) - >Option<T> {
    if self.len == 0 {
      None
    } else {
      self.len -= 1;
      unsafe { Some(ptr::read(self.ptr().add(self.len))) }
    }
  }

  pub fn insert(&mut self, index: usize, element: T) {
    assert!(index <= self.len, "index out of bounds");
    if self.cap() == self.len {
      self.buf.grow();
    }
    unsafe {
      if index < self.len {
        ptr::copy(self.ptr().add(index), self.ptr().add(index + 1), self.len - index);
      }
      ptr::write(self.ptr().add(index), element);
      self.len += 1; }}pub fn remove(&mut self, index: usize) -> T {
    assert!(index < self.len, "index out of bounds");
    unsafe {
      self.len -= 1;
      let result = ptr::read(self.ptr().add(index));
      ptr::copy(self.ptr().add(index + 1), self.ptr().add(index), self.len - index);
      result
    }
  }
}

impl<T> Drop for MyVec<T> {
  fn drop(&mut self) {
    while let Some(_) = self.pop() {}
  }
}

struct IntoIter<T> {
  start: *const T,
  end: *const T,
  _buf: RawVec<T>,
}

impl<T> Iterator for IntoIter<T> {
  type Item = T;
  fn next(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        let result = ptr::read(self.start);
        self.start = self.start.offset(1);
        Some(result)
      }
    }
  }

  fn size_hint(&self) - > (usize.Option<usize>) {
    let len = (self.end as usize - self.start as usize) / mem::size_of::<T>();
    (len, Some(len))
  }
}

impl<T> DoubleEndedIterator for IntoIter<T> {
  fn next_back(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        self.end = self.end.offset(-1);
        Some(ptr::read(self.end))
      }
    }
  }
}

impl<T> Drop for IntoIter<T> {
  fn drop(&mut self) {
    for _ in &mut *self{}}}struct RawVec<T> {
  ptr: MyUnique<T>,
  cap: usize,}impl<T> RawVec<T> {
  fn new() - >Self {
    RawVec {
      ptr: MyUnique::empty(),
      cap: 0,}}fn grow(&self) - >Self {
    unsafe {
      let (new_cap, ptr) = if self.cap == 0 {
        let ptr = alloc(Layout::array::<T>(1).unwrap());
        (1, ptr)
      } else {
        let new_cap = self.cap * 2;
        let layout = Layout::array::<T>(self.cap).unwrap();
        let ptr = realloc(self.ptr.as_ptr() as *mut _, layout, layout.size());
        if ptr.is_null() {
          handle_alloc_error(Layout::from_size_align_unchecked(
            new_cap * mem::size_of::<T>(),
            mem::align_of::<T>(),
          ));
        }
        (new_cap, ptr)
      };

      Self {
        ptr: MyUnique::new_unchecked(ptr as *mut _),
        cap: new_cap,
      }
    }
  }
}

impl<T> Drop for RawVec<T> {
  fn drop(&mut self) {
    if self.cap ! =0 {
      unsafe {
        dealloc(self.ptr.as_ptr() as *mut _, Layout::array::<T>(self.cap).unwrap()); }}}}Copy the code

Extraction iteration operation

Now that we have the basic Vec structure, we will do a package like RawVec before.

struct RawValIter<T> {
  start: *const T,
  end: *const T,
}

impl<T> RawValIter<T> {
  unsafe fn new(slice: &[T]) -> Self {
    RawValIter {
      start: slice.as_ptr(),
      end: if slice.len() == 0 {
        slice.as_ptr()
      } else {
        slice.as_ptr().offset(slice.len() as isize)},}}}impl<T> Iterator for RawValIter<T> {
  type Item = T;

  fn next(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        let result = ptr::read(self.start);
        self.start = self.start.offset(1);
        Some(result)
      }
    }
  }

  fn size_hint(&self) - > (usize.Option<usize>) {
    let len = (self.end as usize - self.start as usize) / mem::size_of::<T>();
    (len, Some(len))
  }
}

impl<T> DoubleEndedIterator for RawValIter<T> {
  fn next_back(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        self.end = self.end.offset(-1);
        Some(ptr::read(self.end))
      }
    }
  }
}
Copy the code

Then modify the iterator so that it now simply calls the RawValIter implementation inside each function

struct IntoIter<T> {
  _buf: RawVec<T>,
  iter: RawValIter<T>,
}

impl<T> Drop for MyVec<T> {
  fn drop(&mut self) {
    while let Some(_) = self.pop() {}
  }
}

impl<T> Iterator for IntoIter<T> {
  type Item = T;
  fn next(&mut self) - >Option<T> {
    self.iter.next()
  }

  fn size_hint(&self) - > (usize.Option<usize>) {
    self.iter.size_hint()
  }
}

impl<T> DoubleEndedIterator for IntoIter<T> {
  fn next_back(&mut self) - >Option<T> {
    self.iter.next_back()
  }
}

impl<T> Drop for IntoIter<T> {
  fn drop(&mut self) {
    for _ in &mut self.iter {}
  }
}
Copy the code

Deal with a Zero – Sized Types

Normally Rust does not need to deal with zero-sized Types, but now we have a lot of naked pointer operations in our code. Passing ZST to the allocator would result in undefined behavior. Offsetting a ZST naked pointer is a no-op behavior.

If T is a size_of, usize::MAX will negate the size_of bit. If T is a size_of bit, usize::MAX will negate the size_of bit. Logically no memory footprint.

impl<T> RawVec<T> {
  fn new() - >Self {
    let cap = if mem::size_of::<T>() == 0{!0 } else { 0 };
    RawVec {
      ptr: MyUnique::empty(),
      cap,
    }
  }
}
Copy the code

And then we do the grow function as well

impl<T> RawVec<T> {
  // ...
  fn grow(&self) - >Self {
    unsafe {
      let elem_size = mem::size_of::<T>();
      assert_ne!(elem_size, 0."capacity overflow");

      let (new_cap, ptr) = if self.cap == 0 {
        let ptr = alloc(Layout::array::<T>(1).unwrap());
        (1, ptr)
      } else {
        let new_cap = self.cap * 2;
        let layout = Layout::array::<T>(self.cap).unwrap();
        let ptr = realloc(self.ptr.as_ptr() as *mut _, layout, layout.size());
        (new_cap, ptr)
      };

      if ptr.is_null() {
        handle_alloc_error(Layout::from_size_align_unchecked(
          new_cap * elem_size,
          mem::align_of::<T>(),
        ));
      }

      Self {
        ptr: MyUnique::new_unchecked(ptr as *mut _),
        cap: new_cap,
      }
    }
  }
}
Copy the code

RawVec drops also need to be handled, as does the previous implementation, but pretend to align them.

impl<T> Drop for RawVec<T> {
  fn drop(&mut self) {
    let elem_size = mem::size_of::<T>();
    if self.cap ! =0&& elem_size ! =0 {
      unsafe {
        dealloc(
          self.ptr.as_ptr() as *mut _,
          Layout::from_size_align_unchecked(self.cap * elem_size, mem::align_of::<T>()), ); }}}}Copy the code

Then there is RawValIter’s ZST treatment

impl<T> RawValIter<T> {
  unsafe fn new(slice: &[T]) -> Self {
    RawValIter {
      start: slice.as_ptr(),
      end: if mem::size_of::<T>() == 0 {
        ((slice.as_ptr() as usize) + slice.len()) as *const_}else if slice.len() == 0 {
        slice.as_ptr()
      } else {
        slice.as_ptr().offset(slice.len() as isize)},}}}Copy the code

Iterators also handle size_hint divisor zero

impl<T> Iterator for RawValIter<T> {
  type Item = T;

  fn next(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        let result = ptr::read(self.start);
        self.start = if mem::size_of::<T>() == 0{(self.start as usize + 1) as *const_}else {
          self.start.offset(1)};Some(result)
      }
    }
  }

  fn size_hint(&self) - > (usize.Option<usize>) {
    let elem_size = mem::size_of::<T>();
    let len = (self.end as usize - self.start as usize)/(if elem_size == 0 { 1 } else { elem_size });
    (len, Some(len))
  }
}

impl<T> DoubleEndedIterator for RawValIter<T> {
  fn next_back(&mut self) - >Option<T> {
    if self.start == self.end {
      None
    } else {
      unsafe {
        self.end = if mem::size_of::<T>() == 0{(self.end as usize - 1) as *const_}else {
          self.end.offset(-1)};Some(ptr::read(self.end))
      }
    }
  }
}
Copy the code

with_capacity

So we’re almost done implementing this, so let’s change MyVec

impl<T> MyVec<T> {
  // ...
  pub fn with_capacity(capacity: usize) -> MyVec<T> {
    MyVec {
      buf: RawVec::with_capacity(capacity),
      len: 0}}}Copy the code

Then add an interface to RawVec

impl<T> RawVec<T> {
  fn new() - >Self {
    let cap = if mem::size_of::<T>() == 0{!0 } else { 0 };
    RawVec {
      ptr: MyUnique::empty(),
      cap,
    }
  }

  pub fn with_capacity(cap: usize) - >Self {
    RawVec::allocate_in(cap, None)}fn allocate_in(cap: usize, p: Option<MyUnique<T>>) -> Self {
    unsafe {
      let elem_size = mem::size_of::<T>();
      assert_ne!(elem_size, 0."capacity overflow");

      let (new_cap, ptr) = if cap == 0 {
        let ptr = alloc(Layout::array::<T>(1).unwrap());
        (1, ptr)
      } else {
        if let Some(some_p) = p {
          let new_cap = cap * 2;
          let layout = Layout::array::<T>(cap).unwrap();
          let ptr = realloc(some_p.as_ptr() as *mut _, layout, layout.size());
          (new_cap, ptr)
        } else {
          let ptr = alloc(Layout::array::<T>(cap).unwrap());
          (cap, ptr)
        }
      };

      if ptr.is_null() {
        handle_alloc_error(Layout::from_size_align_unchecked(
          new_cap * elem_size,
          mem::align_of::<T>(),
        ));
      }

      Self {
        ptr: MyUnique::new_unchecked(ptr as *mut _),
        cap: new_cap,
      }
    }
  }

  fn grow(&self) - >Self {
    RawVec::allocate_in(self.cap, Some(self.ptr))
  }
}
Copy the code

We’ve now extracted grow and called with_capacity

Drain

It is to be decided at present. This is a bit more troublesome. I haven’t thought about how to say it.

Git hosting

Finally, I’ll put the code up

E.coding.net/limitLiu/al…