处理零大小类型(Handling Zero-Sized Types)

是时候了.我们要打击零大小的幽灵.Safe Rust 从不(never) 需要关心这一点,但Vec非常关注原始指针和原始分配,这正是关注零大小类型的两件事.我们需要注意两件事:

  • 如果为分配大小传入0,则原始分配器API具有未定义的行为.

  • 原始指针偏移对于零大小类型是无操作(no-ops),这将破坏我们的C风格指针迭代器.

值得庆幸的是,我们抽象出指针迭代器并分别将处理分配到RawValIterRawVec.多么神秘方便.

分配零大小的类型(Allocating Zero-Sized Types)

因此,如果分配器API不支持零大小的分配,那么我们将分配哪些内容作为分配? NonNull::dangling()当然!几乎每个具有ZST的操作都是无操作,因为ZST只有一个值,因此不需要考虑存储或加载它们的状态.这实际上扩展到ptr::readptr::write:它们实际上根本不会查看指针.因此,我们永远不需要更改指针.

但请注意,我们之前对溢出前内存不足的依赖不再适用于零大小的类型.我们必须显式防范零大小类型的容量溢出.

由于我们目前的架构,所有这些都意味着编写3个守卫(guards),每个RawVec方法一个.

  1. impl<T> RawVec<T> {
  2. fn new() -> Self {
  3. // !0 is usize::MAX. This branch should be stripped at compile time.
  4. let cap = if mem::size_of::<T>() == 0 { !0 } else { 0 };
  5. // `NonNull::dangling()` doubles as "unallocated" and "zero-sized allocation"
  6. RawVec {
  7. ptr: NonNull::dangling(),
  8. cap: cap,
  9. _marker: PhantomData,
  10. }
  11. }
  12. fn grow(&mut self) {
  13. // since we set the capacity to usize::MAX when T has size 0,
  14. // getting to here necessarily means the Vec is overfull.
  15. assert!(mem::size_of::<T>() != 0, "capacity overflow");
  16. let (new_cap, new_layout) = if self.cap == 0 {
  17. (1, Layout::array::<T>(1).unwrap())
  18. } else {
  19. // This can't overflow because we ensure self.cap <= isize::MAX.
  20. let new_cap = 2 * self.cap;
  21. // `Layout::array` checks that the number of bytes is <= usize::MAX,
  22. // but this is redundant since old_layout.size() <= isize::MAX,
  23. // so the `unwrap` should never fail.
  24. let new_layout = Layout::array::<T>(new_cap).unwrap();
  25. (new_cap, new_layout)
  26. };
  27. // Ensure that the new allocation doesn't exceed `isize::MAX` bytes.
  28. assert!(new_layout.size() <= isize::MAX as usize, "Allocation too large");
  29. let new_ptr = if self.cap == 0 {
  30. unsafe { alloc::alloc(new_layout) }
  31. } else {
  32. let old_layout = Layout::array::<T>(self.cap).unwrap();
  33. let old_ptr = self.ptr.as_ptr() as *mut u8;
  34. unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
  35. };
  36. // If allocation fails, `new_ptr` will be null, in which case we abort.
  37. self.ptr = match NonNull::new(new_ptr as *mut T) {
  38. Some(p) => p,
  39. None => alloc::handle_alloc_error(new_layout),
  40. };
  41. self.cap = new_cap;
  42. }
  43. }
  44. impl<T> Drop for RawVec<T> {
  45. fn drop(&mut self) {
  46. let elem_size = mem::size_of::<T>();
  47. if self.cap != 0 && elem_size != 0 {
  48. unsafe {
  49. alloc::dealloc(
  50. self.ptr.as_ptr() as *mut u8,
  51. Layout::array::<T>(self.cap).unwrap(),
  52. );
  53. }
  54. }
  55. }
  56. }

仅此而已.我们现在支持推入和弹出零大小的类型.但是,我们的迭代器(切片Deref不提供)仍然被破坏.

迭代零大小的类型(Iterating Zero-Sized Types)

零大小的偏移是无操作(no-ops).这意味着我们当前的设计将始终将startend初始化为相同的值,并且我们的迭代器将不会产生任何结果.当前的解决方案是将指针转换为整数,递增,然后将它们转换回来:

  1. impl<T> RawValIter<T> {
  2. unsafe fn new(slice: &[T]) -> Self {
  3. RawValIter {
  4. start: slice.as_ptr(),
  5. end: if mem::size_of::<T>() == 0 {
  6. ((slice.as_ptr() as usize) + slice.len()) as *const _
  7. } else if slice.len() == 0 {
  8. slice.as_ptr()
  9. } else {
  10. slice.as_ptr().add(slice.len())
  11. },
  12. }
  13. }
  14. }

现在我们有一个不同的bug.我们的迭代器现在 永远(forever) 运行,而不是我们的迭代器根本不运行.我们需要在迭代器中使用相同的技巧.此外,对于ZST,我们的size_hint计算代码将除以0.因为我们基本上将两个指针视为指向字节,所以我们只是将大小0映射为除以1.

  1. impl<T> Iterator for RawValIter<T> {
  2. type Item = T;
  3. fn next(&mut self) -> Option<T> {
  4. if self.start == self.end {
  5. None
  6. } else {
  7. unsafe {
  8. let result = ptr::read(self.start);
  9. self.start = if mem::size_of::<T>() == 0 {
  10. (self.start as usize + 1) as *const _
  11. } else {
  12. self.start.offset(1)
  13. };
  14. Some(result)
  15. }
  16. }
  17. }
  18. fn size_hint(&self) -> (usize, Option<usize>) {
  19. let elem_size = mem::size_of::<T>();
  20. let len = (self.end as usize - self.start as usize)
  21. / if elem_size == 0 { 1 } else { elem_size };
  22. (len, Some(len))
  23. }
  24. }
  25. impl<T> DoubleEndedIterator for RawValIter<T> {
  26. fn next_back(&mut self) -> Option<T> {
  27. if self.start == self.end {
  28. None
  29. } else {
  30. unsafe {
  31. self.end = if mem::size_of::<T>() == 0 {
  32. (self.end as usize - 1) as *const _
  33. } else {
  34. self.end.offset(-1)
  35. };
  36. Some(ptr::read(self.end))
  37. }
  38. }
  39. }
  40. }

就是这样.迭代工作!