diff --git a/README.md b/README.md index abf9535a..71bca93d 100644 --- a/README.md +++ b/README.md @@ -47,24 +47,17 @@ - @@ -76,6 +69,13 @@
- - + +
- Sunface 🥇 + EluvK

- AllanDowney 🥈 -
-
- - -
- EluvK 🥉 + AllanDowney
+ -
+ + +
+ 孙立刚 +
+
@@ -97,13 +97,6 @@ 1132719438 - - -
- zongzi531 -
-
@@ -141,7 +134,10 @@ ## 社区 & 读者交流 - 知乎: [孙飞 Sunface](https://www.zhihu.com/people/iSunface) -- QQ 群 `1009730433`,用于日常技术交流 +- RustCn 微信交流 2 群: + + + - 微信公众号: 扫描下面的二维码关注公众号 `Rust语言中文网` diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ce1e5052..37f4e711 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -6,18 +6,14 @@ [社区和锈书](community.md) - --- -[Datav: 可编程的数据可视化平台和可观测性平台](some-thoughts.md) - +[Xobserve: 一切皆可观测](some-thoughts.md) +[BeatAI: 工程师 AI 入门圣经](beat-ai.md) - - - # Rust 语言基础学习 --- @@ -29,7 +25,6 @@ - [不仅仅是 Hello world](first-try/hello-world.md) - [下载依赖太慢了?](first-try/slowly-downloading.md) - - [Rust 基础入门](basic/intro.md) - [变量绑定与解构](basic/variable.md) @@ -135,7 +130,6 @@ - [多线程版本](advance-practice1/multi-threads.md) - [优雅关闭和资源清理](advance-practice1/graceful-shutdown.md) - - [进阶实战2: 实现一个简单 Redis](advance-practice/intro.md) - [tokio 概览](advance-practice/overview.md) - [使用初印象](advance-practice/getting-startted.md) @@ -247,8 +241,19 @@ - [数据布局 2](too-many-lists/unsafe-queue/layout2.md) - [额外的操作](too-many-lists/unsafe-queue/extra-junk.md) - [最终代码](too-many-lists/unsafe-queue/final-code.md) + - [生产级的双向 unsafe 队列](too-many-lists/production-unsafe-deque/intro.md) + - [数据布局](too-many-lists/production-unsafe-deque/layout.md) + - [型变与子类型](too-many-lists/production-unsafe-deque/variance-and-phantomData.md) + - [基础结构](too-many-lists/production-unsafe-deque/basics.md) + - [恐慌与安全](too-many-lists/production-unsafe-deque/drop-and-panic-safety.md) + - [无聊的组合](too-many-lists/production-unsafe-deque/boring-combinatorics.md) + - [其它特征](too-many-lists/production-unsafe-deque/filling-in-random-bits.md) + - [测试](too-many-lists/production-unsafe-deque/testing.md) + - [Send,Sync和编译测试](too-many-lists/production-unsafe-deque/send-sync-and-compile-tests.md) + - [实现游标](too-many-lists/production-unsafe-deque/implementing-cursors.md) + - [测试游标](too-many-lists/production-unsafe-deque/testing-cursors.md) + - [最终代码](too-many-lists/production-unsafe-deque/final-code.md) - [使用高级技巧实现链表](too-many-lists/advanced-lists/intro.md) - - [生产级可用的双向链表](too-many-lists/advanced-lists/unsafe-deque.md) - [双单向链表](too-many-lists/advanced-lists/double-singly.md) - [栈上的链表](too-many-lists/advanced-lists/stack-allocated.md) @@ -313,8 +318,6 @@ - [编译器优化 todo](profiling/compiler/optimization/intro.md) - [Option 枚举 todo](profiling/compiler/optimization/option.md) - - src\lib.rs:39:17 + | +39 | (*old).front = Some(new); + | ^^^^^^ +``` + +是的,我真恨 `NonNull>`。我们需要明确地使用 `as_ptr` 从 NonNull 中获取原始指针,因为 DerefMut 是以 `&mut` 定义的,我们不想在不安全代码中随意引入安全引用! + +```rust + (*old.as_ptr()).front = Some(new); + (*new.as_ptr()).back = Some(old); + Compiling linked-list v0.0.3 +warning: field is never read: `elem` + --> src\lib.rs:16:5 + | +16 | elem: T, + | ^^^^^^^ + | + = note: `#[warn(dead_code)]` on by default + +warning: `linked-list` (lib) generated 1 warning (1 duplicate) +warning: `linked-list` (lib test) generated 1 warning + Finished test [unoptimized + debuginfo] target(s) in 0.33s +``` + +很好,接下来是 `pop` 和 `len`: + +```rust +pub fn pop_front(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a front node to pop. + // Note that we don't need to mess around with `take` anymore + // because everything is Copy and there are no dtors that will + // run if we mess up... right? :) Riiiight? :))) + self.front.map(|node| { + // Bring the Box back to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new front. + self.front = boxed_node.back; + if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; + } else { + // If the front is now null, then this list is now empty! + debug_assert!(self.len == 1); + self.back = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } +} + +pub fn len(&self) -> usize { + self.len +} + Compiling linked-list v0.0.3 + Finished dev [unoptimized + debuginfo] target(s) in 0.37s +``` + +在我看来是合法的,是时候写一个测试了! + +```rust +#[cfg(test)] +mod test { + use super::LinkedList; + + #[test] + fn test_basic_front() { + let mut list = LinkedList::new(); + + // Try to break an empty list + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Try to break a one item list + list.push_front(10); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Mess around + list.push_front(10); + assert_eq!(list.len(), 1); + list.push_front(20); + assert_eq!(list.len(), 2); + list.push_front(30); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(30)); + assert_eq!(list.len(), 2); + list.push_front(40); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(40)); + assert_eq!(list.len(), 2); + assert_eq!(list.pop_front(), Some(20)); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + } +} + Compiling linked-list v0.0.3 + Finished test [unoptimized + debuginfo] target(s) in 0.40s + Running unittests src\lib.rs + +running 1 test +test test::test_basic_front ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +万幸,我们是完美的!是吗? diff --git a/src/too-many-lists/production-unsafe-deque/boring-combinatorics.md b/src/too-many-lists/production-unsafe-deque/boring-combinatorics.md new file mode 100644 index 00000000..0e294d9c --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/boring-combinatorics.md @@ -0,0 +1,565 @@ +# Boring Combinatorics + +好了,回到我们的常规链接列表! + +首先,让我们来解决 `Drop` 的问题: + +```rust +impl Drop for LinkedList { + fn drop(&mut self) { + // Pop until we have to stop + while let Some(_) = self.pop_front() { } + } +} +``` + +我们必须填写一堆非常无聊的组合实现,如 front、front_mut、back、back_mut、iter、iter_mut、into_iter...... + +我已经精心设计了之前的 push/pop 实现,因此我们只需前后对调,代码就能做正确的事情!痛苦的经验万岁!(对于节点来说,使用 "prev和next "是很有诱惑力的,但我发现,为了避免错误,尽量使用 "front "和 "back"才是真正对的)。 + +首先是 `front`: + +```rust +pub fn front(&self) -> Option<&T> { + unsafe { + self.front.map(|node| &(*node.as_ptr()).elem) + } +} +``` + +接着是: + +```rust +pub fn front_mut(&mut self) -> Option<&mut T> { + unsafe { + self.front.map(|node| &mut (*node.as_ptr()).elem) + } +} +``` + +我会把所有的 `back` 版本放到文章最终的代码中。 + +接下来是迭代器。与之前的所有列表不同,我们终于解锁了双端迭代器([DoubleEndedIterator](https://doc.rust-lang.org/std/iter/trait.DoubleEndedIterator.html))的功能,而且如果要达到生产质量,我们还要支持精确大小迭代器( [ExactSizeIterator](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html))。 + +因此,除了 `next` 和 `size_hint`,我们还将支持 `next_back` 和 `len`。 + +```rust +pub struct Iter<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a T>, +} + +impl LinkedList { + pub fn iter(&self) -> Iter { + Iter { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } +} + + +impl<'a, T> IntoIterator for &'a LinkedList { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &(*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &(*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} +``` + +...这只是 `.iter()`... + +我们将在最后粘贴 IterMut,它在很多地方与 `mut` 的代码完全相同,让我们先敲掉 `into_iter`。我们仍然可以使用我们屡试不爽的解决方案,即让它包裹我们的集合,并在下一步中使用 `pop`: + +```rust +pub struct IntoIter { + list: LinkedList, +} + +impl LinkedList { + pub fn into_iter(self) -> IntoIter { + IntoIter { + list: self + } + } +} + + +impl IntoIterator for LinkedList { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + self.into_iter() + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.list.pop_front() + } + + fn size_hint(&self) -> (usize, Option) { + (self.list.len, Some(self.list.len)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.list.pop_back() + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.list.len + } +} +``` + +仍然是一大堆模板,但至少是令人满意的模板。 + +好了,这是我们所有的代码,其中包含了所有的组合: + +```rust +use std::ptr::NonNull; +use std::marker::PhantomData; + +pub struct LinkedList { + front: Link, + back: Link, + len: usize, + _boo: PhantomData, +} + +type Link = Option>>; + +struct Node { + front: Link, + back: Link, + elem: T, +} + +pub struct Iter<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a T>, +} + +pub struct IterMut<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a mut T>, +} + +pub struct IntoIter { + list: LinkedList, +} + +impl LinkedList { + pub fn new() -> Self { + Self { + front: None, + back: None, + len: 0, + _boo: PhantomData, + } + } + + pub fn push_front(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + front: None, + back: None, + elem, + }))); + if let Some(old) = self.front { + // Put the new front before the old one + (*old.as_ptr()).front = Some(new); + (*new.as_ptr()).back = Some(old); + } else { + // If there's no front, then we're the empty list and need + // to set the back too. + self.back = Some(new); + } + // These things always happen! + self.front = Some(new); + self.len += 1; + } + } + + pub fn push_back(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + back: None, + front: None, + elem, + }))); + if let Some(old) = self.back { + // Put the new back before the old one + (*old.as_ptr()).back = Some(new); + (*new.as_ptr()).front = Some(old); + } else { + // If there's no back, then we're the empty list and need + // to set the front too. + self.front = Some(new); + } + // These things always happen! + self.back = Some(new); + self.len += 1; + } + } + + pub fn pop_front(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a front node to pop. + self.front.map(|node| { + // Bring the Box back to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new front. + self.front = boxed_node.back; + if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; + } else { + // If the front is now null, then this list is now empty! + self.back = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn pop_back(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a back node to pop. + self.back.map(|node| { + // Bring the Box front to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new back. + self.back = boxed_node.front; + if let Some(new) = self.back { + // Cleanup its reference to the removed node + (*new.as_ptr()).back = None; + } else { + // If the back is now null, then this list is now empty! + self.front = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn front(&self) -> Option<&T> { + unsafe { + self.front.map(|node| &(*node.as_ptr()).elem) + } + } + + pub fn front_mut(&mut self) -> Option<&mut T> { + unsafe { + self.front.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn back(&self) -> Option<&T> { + unsafe { + self.back.map(|node| &(*node.as_ptr()).elem) + } + } + + pub fn back_mut(&mut self) -> Option<&mut T> { + unsafe { + self.back.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn iter(&self) -> Iter { + Iter { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn into_iter(self) -> IntoIter { + IntoIter { + list: self + } + } +} + +impl Drop for LinkedList { + fn drop(&mut self) { + // Pop until we have to stop + while let Some(_) = self.pop_front() { } + } +} + +impl<'a, T> IntoIterator for &'a LinkedList { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &(*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &(*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> IntoIterator for &'a mut LinkedList { + type IntoIter = IterMut<'a, T>; + type Item = &'a mut T; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for IterMut<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl IntoIterator for LinkedList { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + self.into_iter() + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.list.pop_front() + } + + fn size_hint(&self) -> (usize, Option) { + (self.list.len, Some(self.list.len)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.list.pop_back() + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.list.len + } +} + + +#[cfg(test)] +mod test { + use super::LinkedList; + + #[test] + fn test_basic_front() { + let mut list = LinkedList::new(); + + // Try to break an empty list + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Try to break a one item list + list.push_front(10); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Mess around + list.push_front(10); + assert_eq!(list.len(), 1); + list.push_front(20); + assert_eq!(list.len(), 2); + list.push_front(30); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(30)); + assert_eq!(list.len(), 2); + list.push_front(40); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(40)); + assert_eq!(list.len(), 2); + assert_eq!(list.pop_front(), Some(20)); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + } +} +``` diff --git a/src/too-many-lists/production-unsafe-deque/drop-and-panic-safety.md b/src/too-many-lists/production-unsafe-deque/drop-and-panic-safety.md new file mode 100644 index 00000000..5c0cdc06 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/drop-and-panic-safety.md @@ -0,0 +1,162 @@ +# Drop and Panic Safety + +嘿,你注意到这些注释了吗: + +```rust +// Note that we don't need to mess around with `take` anymore +// because everything is Copy and there are no dtors that will +// run if we mess up... right? :) Riiiight? :))) +``` + +这对吗? + +你忘记你正在读那本书了吗?当然这是错误的(部分上是)。 + +让我们再次看看 pop_front 内部: + +```rust +// Bring the Box back to life so we can move out its value and +// Drop it (Box continues to magically understand this for us). +let boxed_node = Box::from_raw(node.as_ptr()); +let result = boxed_node.elem; + +// Make the next node into the new front. +self.front = boxed_node.back; +if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; +} else { + // If the front is now null, then this list is now empty! + debug_assert!(self.len == 1); + self.back = None; +} + +self.len -= 1; +result +// Box gets implicitly freed here, knows there is no T. +``` + +你看到 bug 了吗? 真可怕, 是这一行: + +```rust +debug_assert!(self.len == 1); +``` + +大多数情况下,你不需要考虑或担心恐慌,但一旦你开始编写真正不安全的代码,并在 "invariants(不可变性) "上大做文章,你就需要对恐慌保持高度警惕! + +我们必须谈谈 [*异常安全*](https://doc.rust-lang.org/nightly/nomicon/exception-safety.html) (又名恐慌安全、解除安全......)。 + +情况是这样的:在默认情况下,恐慌会被 unwinding。unwind 只是 "让每个函数立即返回 "的一种花哨说法。你可能会想:"好吧,如果每个函数都返回,那么程序就要结束了,何必在乎它呢?"但你错了! + +我们必须关注有两个原因:当函数返回时,析构函数会运行,而且可以捕获 unwind。在这两种情况下,代码都可能在恐慌发生后继续运行,因此我们必须非常小心,确保我们的不安全的集合在恐慌发生时始终处于某种一致的状态,因为每次恐慌都是隐式的提前返回! + +让我们想一想,到这一行时,我们的集合处于什么状态: + +我们将 boxed_node 放在栈上,并从中提取了元素。如果我们在此时返回,Box 将被丢弃,节点将被释放。self.back 仍然指向那个被释放的节点!一旦我们使用 self.back 来处理一些事情,这就可能导致释放后再使用! + +有趣的是,这行也有类似的问题,但它要安全得多: + +```rust +self.len -= 1; +``` + +默认情况下,Rust 会在调试构建时检查上溢和下溢,并在发生时产生恐慌。是的,每一次算术运算都会带来恐慌安全隐患!这行还好,他不会导致内存错误,因为之前已经完成了该做的所有操作。所以调试断言哪行在某种意义上更糟糕,因为它可能将一个小问题升级为关键问题! + +在实现过程中,只要我们确保在别人注意到之前修复它们,我们可以临时性的破坏invariants(不可变性)。这实际上是 Rust 的集合所有权和借用系统的 "杀手级应用 "之一:如果一个操作需要一个 `&mut Self`,那么我们就能保证对我们的集合拥有独占访问权,而且我们可以暂时破坏invariants(不可变性),因为我们知道没有人能偷偷摸摸地破坏它。 + +我们有两种方法可以让我们的代码更健壮: + +- 更积极地使用 Option::take 这样的操作,因为它们更 "事务性",更倾向于保留invariants(不可变性)。 +- 放弃 debug_asserts,相信自己能写出更好的测试,并使用专用的 "完整性检查 "函数,而这些函数永远不会在用户代码中运行。 + +原则上,我喜欢第一种方案,但它对双链路列表的实际效果并不好,因为所有内容都是双冗余编码的。Option::take 并不能解决这里的问题,但将 debug_assert 下移一行却可以。不过说真的,为什么要为难我们自己呢?让我们移除那些 debug_asserts,并确保任何可能引起恐慌的事情都发生在我们方法的开头或结尾,而我们在这些地方保持invariants(不可变性)。 + +这是我们的全部实现: + +```rust +use std::ptr::NonNull; +use std::marker::PhantomData; + +pub struct LinkedList { + front: Link, + back: Link, + len: usize, + _boo: PhantomData, +} + +type Link = Option>>; + +struct Node { + front: Link, + back: Link, + elem: T, +} + +impl LinkedList { + pub fn new() -> Self { + Self { + front: None, + back: None, + len: 0, + _boo: PhantomData, + } + } + + pub fn push_front(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + front: None, + back: None, + elem, + }))); + if let Some(old) = self.front { + // Put the new front before the old one + (*old.as_ptr()).front = Some(new); + (*new.as_ptr()).back = Some(old); + } else { + // If there's no front, then we're the empty list and need + // to set the back too. + self.back = Some(new); + } + // These things always happen! + self.front = Some(new); + self.len += 1; + } + } + + pub fn pop_front(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a front node to pop. + self.front.map(|node| { + // Bring the Box back to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new front. + self.front = boxed_node.back; + if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; + } else { + // If the front is now null, then this list is now empty! + self.back = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn len(&self) -> usize { + self.len + } +} +``` + +这还有什么可以引发恐慌?老实说,要知道这些需要你是 Rust 专家,不过幸好我是! + +在这段代码中,我能看到的唯一可能引起恐慌的地方是 `Box::new`(用于内存不足的情况)和 `len` 运算。所有这些都在我们方法的最末端或最开始,所以,我们是安全的! diff --git a/src/too-many-lists/production-unsafe-deque/filling-in-random-bits.md b/src/too-many-lists/production-unsafe-deque/filling-in-random-bits.md new file mode 100644 index 00000000..79cfe4e6 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/filling-in-random-bits.md @@ -0,0 +1,572 @@ +# Filling In Random Bits + +嘿,你不是说要做成精品吗? + +为了成为一个 "好 "系列,这里还有一些乱七八糟的东西: + +```rust +impl LinkedList { + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn clear(&mut self) { + // Oh look it's drop again + while let Some(_) = self.pop_front() { } + } +} +``` + +现在,我们已经有了一大堆大家都期待的特性需要实现: + +```rust +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + +impl Clone for LinkedList { + fn clone(&self) -> Self { + let mut new_list = Self::new(); + for item in self { + new_list.push_back(item.clone()); + } + new_list + } +} + +impl Extend for LinkedList { + fn extend>(&mut self, iter: I) { + for item in iter { + self.push_back(item); + } + } +} + +impl FromIterator for LinkedList { + fn from_iter>(iter: I) -> Self { + let mut list = Self::new(); + list.extend(iter); + list + } +} + +impl Debug for LinkedList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self).finish() + } +} + +impl PartialEq for LinkedList { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other) + } + + fn ne(&self, other: &Self) -> bool { + self.len() != other.len() || self.iter().ne(other) + } +} + +impl Eq for LinkedList { } + +impl PartialOrd for LinkedList { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other) + } +} + +impl Ord for LinkedList { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other) + } +} + +impl Hash for LinkedList { + fn hash(&self, state: &mut H) { + self.len().hash(state); + for item in self { + item.hash(state); + } + } +} +``` + +另一个有趣的话题是哈希本身。你看到我们如何将 `len` 写入散列的吗?这其实非常重要!如果集合不把 `len` 加入散列,很可能会意外的造成前缀碰撞。例如,一个集合包含 `["he", "llo"]` 另一个集合包含 `["hello"]`,我们该如何区分?如果没有把集合长度或其它"分隔符"加入到散列 ,这将毫无意义!会让意外哈希碰撞发生变得太容易,会导致严重的后果,所以还是照做吧! + +好了,这是我们现在的代码: + +```rust +use std::cmp::Ordering; +use std::fmt::{self, Debug}; +use std::hash::{Hash, Hasher}; +use std::iter::FromIterator; +use std::ptr::NonNull; +use std::marker::PhantomData; + +pub struct LinkedList { + front: Link, + back: Link, + len: usize, + _boo: PhantomData, +} + +type Link = Option>>; + +struct Node { + front: Link, + back: Link, + elem: T, +} + +pub struct Iter<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a T>, +} + +pub struct IterMut<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a mut T>, +} + +pub struct IntoIter { + list: LinkedList, +} + +impl LinkedList { + pub fn new() -> Self { + Self { + front: None, + back: None, + len: 0, + _boo: PhantomData, + } + } + + pub fn push_front(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + front: None, + back: None, + elem, + }))); + if let Some(old) = self.front { + // Put the new front before the old one + (*old.as_ptr()).front = Some(new); + (*new.as_ptr()).back = Some(old); + } else { + // If there's no front, then we're the empty list and need + // to set the back too. + self.back = Some(new); + } + // These things always happen! + self.front = Some(new); + self.len += 1; + } + } + + pub fn push_back(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + back: None, + front: None, + elem, + }))); + if let Some(old) = self.back { + // Put the new back before the old one + (*old.as_ptr()).back = Some(new); + (*new.as_ptr()).front = Some(old); + } else { + // If there's no back, then we're the empty list and need + // to set the front too. + self.front = Some(new); + } + // These things always happen! + self.back = Some(new); + self.len += 1; + } + } + + pub fn pop_front(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a front node to pop. + self.front.map(|node| { + // Bring the Box back to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new front. + self.front = boxed_node.back; + if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; + } else { + // If the front is now null, then this list is now empty! + self.back = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn pop_back(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a back node to pop. + self.back.map(|node| { + // Bring the Box front to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new back. + self.back = boxed_node.front; + if let Some(new) = self.back { + // Cleanup its reference to the removed node + (*new.as_ptr()).back = None; + } else { + // If the back is now null, then this list is now empty! + self.front = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn front(&self) -> Option<&T> { + unsafe { + self.front.map(|node| &(*node.as_ptr()).elem) + } + } + + pub fn front_mut(&mut self) -> Option<&mut T> { + unsafe { + self.front.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn back(&self) -> Option<&T> { + unsafe { + self.back.map(|node| &(*node.as_ptr()).elem) + } + } + + pub fn back_mut(&mut self) -> Option<&mut T> { + unsafe { + self.back.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn clear(&mut self) { + // Oh look it's drop again + while let Some(_) = self.pop_front() { } + } + + pub fn iter(&self) -> Iter { + Iter { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn into_iter(self) -> IntoIter { + IntoIter { + list: self + } + } +} + +impl Drop for LinkedList { + fn drop(&mut self) { + // Pop until we have to stop + while let Some(_) = self.pop_front() { } + } +} + +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + +impl Clone for LinkedList { + fn clone(&self) -> Self { + let mut new_list = Self::new(); + for item in self { + new_list.push_back(item.clone()); + } + new_list + } +} + +impl Extend for LinkedList { + fn extend>(&mut self, iter: I) { + for item in iter { + self.push_back(item); + } + } +} + +impl FromIterator for LinkedList { + fn from_iter>(iter: I) -> Self { + let mut list = Self::new(); + list.extend(iter); + list + } +} + +impl Debug for LinkedList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self).finish() + } +} + +impl PartialEq for LinkedList { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other) + } + + fn ne(&self, other: &Self) -> bool { + self.len() != other.len() || self.iter().ne(other) + } +} + +impl Eq for LinkedList { } + +impl PartialOrd for LinkedList { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other) + } +} + +impl Ord for LinkedList { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other) + } +} + +impl Hash for LinkedList { + fn hash(&self, state: &mut H) { + self.len().hash(state); + for item in self { + item.hash(state); + } + } +} + +impl<'a, T> IntoIterator for &'a LinkedList { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &(*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &(*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> IntoIterator for &'a mut LinkedList { + type IntoIter = IterMut<'a, T>; + type Item = &'a mut T; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for IterMut<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl IntoIterator for LinkedList { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + self.into_iter() + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.list.pop_front() + } + + fn size_hint(&self) -> (usize, Option) { + (self.list.len, Some(self.list.len)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.list.pop_back() + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.list.len + } +} + + +#[cfg(test)] +mod test { + use super::LinkedList; + + #[test] + fn test_basic_front() { + let mut list = LinkedList::new(); + + // Try to break an empty list + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Try to break a one item list + list.push_front(10); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Mess around + list.push_front(10); + assert_eq!(list.len(), 1); + list.push_front(20); + assert_eq!(list.len(), 2); + list.push_front(30); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(30)); + assert_eq!(list.len(), 2); + list.push_front(40); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(40)); + assert_eq!(list.len(), 2); + assert_eq!(list.pop_front(), Some(20)); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + } +} +``` diff --git a/src/too-many-lists/production-unsafe-deque/final-code.md b/src/too-many-lists/production-unsafe-deque/final-code.md new file mode 100644 index 00000000..515b0823 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/final-code.md @@ -0,0 +1,1210 @@ +# Final Code + +我真不敢相信,我居然让你坐在那里,听我从头开始重新实现 std::collections::LinkedList,一路上我犯了很多繁琐的小错误。 + +我做到了,书写完了,我终于可以休息了。 + +好了,下面是我们完整重写的 1200 行代码的全部内容。这应该与 [this commit](https://github.com/contain-rs/linked-list/commit/5b69cc29454595172a5167a09277660342b78092) 的文本相同。 + +```rust +use std::cmp::Ordering; +use std::fmt::{self, Debug}; +use std::hash::{Hash, Hasher}; +use std::iter::FromIterator; +use std::marker::PhantomData; +use std::ptr::NonNull; + +pub struct LinkedList { + front: Link, + back: Link, + len: usize, + _boo: PhantomData, +} + +type Link = Option>>; + +struct Node { + front: Link, + back: Link, + elem: T, +} + +pub struct Iter<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a T>, +} + +pub struct IterMut<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a mut T>, +} + +pub struct IntoIter { + list: LinkedList, +} + +pub struct CursorMut<'a, T> { + list: &'a mut LinkedList, + cur: Link, + index: Option, +} + +impl LinkedList { + pub fn new() -> Self { + Self { + front: None, + back: None, + len: 0, + _boo: PhantomData, + } + } + + pub fn push_front(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + front: None, + back: None, + elem, + }))); + if let Some(old) = self.front { + // Put the new front before the old one + (*old.as_ptr()).front = Some(new); + (*new.as_ptr()).back = Some(old); + } else { + // If there's no front, then we're the empty list and need + // to set the back too. + self.back = Some(new); + } + // These things always happen! + self.front = Some(new); + self.len += 1; + } + } + + pub fn push_back(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + back: None, + front: None, + elem, + }))); + if let Some(old) = self.back { + // Put the new back before the old one + (*old.as_ptr()).back = Some(new); + (*new.as_ptr()).front = Some(old); + } else { + // If there's no back, then we're the empty list and need + // to set the front too. + self.front = Some(new); + } + // These things always happen! + self.back = Some(new); + self.len += 1; + } + } + + pub fn pop_front(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a front node to pop. + self.front.map(|node| { + // Bring the Box back to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new front. + self.front = boxed_node.back; + if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; + } else { + // If the front is now null, then this list is now empty! + self.back = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn pop_back(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a back node to pop. + self.back.map(|node| { + // Bring the Box front to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new back. + self.back = boxed_node.front; + if let Some(new) = self.back { + // Cleanup its reference to the removed node + (*new.as_ptr()).back = None; + } else { + // If the back is now null, then this list is now empty! + self.front = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn front(&self) -> Option<&T> { + unsafe { self.front.map(|node| &(*node.as_ptr()).elem) } + } + + pub fn front_mut(&mut self) -> Option<&mut T> { + unsafe { self.front.map(|node| &mut (*node.as_ptr()).elem) } + } + + pub fn back(&self) -> Option<&T> { + unsafe { self.back.map(|node| &(*node.as_ptr()).elem) } + } + + pub fn back_mut(&mut self) -> Option<&mut T> { + unsafe { self.back.map(|node| &mut (*node.as_ptr()).elem) } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn clear(&mut self) { + // Oh look it's drop again + while self.pop_front().is_some() {} + } + + pub fn iter(&self) -> Iter { + Iter { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn cursor_mut(&mut self) -> CursorMut { + CursorMut { + list: self, + cur: None, + index: None, + } + } +} + +impl Drop for LinkedList { + fn drop(&mut self) { + // Pop until we have to stop + while self.pop_front().is_some() {} + } +} + +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + +impl Clone for LinkedList { + fn clone(&self) -> Self { + let mut new_list = Self::new(); + for item in self { + new_list.push_back(item.clone()); + } + new_list + } +} + +impl Extend for LinkedList { + fn extend>(&mut self, iter: I) { + for item in iter { + self.push_back(item); + } + } +} + +impl FromIterator for LinkedList { + fn from_iter>(iter: I) -> Self { + let mut list = Self::new(); + list.extend(iter); + list + } +} + +impl Debug for LinkedList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self).finish() + } +} + +impl PartialEq for LinkedList { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other) + } +} + +impl Eq for LinkedList {} + +impl PartialOrd for LinkedList { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other) + } +} + +impl Ord for LinkedList { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other) + } +} + +impl Hash for LinkedList { + fn hash(&self, state: &mut H) { + self.len().hash(state); + for item in self { + item.hash(state); + } + } +} + +impl<'a, T> IntoIterator for &'a LinkedList { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &(*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &(*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> IntoIterator for &'a mut LinkedList { + type IntoIter = IterMut<'a, T>; + type Item = &'a mut T; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for IterMut<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl IntoIterator for LinkedList { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { list: self } + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.list.pop_front() + } + + fn size_hint(&self) -> (usize, Option) { + (self.list.len, Some(self.list.len)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.list.pop_back() + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.list.len + } +} + +impl<'a, T> CursorMut<'a, T> { + pub fn index(&self) -> Option { + self.index + } + + pub fn move_next(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its next (back) + self.cur = (*cur.as_ptr()).back; + if self.cur.is_some() { + *self.index.as_mut().unwrap() += 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real front, so move to it! + self.cur = self.list.front; + self.index = Some(0) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } + + pub fn move_prev(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its previous (front) + self.cur = (*cur.as_ptr()).front; + if self.cur.is_some() { + *self.index.as_mut().unwrap() -= 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real back, so move to it! + self.cur = self.list.back; + self.index = Some(self.list.len - 1) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } + + pub fn current(&mut self) -> Option<&mut T> { + unsafe { self.cur.map(|node| &mut (*node.as_ptr()).elem) } + } + + pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + let next = if let Some(cur) = self.cur { + // Normal case, try to follow the cur node's back pointer + (*cur.as_ptr()).back + } else { + // Ghost case, try to use the list's front pointer + self.list.front + }; + + // Yield the element if the next node exists + next.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn peek_prev(&mut self) -> Option<&mut T> { + unsafe { + let prev = if let Some(cur) = self.cur { + // Normal case, try to follow the cur node's front pointer + (*cur.as_ptr()).front + } else { + // Ghost case, try to use the list's back pointer + self.list.back + }; + + // Yield the element if the prev node exists + prev.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn split_before(&mut self) -> LinkedList { + // We have this: + // + // list.front -> A <-> B <-> C <-> D <- list.back + // ^ + // cur + // + // + // And we want to produce this: + // + // list.front -> C <-> D <- list.back + // ^ + // cur + // + // + // return.front -> A <-> B <- return.back + // + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let prev = (*cur.as_ptr()).front; + + // What self will become + let new_len = old_len - old_idx; + let new_front = self.cur; + let new_back = self.list.back; + let new_idx = Some(0); + + // What the output will become + let output_len = old_len - new_len; + let output_front = self.list.front; + let output_back = prev; + + // Break the links between cur and prev + if let Some(prev) = prev { + (*cur.as_ptr()).front = None; + (*prev.as_ptr()).back = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.list.back = new_back; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } + } + + pub fn split_after(&mut self) -> LinkedList { + // We have this: + // + // list.front -> A <-> B <-> C <-> D <- list.back + // ^ + // cur + // + // + // And we want to produce this: + // + // list.front -> A <-> B <- list.back + // ^ + // cur + // + // + // return.front -> C <-> D <- return.back + // + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let next = (*cur.as_ptr()).back; + + // What self will become + let new_len = old_idx + 1; + let new_back = self.cur; + let new_front = self.list.front; + let new_idx = Some(old_idx); + + // What the output will become + let output_len = old_len - new_len; + let output_front = next; + let output_back = self.list.back; + + // Break the links between cur and next + if let Some(next) = next { + (*cur.as_ptr()).back = None; + (*next.as_ptr()).front = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.list.back = new_back; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } + } + + pub fn splice_before(&mut self, mut input: LinkedList) { + // We have this: + // + // input.front -> 1 <-> 2 <- input.back + // + // list.front -> A <-> B <-> C <- list.back + // ^ + // cur + // + // + // Becoming this: + // + // list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back + // ^ + // cur + // + unsafe { + // We can either `take` the input's pointers or `mem::forget` + // it. Using `take` is more responsible in case we ever do custom + // allocators or something that also needs to be cleaned up! + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(prev) = (*cur.as_ptr()).front { + // General Case, no boundaries, just internal fixups + (*prev.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(prev); + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + } else { + // No prev, we're appending to the front + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + self.list.front = Some(in_front); + } + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; + } else if let Some(back) = self.list.back { + // We're on the ghost but non-empty, append to the back + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*back.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(back); + self.list.back = Some(in_back); + } else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); + } + + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + + // Input dropped here + } + } + + pub fn splice_after(&mut self, mut input: LinkedList) { + // We have this: + // + // input.front -> 1 <-> 2 <- input.back + // + // list.front -> A <-> B <-> C <- list.back + // ^ + // cur + // + // + // Becoming this: + // + // list.front -> A <-> B <-> 1 <-> 2 <-> C <- list.back + // ^ + // cur + // + unsafe { + // We can either `take` the input's pointers or `mem::forget` + // it. Using `take` is more responsible in case we ever do custom + // allocators or something that also needs to be cleaned up! + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(next) = (*cur.as_ptr()).back { + // General Case, no boundaries, just internal fixups + (*next.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(next); + (*cur.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(cur); + } else { + // No next, we're appending to the back + (*cur.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(cur); + self.list.back = Some(in_back); + } + // Index doesn't change + } else if let Some(front) = self.list.front { + // We're on the ghost but non-empty, append to the front + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*front.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(front); + self.list.front = Some(in_front); + } else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); + } + + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + + // Input dropped here + } + } +} + +unsafe impl Send for LinkedList {} +unsafe impl Sync for LinkedList {} + +unsafe impl<'a, T: Send> Send for Iter<'a, T> {} +unsafe impl<'a, T: Sync> Sync for Iter<'a, T> {} + +unsafe impl<'a, T: Send> Send for IterMut<'a, T> {} +unsafe impl<'a, T: Sync> Sync for IterMut<'a, T> {} + +#[allow(dead_code)] +fn assert_properties() { + fn is_send() {} + fn is_sync() {} + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + fn linked_list_covariant<'a, T>(x: LinkedList<&'static T>) -> LinkedList<&'a T> { + x + } + fn iter_covariant<'i, 'a, T>(x: Iter<'i, &'static T>) -> Iter<'i, &'a T> { + x + } + fn into_iter_covariant<'a, T>(x: IntoIter<&'static T>) -> IntoIter<&'a T> { + x + } + + /// ```compile_fail,E0308 + /// use linked_list::IterMut; + /// + /// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x } + /// ``` + fn iter_mut_invariant() {} +} + +#[cfg(test)] +mod test { + use super::LinkedList; + + fn generate_test() -> LinkedList { + list_from(&[0, 1, 2, 3, 4, 5, 6]) + } + + fn list_from(v: &[T]) -> LinkedList { + v.iter().map(|x| (*x).clone()).collect() + } + + #[test] + fn test_basic_front() { + let mut list = LinkedList::new(); + + // Try to break an empty list + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Try to break a one item list + list.push_front(10); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Mess around + list.push_front(10); + assert_eq!(list.len(), 1); + list.push_front(20); + assert_eq!(list.len(), 2); + list.push_front(30); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(30)); + assert_eq!(list.len(), 2); + list.push_front(40); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(40)); + assert_eq!(list.len(), 2); + assert_eq!(list.pop_front(), Some(20)); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + } + + #[test] + fn test_basic() { + let mut m = LinkedList::new(); + assert_eq!(m.pop_front(), None); + assert_eq!(m.pop_back(), None); + assert_eq!(m.pop_front(), None); + m.push_front(1); + assert_eq!(m.pop_front(), Some(1)); + m.push_back(2); + m.push_back(3); + assert_eq!(m.len(), 2); + assert_eq!(m.pop_front(), Some(2)); + assert_eq!(m.pop_front(), Some(3)); + assert_eq!(m.len(), 0); + assert_eq!(m.pop_front(), None); + m.push_back(1); + m.push_back(3); + m.push_back(5); + m.push_back(7); + assert_eq!(m.pop_front(), Some(1)); + + let mut n = LinkedList::new(); + n.push_front(2); + n.push_front(3); + { + assert_eq!(n.front().unwrap(), &3); + let x = n.front_mut().unwrap(); + assert_eq!(*x, 3); + *x = 0; + } + { + assert_eq!(n.back().unwrap(), &2); + let y = n.back_mut().unwrap(); + assert_eq!(*y, 2); + *y = 1; + } + assert_eq!(n.pop_front(), Some(0)); + assert_eq!(n.pop_front(), Some(1)); + } + + #[test] + fn test_iterator() { + let m = generate_test(); + for (i, elt) in m.iter().enumerate() { + assert_eq!(i as i32, *elt); + } + let mut n = LinkedList::new(); + assert_eq!(n.iter().next(), None); + n.push_front(4); + let mut it = n.iter(); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next().unwrap(), &4); + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_iterator_double_end() { + let mut n = LinkedList::new(); + assert_eq!(n.iter().next(), None); + n.push_front(4); + n.push_front(5); + n.push_front(6); + let mut it = n.iter(); + assert_eq!(it.size_hint(), (3, Some(3))); + assert_eq!(it.next().unwrap(), &6); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(it.next_back().unwrap(), &4); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next_back().unwrap(), &5); + assert_eq!(it.next_back(), None); + assert_eq!(it.next(), None); + } + + #[test] + fn test_rev_iter() { + let m = generate_test(); + for (i, elt) in m.iter().rev().enumerate() { + assert_eq!(6 - i as i32, *elt); + } + let mut n = LinkedList::new(); + assert_eq!(n.iter().rev().next(), None); + n.push_front(4); + let mut it = n.iter().rev(); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next().unwrap(), &4); + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_mut_iter() { + let mut m = generate_test(); + let mut len = m.len(); + for (i, elt) in m.iter_mut().enumerate() { + assert_eq!(i as i32, *elt); + len -= 1; + } + assert_eq!(len, 0); + let mut n = LinkedList::new(); + assert!(n.iter_mut().next().is_none()); + n.push_front(4); + n.push_back(5); + let mut it = n.iter_mut(); + assert_eq!(it.size_hint(), (2, Some(2))); + assert!(it.next().is_some()); + assert!(it.next().is_some()); + assert_eq!(it.size_hint(), (0, Some(0))); + assert!(it.next().is_none()); + } + + #[test] + fn test_iterator_mut_double_end() { + let mut n = LinkedList::new(); + assert!(n.iter_mut().next_back().is_none()); + n.push_front(4); + n.push_front(5); + n.push_front(6); + let mut it = n.iter_mut(); + assert_eq!(it.size_hint(), (3, Some(3))); + assert_eq!(*it.next().unwrap(), 6); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(*it.next_back().unwrap(), 4); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(*it.next_back().unwrap(), 5); + assert!(it.next_back().is_none()); + assert!(it.next().is_none()); + } + + #[test] + fn test_eq() { + let mut n: LinkedList = list_from(&[]); + let mut m = list_from(&[]); + assert!(n == m); + n.push_front(1); + assert!(n != m); + m.push_back(1); + assert!(n == m); + + let n = list_from(&[2, 3, 4]); + let m = list_from(&[1, 2, 3]); + assert!(n != m); + } + + #[test] + fn test_ord() { + let n = list_from(&[]); + let m = list_from(&[1, 2, 3]); + assert!(n < m); + assert!(m > n); + assert!(n <= n); + assert!(n >= n); + } + + #[test] + fn test_ord_nan() { + let nan = 0.0f64 / 0.0; + let n = list_from(&[nan]); + let m = list_from(&[nan]); + assert!(!(n < m)); + assert!(!(n > m)); + assert!(!(n <= m)); + assert!(!(n >= m)); + + let n = list_from(&[nan]); + let one = list_from(&[1.0f64]); + assert!(!(n < one)); + assert!(!(n > one)); + assert!(!(n <= one)); + assert!(!(n >= one)); + + let u = list_from(&[1.0f64, 2.0, nan]); + let v = list_from(&[1.0f64, 2.0, 3.0]); + assert!(!(u < v)); + assert!(!(u > v)); + assert!(!(u <= v)); + assert!(!(u >= v)); + + let s = list_from(&[1.0f64, 2.0, 4.0, 2.0]); + let t = list_from(&[1.0f64, 2.0, 3.0, 2.0]); + assert!(!(s < t)); + assert!(s > one); + assert!(!(s <= one)); + assert!(s >= one); + } + + #[test] + fn test_debug() { + let list: LinkedList = (0..10).collect(); + assert_eq!(format!("{:?}", list), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"); + + let list: LinkedList<&str> = vec!["just", "one", "test", "more"] + .iter() + .copied() + .collect(); + assert_eq!(format!("{:?}", list), r#"["just", "one", "test", "more"]"#); + } + + #[test] + fn test_hashmap() { + // Check that HashMap works with this as a key + + let list1: LinkedList = (0..10).collect(); + let list2: LinkedList = (1..11).collect(); + let mut map = std::collections::HashMap::new(); + + assert_eq!(map.insert(list1.clone(), "list1"), None); + assert_eq!(map.insert(list2.clone(), "list2"), None); + + assert_eq!(map.len(), 2); + + assert_eq!(map.get(&list1), Some(&"list1")); + assert_eq!(map.get(&list2), Some(&"list2")); + + assert_eq!(map.remove(&list1), Some("list1")); + assert_eq!(map.remove(&list2), Some("list2")); + + assert!(map.is_empty()); + } + + #[test] + fn test_cursor_move_peek() { + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + assert_eq!(cursor.current(), Some(&mut 1)); + assert_eq!(cursor.peek_next(), Some(&mut 2)); + assert_eq!(cursor.peek_prev(), None); + assert_eq!(cursor.index(), Some(0)); + cursor.move_prev(); + assert_eq!(cursor.current(), None); + assert_eq!(cursor.peek_next(), Some(&mut 1)); + assert_eq!(cursor.peek_prev(), Some(&mut 6)); + assert_eq!(cursor.index(), None); + cursor.move_next(); + cursor.move_next(); + assert_eq!(cursor.current(), Some(&mut 2)); + assert_eq!(cursor.peek_next(), Some(&mut 3)); + assert_eq!(cursor.peek_prev(), Some(&mut 1)); + assert_eq!(cursor.index(), Some(1)); + + let mut cursor = m.cursor_mut(); + cursor.move_prev(); + assert_eq!(cursor.current(), Some(&mut 6)); + assert_eq!(cursor.peek_next(), None); + assert_eq!(cursor.peek_prev(), Some(&mut 5)); + assert_eq!(cursor.index(), Some(5)); + cursor.move_next(); + assert_eq!(cursor.current(), None); + assert_eq!(cursor.peek_next(), Some(&mut 1)); + assert_eq!(cursor.peek_prev(), Some(&mut 6)); + assert_eq!(cursor.index(), None); + cursor.move_prev(); + cursor.move_prev(); + assert_eq!(cursor.current(), Some(&mut 5)); + assert_eq!(cursor.peek_next(), Some(&mut 6)); + assert_eq!(cursor.peek_prev(), Some(&mut 4)); + assert_eq!(cursor.index(), Some(4)); + } + + #[test] + fn test_cursor_mut_insert() { + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.splice_before(Some(7).into_iter().collect()); + cursor.splice_after(Some(8).into_iter().collect()); + // check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[7, 1, 8, 2, 3, 4, 5, 6] + ); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + cursor.splice_before(Some(9).into_iter().collect()); + cursor.splice_after(Some(10).into_iter().collect()); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[10, 7, 1, 8, 2, 3, 4, 5, 6, 9] + ); + + /* remove_current not impl'd + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + assert_eq!(cursor.remove_current(), None); + cursor.move_next(); + cursor.move_next(); + assert_eq!(cursor.remove_current(), Some(7)); + cursor.move_prev(); + cursor.move_prev(); + cursor.move_prev(); + assert_eq!(cursor.remove_current(), Some(9)); + cursor.move_next(); + assert_eq!(cursor.remove_current(), Some(10)); + check_links(&m); + assert_eq!(m.iter().cloned().collect::>(), &[1, 8, 2, 3, 4, 5, 6]); + */ + + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 8, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + let mut p: LinkedList = LinkedList::new(); + p.extend([100, 101, 102, 103]); + let mut q: LinkedList = LinkedList::new(); + q.extend([200, 201, 202, 203]); + cursor.splice_after(p); + cursor.splice_before(q); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[200, 201, 202, 203, 1, 100, 101, 102, 103, 8, 2, 3, 4, 5, 6] + ); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + let tmp = cursor.split_before(); + assert_eq!(m.into_iter().collect::>(), &[]); + m = tmp; + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + let tmp = cursor.split_after(); + assert_eq!( + tmp.into_iter().collect::>(), + &[102, 103, 8, 2, 3, 4, 5, 6] + ); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[200, 201, 202, 203, 1, 100, 101] + ); + } + + fn check_links(list: &LinkedList) { + let from_front: Vec<_> = list.iter().collect(); + let from_back: Vec<_> = list.iter().rev().collect(); + let re_reved: Vec<_> = from_back.into_iter().rev().collect(); + + assert_eq!(from_front, re_reved); + } +} +``` diff --git a/src/too-many-lists/production-unsafe-deque/implementing-cursors.md b/src/too-many-lists/production-unsafe-deque/implementing-cursors.md new file mode 100644 index 00000000..fc1e2717 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/implementing-cursors.md @@ -0,0 +1,767 @@ +# Implementing Cursors + +好了,我们现在讨论 CursorMut。就像我最初的设计一样,它有一个包含 None 的 "幽灵 "元素,用来指示列表的开始/结束,你可以 "跨过它",绕到列表的另一边。要实现它,我们需要 + +- 指向当前节点的指针 +- 指向列表的指针 +- 当前索引 + +等等,当我们指向 "幽灵 "时,索引是多少? + +好吧,游标 (cursors)上的索引返回一个 `Option`,这很合理。Std 的实现做了一堆垃圾来避免将其存储为一个 Option,但是...... 我们是一个链接列表,这很好。此外,std 还有 cursor_front/cursor_back 功能,它可以在前/后元素上启动光标,感觉很直观,但当列表为空时,又要做一些奇怪的事情。 + +如果你愿意,也可以实现这些东西,但我打算减少所有重复的垃圾和角落情况,只做一个从 ghost 处开始的 cursor_mut 方法,人们可以使用 move_next/move_prev 来获取他们想要的元素(如果你真的愿意,也可以将其封装为 cursor_front)。 + +让我们开始吧: + +非常简单直接,上面的需求列表每一项都有一个字段! + +```rust +pub struct CursorMut<'a, T> { + cur: Link, + list: &'a mut LinkedList, + index: Option, +} +``` + +现在是`cursor_mut` 方法: + +```rust +impl LinkedList { + pub fn cursor_mut(&mut self) -> CursorMut { + CursorMut { + list: self, + cur: None, + index: None, + } + } +} +``` + +既然我们从幽灵节点开始,我们所以开始节点都是 `None`,简单明了!下一个是 `move_next`: + +```rust +impl<'a, T> CursorMut<'a, T> { + pub fn index(&self) -> Option { + self.index + } + + pub fn move_next(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its next (back) + self.cur = (*cur.as_ptr()).back; + if self.cur.is_some() { + *self.index.as_mut().unwrap() += 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real front, so move to it! + self.cur = self.list.front; + self.index = Some(0) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } +} +``` + +所以这有4种有趣的情况: + +- 正常情况 +- 正常情况,但我们移动到了幽灵节点 +- 幽灵节点开始,向列表头部节点移动 +- 幽灵节点开始,列表是空的,所以什么都不做 + +`move_prev` 的逻辑完全相同,但前后颠倒,索引变化也颠倒: + +```rust +pub fn move_prev(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its previous (front) + self.cur = (*cur.as_ptr()).front; + if self.cur.is_some() { + *self.index.as_mut().unwrap() -= 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real back, so move to it! + self.cur = self.list.back; + self.index = Some(self.list.len - 1) + } else { + // We're at the ghost, but that's the only element... do nothing. + } +} +``` + +接下来,让我们添加一些方法来查看游标周围的元素:`current`、`peek_next` 和 `peek_prev`。 **一个非常重要的注意事项**:这些方法必须通过 `&mut self` 借用我们的游标,并且结果必须与借用绑定。我们不能让用户获得可变引用的多个副本,也不能让他们在持有该引用的情况下使用我们的 insert/remove/split/splice API! + +值得庆幸的是,这是 rust 在使用生命周期省略规则时的默认设置,因此我们将默认做正确的事情! + +```rust +pub fn current(&mut self) -> Option<&mut T> { + unsafe { + self.cur.map(|node| &mut (*node.as_ptr()).elem) + } +} + +pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).back) + .map(|node| &mut (*node.as_ptr()).elem) + } +} + +pub fn peek_prev(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).front) + .map(|node| &mut (*node.as_ptr()).elem) + } +} +``` + +# [Split](https://rust-unofficial.github.io/too-many-lists/sixth-cursors-impl.html#split) + +首先是 split_before 和 split_after,它们会将当前元素之前/之后的所有内容以 LinkedList 的形式返回(在幽灵元素处停止,在这种情况下,我们只返回整个 List,光标现在指向一个空 list): + +这个逻辑其实并不复杂,所以我们得一步一步来。 + +我发现 split_before 有四种潜在的情况: + +- 正常情况 +- 正常情况,但 prev 是幽灵节点 +- 幽灵节点情况,我们返回整个列表,然后变成空列表 +- 幽灵节点情况,但列表是空的,所以什么也不做,返回空列表 + +让我们先从极端情况开始。我认为第三种情况 + +```rust +mem::replace(self.list, LinkedList::new()) +``` + +对不对?我们是空的了,并返回了整个列表,而我们的字段都应该是 "None",所以没什么可更新的。不错。哦,嘿嘿,这在第四种情况下也对! + +现在是普通情况......,我需要画下图。最常见的情况是这样的 + +```text +list.front -> A <-> B <-> C <-> D <- list.back + ^ + cur +``` + +我们想变成这样: + +```text +list.front -> C <-> D <- list.back + ^ + cur + +return.front -> A <-> B <- return.back +``` + +因此,我们需要打破当前数据和前一个数据之间的联系,而且......天哪,需要改变的东西太多了。好吧,我只需要把它分成几个步骤,这样我就能说服自己这是有意义的。虽然有点啰嗦,但我至少能说得通: + +```rust +pub fn split_before(&mut self) -> LinkedList { + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let prev = (*cur.as_ptr()).front; + + // What self will become + let new_len = old_len - old_idx; + let new_front = self.cur; + let new_idx = Some(0); + + // What the output will become + let output_len = old_len - new_len; + let output_front = self.list.front; + let output_back = prev; + + // Break the links between cur and prev + if let Some(prev) = prev { + (*cur.as_ptr()).front = None; + (*prev.as_ptr()).back = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } +} +``` + +你可能注意到,我们没有处理 prev 是幽灵节点的情况。但据我所知,其他一切都只是顺便做了正确的事。等我们写测试的时候就知道了!(复制粘贴完成 split_after)。 + +# [Splice](https://rust-unofficial.github.io/too-many-lists/sixth-cursors-impl.html#splice) + +还有一个老大难,那就是 splice_before 和 splice_after,我估计这是最容易出错的一个。这两个函数接收一个 LinkedList,并将其内容嫁接到我们的列表中。我们的列表可能是空的,他们的列表也可能是空的,我们还有幽灵节点要处理......叹口气,让我们一步一步来吧,从 splice_before 开始。 + +- 如果他们的列表是空的,我们就什么都不用做。 +- 如果我们的列表是空的,那么我们的列表就变成了他们的列表。 +- 如果我们指向的是幽灵节点,则追加到后面(更改 list.back) +- 如果我们指向的是第一个元素(0),则追加到前面(更改 list.front) +- 一般情况下,我们会进行大量的指针操作 + +一般情况: + +```text +input.front -> 1 <-> 2 <- input.back + + list.front -> A <-> B <-> C <- list.back + ^ + cur +``` + +变成这样: + +```text +list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back +``` + +好的,让我们来写一下: + +```rust + pub fn splice_before(&mut self, mut input: LinkedList) { + unsafe { + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + if let Some(0) = self.index { + // We're appending to the front, see append to back + (*cur.as_ptr()).front = input.back.take(); + (*input.back.unwrap().as_ptr()).back = Some(cur); + self.list.front = input.front.take(); + + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; + self.list.len += input.len; + input.len = 0; + } else { + // General Case, no boundaries, just internal fixups + let prev = (*cur.as_ptr()).front.unwrap(); + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*prev.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(prev); + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; + self.list.len += input.len; + input.len = 0; + } + } else if let Some(back) = self.list.back { + // We're on the ghost but non-empty, append to the back + // We can either `take` the input's pointers or `mem::forget` + // it. Using take is more responsible in case we do custom + // allocators or something that also needs to be cleaned up! + (*back.as_ptr()).back = input.front.take(); + (*input.front.unwrap().as_ptr()).front = Some(back); + self.list.back = input.back.take(); + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + } else { + // We're empty, become the input, remain on the ghost + *self.list = input; + } + } + } +``` + +好吧,这个程序真的很可怕,现在真的感觉到 Option 的痛苦了。但我们可以做很多清理工作。首先,我们可以把这段代码拖到最后。 + +```rust +self.list.len += input.len; +input.len = 0; +``` + +好了,现在在分支 "we're empty" 中有以下错误。所以我们应该使用 `swap`: + +> Use of moved value: `input` + +```rust +// We're empty, become the input, remain on the ghost +std::mem::swap(self.list, &mut input); +``` + +在我反向思考下面这种情况时,我发现了这个 `unwrap` 有问题(因为 cur 的 front 在前面已经被设置为其它值了): + +```rust +if let Some(0) = self.index { + +} else { + let prev = (*cur.as_ptr()).front.unwrap(); +} +``` + +这行也是重复的,可以提升: + +```rust +*self.index.as_mut().unwrap() += input.len; +``` + +好了,把上面的问题修改后得到这些: + +```rust +if input.is_empty() { + // Input is empty, do nothing. +} else if let Some(cur) = self.cur { + // Both lists are non-empty + if let Some(prev) = (*cur.as_ptr()).front { + // General Case, no boundaries, just internal fixups + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*prev.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(prev); + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + } else { + // We're appending to the front, see append to back below + (*cur.as_ptr()).front = input.back.take(); + (*input.back.unwrap().as_ptr()).back = Some(cur); + self.list.front = input.front.take(); + } + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; +} else if let Some(back) = self.list.back { + // We're on the ghost but non-empty, append to the back + // We can either `take` the input's pointers or `mem::forget` + // it. Using take is more responsible in case we do custom + // allocators or something that also needs to be cleaned up! + (*back.as_ptr()).back = input.front.take(); + (*input.front.unwrap().as_ptr()).front = Some(back); + self.list.back = input.back.take(); + +} else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); +} + +self.list.len += input.len; +// Not necessary but Polite To Do +input.len = 0; + +// Input dropped here +``` + +还是不对,下面的代码存在bug: + +```rust + (*back.as_ptr()).back = input.front.take(); + (*input.front.unwrap().as_ptr()).front = Some(back); +``` + +我们使用 `take` 拿走了 input.front 的值,然后在下一行将其 `unwrap`!boom,panic! + +```rust +// We can either `take` the input's pointers or `mem::forget` +// it. Using `take` is more responsible in case we ever do custom +// allocators or something that also needs to be cleaned up! +if input.is_empty() { + // Input is empty, do nothing. +} else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(prev) = (*cur.as_ptr()).front { + // General Case, no boundaries, just internal fixups + (*prev.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(prev); + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + } else { + // No prev, we're appending to the front + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + self.list.front = Some(in_front); + } + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; +} else if let Some(back) = self.list.back { + // We're on the ghost but non-empty, append to the back + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*back.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(back); + self.list.back = Some(in_back); +} else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); +} + +self.list.len += input.len; +// Not necessary but Polite To Do +input.len = 0; + +// Input dropped here +``` + +总之,我已经筋疲力尽了,所以 `insert` 和 `remove` 以及所有其他应用程序接口就留给读者练习。 +下面是 Cursor 的最终代码,我做对了吗?我只有在写下一章并测试这个怪东西时才能知道! + +```rust +pub struct CursorMut<'a, T> { + list: &'a mut LinkedList, + cur: Link, + index: Option, +} + +impl LinkedList { + pub fn cursor_mut(&mut self) -> CursorMut { + CursorMut { + list: self, + cur: None, + index: None, + } + } +} + +impl<'a, T> CursorMut<'a, T> { + pub fn index(&self) -> Option { + self.index + } + + pub fn move_next(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its next (back) + self.cur = (*cur.as_ptr()).back; + if self.cur.is_some() { + *self.index.as_mut().unwrap() += 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real front, so move to it! + self.cur = self.list.front; + self.index = Some(0) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } + + pub fn move_prev(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its previous (front) + self.cur = (*cur.as_ptr()).front; + if self.cur.is_some() { + *self.index.as_mut().unwrap() -= 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real back, so move to it! + self.cur = self.list.back; + self.index = Some(self.list.len - 1) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } + + pub fn current(&mut self) -> Option<&mut T> { + unsafe { + self.cur.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).back) + .map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn peek_prev(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).front) + .map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn split_before(&mut self) -> LinkedList { + // We have this: + // + // list.front -> A <-> B <-> C <-> D <- list.back + // ^ + // cur + // + // + // And we want to produce this: + // + // list.front -> C <-> D <- list.back + // ^ + // cur + // + // + // return.front -> A <-> B <- return.back + // + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let prev = (*cur.as_ptr()).front; + + // What self will become + let new_len = old_len - old_idx; + let new_front = self.cur; + let new_back = self.list.back; + let new_idx = Some(0); + + // What the output will become + let output_len = old_len - new_len; + let output_front = self.list.front; + let output_back = prev; + + // Break the links between cur and prev + if let Some(prev) = prev { + (*cur.as_ptr()).front = None; + (*prev.as_ptr()).back = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.list.back = new_back; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } + } + + pub fn split_after(&mut self) -> LinkedList { + // We have this: + // + // list.front -> A <-> B <-> C <-> D <- list.back + // ^ + // cur + // + // + // And we want to produce this: + // + // list.front -> A <-> B <- list.back + // ^ + // cur + // + // + // return.front -> C <-> D <- return.back + // + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let next = (*cur.as_ptr()).back; + + // What self will become + let new_len = old_idx + 1; + let new_back = self.cur; + let new_front = self.list.front; + let new_idx = Some(old_idx); + + // What the output will become + let output_len = old_len - new_len; + let output_front = next; + let output_back = self.list.back; + + // Break the links between cur and next + if let Some(next) = next { + (*cur.as_ptr()).back = None; + (*next.as_ptr()).front = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.list.back = new_back; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } + } + + pub fn splice_before(&mut self, mut input: LinkedList) { + // We have this: + // + // input.front -> 1 <-> 2 <- input.back + // + // list.front -> A <-> B <-> C <- list.back + // ^ + // cur + // + // + // Becoming this: + // + // list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back + // ^ + // cur + // + unsafe { + // We can either `take` the input's pointers or `mem::forget` + // it. Using `take` is more responsible in case we ever do custom + // allocators or something that also needs to be cleaned up! + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(prev) = (*cur.as_ptr()).front { + // General Case, no boundaries, just internal fixups + (*prev.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(prev); + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + } else { + // No prev, we're appending to the front + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + self.list.front = Some(in_front); + } + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; + } else if let Some(back) = self.list.back { + // We're on the ghost but non-empty, append to the back + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*back.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(back); + self.list.back = Some(in_back); + } else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); + } + + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + + // Input dropped here + } + } + + pub fn splice_after(&mut self, mut input: LinkedList) { + // We have this: + // + // input.front -> 1 <-> 2 <- input.back + // + // list.front -> A <-> B <-> C <- list.back + // ^ + // cur + // + // + // Becoming this: + // + // list.front -> A <-> B <-> 1 <-> 2 <-> C <- list.back + // ^ + // cur + // + unsafe { + // We can either `take` the input's pointers or `mem::forget` + // it. Using `take` is more responsible in case we ever do custom + // allocators or something that also needs to be cleaned up! + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(next) = (*cur.as_ptr()).back { + // General Case, no boundaries, just internal fixups + (*next.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(next); + (*cur.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(cur); + } else { + // No next, we're appending to the back + (*cur.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(cur); + self.list.back = Some(in_back); + } + // Index doesn't change + } else if let Some(front) = self.list.front { + // We're on the ghost but non-empty, append to the front + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*front.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(front); + self.list.front = Some(in_front); + } else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); + } + + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + + // Input dropped here + } + } +} +``` diff --git a/src/too-many-lists/production-unsafe-deque/intro.md b/src/too-many-lists/production-unsafe-deque/intro.md new file mode 100644 index 00000000..413f48ef --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/intro.md @@ -0,0 +1,9 @@ +# A Production-Quality Unsafe Doubly-Linked Deque + +我们终于成功了。我最大的克星:**[std::collections::LinkedList](https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/linked_list.rs),双向链接的 Deque**。 + +我尝试过但未能击败的那个。 + +来吧,我将向你展示你需要知道的一切,帮助我一劳永逸地摧毁它--实现一个 **unsafe** 的生产质量双向链接 Deque 所需要知道的一切。 + +我们将彻底重写我那古老的 Rust 1.0 linked-list crate,那个 linked-list 客观上比 std 要好,它从 2015 年开始,就存在 Cursors (游标,后面文章会介绍)!而标准库2022年了还没有的东西! diff --git a/src/too-many-lists/production-unsafe-deque/layout.md b/src/too-many-lists/production-unsafe-deque/layout.md new file mode 100644 index 00000000..e531e280 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/layout.md @@ -0,0 +1,86 @@ +# 数据布局 + +首先,让我们来研究一下敌人的结构。双向链接列表在概念上很简单,但它就是这样欺骗和操纵你的。这是我们反复研究过的同一种链接列表,但链接是双向的。双倍链接,双倍邪恶。 + +相比于单向(删掉了 Some/None 这类东西以保持简洁): + +```text +... -> (A, ptr) -> (B, ptr) -> ... +``` + +我们需要这个: + +```text +... <-> (ptr, A, ptr) <-> (ptr, B, ptr) <-> ... +``` + +这使你可以从任一方向遍历列表,或使用[cursor(游标)](https://doc.rust-lang.org/std/collections/struct.LinkedList.html#method.cursor_back_mut)来回查找。 + +为了换取这种灵活性,每个节点必须存储两倍的指针,并且每个操作都必须修复更多的指针。这是一个足够复杂的问题,更容易犯错,所以我们将做大量的测试。 + +你可能也注意到了,我故意没有画出列表的两端。这正是我们下面的方案中要实现的对方。我们的实现肯定需要两个指针:一个指向列表的起点,另一个指向列表的终点。。 + +在我看来,有两种值得注意的方法可以做到这一点:“传统节点”和“虚拟节点”。 + +传统的方法是对堆栈的简单扩展——只需将头部和尾部指针存储在堆栈上: + +```text +[ptr, ptr] <-> (ptr, A, ptr) <-> (ptr, B, ptr) + ^ ^ + +----------------------------------------+ +``` + +这很好,但它有一个缺点:极端情况。现在我们的列表有两个边缘,这意味着极端情况的数量增加了一倍。很容易忘记一个并有一个严重的错误。 + +虚拟节点方法试图通过在我们的列表中添加一个额外的节点来消除这些极端情况,该节点不包含任何数据,但将两端链接成一个环: + +```text +[ptr] -> (ptr, ?DUMMY?, ptr) <-> (ptr, A, ptr) <-> (ptr, B, ptr) + ^ ^ + +-------------------------------------------------+ +``` + +通过执行此操作,每个节点*始终*具有指向列表中上一个和下一个节点的实际指针。即使你从列表中删除了最后一个元素,你最终也只是拼接了虚拟节点以指向它自己: + +```text +[ptr] -> (ptr, ?DUMMY?, ptr) + ^ ^ + +-------------+ +``` + +一定程度上这非常令人满意和优雅。不幸的是,它有几个实际问题: + +问题 1:额外的间接和分配,尤其是对于必须包含虚拟节点的空列表。可能的解决方案包括: + +- 在插入某些内容之前不要分配虚拟节点:简单而有效,但它会添加一些我们试图通过使用虚拟指针来避免的极端情况! +- 使用静态的 "copy-on-write" 单例虚拟节点,并采用一些非常巧妙的方案,让 "copy-on-write" 检查捎带上正常检查:看,我真的很想,我真的很喜欢这种东西,但我们不能在这本书中走那条路。如果你想看到那种变态的东西,请阅读 [ThinVec 的源代码](https://docs.rs/thin-vec/0.2.4/src/thin_vec/lib.rs.html#319-325)。 +- 将虚拟节点存储在栈上 - 这在没有 C++ 风格的移动构造函数的语言中并不实用。我敢肯定,我们可以在这里用[pinning](https://doc.rust-lang.org/std/pin/index.html)做一些奇怪的事情,但我们不会这样做。 + +问题 2:虚拟节点中存储了什么*值*?当然,如果它是一个整数,那很好,但如果我们存储的是一个满是 Box 的列表呢?我们可能无法初始化这个值!可能的解决方案包括: + +- 让每个节点存储`Option`:简单有效,但也臃肿烦人。 +- 使每个节点都存储 [`MaybeUninit`](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html)。可怕又烦人。 +- 虚拟节点不包含数据字段。这也很诱人,但非常危险和烦人。如果你想看到那种的东西,请阅读 [BTreeMap 的来源](https://doc.rust-lang.org/1.55.0/src/alloc/collections/btree/node.rs.html#49-104)。 + +对于像 Rust 这样的语言来说,这些虚拟节点方案的问题确实超过了便利性,所以我们将坚持传统的布局。我们将使用与上一章中对不安全队列相同的基本设计: + +```rust +#![allow(unused)] +fn main() { +pub struct LinkedList { + front: Link, + back: Link, + len: usize, +} + +type Link = *mut Node; + +struct Node { + front: Link, + back: Link, + elem: T, +} +} +``` + +这还不是一个*真正的*生产质量的布局。不过还不错。我们可以使用一些魔法技巧来告诉 Rust 我们可以做得更好一些。要做到这一点,我们需要 ... 更加深入。 diff --git a/src/too-many-lists/production-unsafe-deque/send-sync-and-compile-tests.md b/src/too-many-lists/production-unsafe-deque/send-sync-and-compile-tests.md new file mode 100644 index 00000000..3d7e8271 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/send-sync-and-compile-tests.md @@ -0,0 +1,216 @@ +# Send, Sync, and Compile Tests + +好吧,其实我们还有一对特征需要考虑,但它们很特别。我们必须对付 Rust 的神圣罗马帝国: unsafe 的 Opt-in Built-out 特征(OIBITs): Send 和 Sync,它们实际上是(opt-out)和(built-out)(3 个中有 1 个已经很不错了!)。 + +与 Copy 一样,这些特征完全没有相关代码,只是标记您的类型具有特定属性。Send 表示你的类型可以安全地发送到另一个线程。Sync 表示你的类型可以在线程间安全共享(&Self: Send)。 + +关于 LinkedList *covariant(协变的)* 论点在这里同样适用:一般来说,不使用花哨的内部可变技巧的普通集合可以安全地进行 Send 和 Sync。 + +But I said they're *opt out*. So actually, are we already? How would we know? + +让我们在代码中添加一些新的魔法:随机的私有垃圾,除非我们的类型具有我们所期望的属性,否则将无法编译: + +```rust +#[allow(dead_code)] +fn assert_properties() { + fn is_send() {} + fn is_sync() {} + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + fn linked_list_covariant<'a, T>(x: LinkedList<&'static T>) -> LinkedList<&'a T> { x } + fn iter_covariant<'i, 'a, T>(x: Iter<'i, &'static T>) -> Iter<'i, &'a T> { x } + fn into_iter_covariant<'a, T>(x: IntoIter<&'static T>) -> IntoIter<&'a T> { x } +} +cargo build + Compiling linked-list v0.0.3 +error[E0277]: `NonNull>` cannot be sent between threads safely + --> src\lib.rs:433:5 + | +433 | is_send::>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `NonNull>` cannot be sent between threads safely + | + = help: within `LinkedList`, the trait `Send` is not implemented for `NonNull>` + = note: required because it appears within the type `Option>>` +note: required because it appears within the type `LinkedList` + --> src\lib.rs:8:12 + | +8 | pub struct LinkedList { + | ^^^^^^^^^^ +note: required by a bound in `is_send` + --> src\lib.rs:430:19 + | +430 | fn is_send() {} + | ^^^^ required by this bound in `is_send` + + +``` + +我骗你说原始指针只有一个安全保护:这是另一个。 `*const` 和 `*mut` explicitly opt out of Send and Sync to be safe, so we do *actually* have to opt back in: + +```rust +unsafe impl Send for LinkedList {} +unsafe impl Sync for LinkedList {} + +unsafe impl<'a, T: Send> Send for Iter<'a, T> {} +unsafe impl<'a, T: Sync> Sync for Iter<'a, T> {} + +unsafe impl<'a, T: Send> Send for IterMut<'a, T> {} +unsafe impl<'a, T: Sync> Sync for IterMut<'a, T> {} +``` + +请注意,我们必须在这里编写不安全的 impl:这些是不安全的特征!不安全代码(如并发库)只能依靠我们正确地实现这些特征!由于没有实际代码,我们所做的保证只是:是的,我们在线程间发送或共享确实是安全的! + +别以为这些都是随便说说的,我可是经过认证的专业人士,我在这里要说:是的,这些都是完全没问题的。请注意,我们并不需要为 IntoIter 实现 Send 和 Sync:它只是包含 LinkedList,所以会自动生成 Send 和 Sync--我告诉过你它们实际上是 opt out! + +```text +cargo build + Compiling linked-list v0.0.3 + Finished dev [unoptimized + debuginfo] target(s) in 0.18s +``` + +很好 + +IterMut 绝对不应该是协变的,因为它 "就像" `&mut T`。 + +用魔术!其实是用 rustdoc!好吧,我们不一定要使用 rustdoc,但这是最有趣的用法。你看,如果你写了一个 doccomment 并包含了一个代码块,那么 rustdoc 就会尝试编译并运行它,所以我们可以用它来创建新的匿名 "程序",而这些程序不会影响主程序: + +```rust + /// ``` + /// use linked_list::IterMut; + /// + /// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x } + /// ``` + fn iter_mut_invariant() {} +cargo test + +... + + Doc-tests linked-list + +running 1 test +test src\lib.rs - assert_properties::iter_mut_invariant (line 458) ... FAILED + +failures: + +---- src\lib.rs - assert_properties::iter_mut_invariant (line 458) stdout ---- +error[E0308]: mismatched types + --> src\lib.rs:461:86 + | +6 | fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x } + | ^ lifetime mismatch + | + = note: expected struct `linked_list::IterMut<'_, &'a T>` + found struct `linked_list::IterMut<'_, &'static T>` +``` + +好吧,我们已经证明了它是不变的,但现在我们的测试失败了。不用担心,rustdoc 会让你在栅栏上注释 compile_fail,说明这是意料之中的! + +(实际上,我们只证明了它 "不是*covariant(协变的)*",但老实说,如果你能让一个类型 "意外地、错误地*contravariant(逆变的)* ",那么,恭喜你。) + +```rust + /// ```compile_fail + /// use linked_list::IterMut; + /// + /// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x } + /// ``` + fn iter_mut_invariant() {} +cargo test + Compiling linked-list v0.0.3 + Finished test [unoptimized + debuginfo] target(s) in 0.49s + Running unittests src\lib.rs + +... + + Doc-tests linked-list + +running 1 test +test src\lib.rs - assert_properties::iter_mut_invariant (line 458) - compile fail ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s +``` + +是的!我建议在进行测试时不要使用 compile_fail,这样你可以看到错误是不是和你预期的一致。例如,你忘记了使用 use 关键字,这是错误的,但因为 compile_fail通过了测试。如果不使用compile_fail,测试会因为没有使用 use 失败,这不是我们想要的, 我们想要的是:测试因为 `mut` 是*covariant(协变的)*的而失败! + +(哦,等等,我们其实可以在 compile_fail 旁边指定我们想要的错误代码,但这只适用于 nightly,而且由于上述原因,依赖它是个坏主意。在 not-nightly 版本运行时,它将被默默忽略)。 + +```rust + /// ```compile_fail,E0308 + /// use linked_list::IterMut; + /// + /// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x } + /// ``` + fn iter_mut_invariant() {} +``` + +......还有,你注意到我们实际上把 IterMut 变成*invariant(不变的)*的那部分了吗?这很容易被忽略,因为我 "只是 "复制粘贴了 Iter 并把它放在了最后。这是最后一行: + +```rust +pub struct IterMut<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a mut T>, +} +``` + +我们试着去掉 PhantomData: + +```text + cargo build + Compiling linked-list v0.0.3 (C:\Users\ninte\dev\contain\linked-list) +error[E0392]: parameter `'a` is never used + --> src\lib.rs:30:20 + | +30 | pub struct IterMut<'a, T> { + | ^^ unused parameter + | + = help: consider removing `'a`, referring to it in a field, or using a marker such as `PhantomData` +``` + +哈!编译器在背后支持我们,提示我们未使用的 lifetime。让我们试着用一个错误的例子来代替: + +```rust + _boo: PhantomData<&'a T>, +cargo build + Compiling linked-list v0.0.3 (C:\Users\ninte\dev\contain\linked-list) + Finished dev [unoptimized + debuginfo] target(s) in 0.17s +``` + +它可以构建!我们的测试可以发现问题吗? + +```text +cargo test + +... + + Doc-tests linked-list + +running 1 test +test src\lib.rs - assert_properties::iter_mut_invariant (line 458) - compile fail ... FAILED + +failures: + +---- src\lib.rs - assert_properties::iter_mut_invariant (line 458) stdout ---- +Test compiled successfully, but it's marked `compile_fail`. + +failures: + src\lib.rs - assert_properties::iter_mut_invariant (line 458) + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s +``` + +Eyyy!!.!这个系统真管用!我喜欢那些能真正完成任务的测试,这样我就不必为那些若隐若现的错误而感到恐惧了! diff --git a/src/too-many-lists/production-unsafe-deque/testing-cursors.md b/src/too-many-lists/production-unsafe-deque/testing-cursors.md new file mode 100644 index 00000000..4a11cdd7 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/testing-cursors.md @@ -0,0 +1,426 @@ +# Testing Cursors + +是时候找出我在上一节中犯了多少令人尴尬的错误了! + +哦,天哪,我们的 API 既不像标准版,也不像旧版。好吧,那我打算从这两个地方拼凑一些东西吧。是的,让我们 "借用 " 标准版中的这些测试: + +```rust + #[test] + fn test_cursor_move_peek() { + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + assert_eq!(cursor.current(), Some(&mut 1)); + assert_eq!(cursor.peek_next(), Some(&mut 2)); + assert_eq!(cursor.peek_prev(), None); + assert_eq!(cursor.index(), Some(0)); + cursor.move_prev(); + assert_eq!(cursor.current(), None); + assert_eq!(cursor.peek_next(), Some(&mut 1)); + assert_eq!(cursor.peek_prev(), Some(&mut 6)); + assert_eq!(cursor.index(), None); + cursor.move_next(); + cursor.move_next(); + assert_eq!(cursor.current(), Some(&mut 2)); + assert_eq!(cursor.peek_next(), Some(&mut 3)); + assert_eq!(cursor.peek_prev(), Some(&mut 1)); + assert_eq!(cursor.index(), Some(1)); + + let mut cursor = m.cursor_mut(); + cursor.move_prev(); + assert_eq!(cursor.current(), Some(&mut 6)); + assert_eq!(cursor.peek_next(), None); + assert_eq!(cursor.peek_prev(), Some(&mut 5)); + assert_eq!(cursor.index(), Some(5)); + cursor.move_next(); + assert_eq!(cursor.current(), None); + assert_eq!(cursor.peek_next(), Some(&mut 1)); + assert_eq!(cursor.peek_prev(), Some(&mut 6)); + assert_eq!(cursor.index(), None); + cursor.move_prev(); + cursor.move_prev(); + assert_eq!(cursor.current(), Some(&mut 5)); + assert_eq!(cursor.peek_next(), Some(&mut 6)); + assert_eq!(cursor.peek_prev(), Some(&mut 4)); + assert_eq!(cursor.index(), Some(4)); + } + + #[test] + fn test_cursor_mut_insert() { + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.splice_before(Some(7).into_iter().collect()); + cursor.splice_after(Some(8).into_iter().collect()); + // check_links(&m); + assert_eq!(m.iter().cloned().collect::>(), &[7, 1, 8, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + cursor.splice_before(Some(9).into_iter().collect()); + cursor.splice_after(Some(10).into_iter().collect()); + check_links(&m); + assert_eq!(m.iter().cloned().collect::>(), &[10, 7, 1, 8, 2, 3, 4, 5, 6, 9]); + + /* remove_current not impl'd + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + assert_eq!(cursor.remove_current(), None); + cursor.move_next(); + cursor.move_next(); + assert_eq!(cursor.remove_current(), Some(7)); + cursor.move_prev(); + cursor.move_prev(); + cursor.move_prev(); + assert_eq!(cursor.remove_current(), Some(9)); + cursor.move_next(); + assert_eq!(cursor.remove_current(), Some(10)); + check_links(&m); + assert_eq!(m.iter().cloned().collect::>(), &[1, 8, 2, 3, 4, 5, 6]); + */ + + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 8, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + let mut p: LinkedList = LinkedList::new(); + p.extend([100, 101, 102, 103]); + let mut q: LinkedList = LinkedList::new(); + q.extend([200, 201, 202, 203]); + cursor.splice_after(p); + cursor.splice_before(q); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[200, 201, 202, 203, 1, 100, 101, 102, 103, 8, 2, 3, 4, 5, 6] + ); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + let tmp = cursor.split_before(); + assert_eq!(m.into_iter().collect::>(), &[]); + m = tmp; + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + let tmp = cursor.split_after(); + assert_eq!(tmp.into_iter().collect::>(), &[102, 103, 8, 2, 3, 4, 5, 6]); + check_links(&m); + assert_eq!(m.iter().cloned().collect::>(), &[200, 201, 202, 203, 1, 100, 101]); + } + + fn check_links(_list: &LinkedList) { + // would be good to do this! + } +``` + +见证奇迹的时候! + +```text +cargo test + + Compiling linked-list v0.0.3 + Finished test [unoptimized + debuginfo] target(s) in 1.03s + Running unittests src\lib.rs + +running 14 tests +test test::test_basic_front ... ok +test test::test_basic ... ok +test test::test_debug ... ok +test test::test_iterator_mut_double_end ... ok +test test::test_ord ... ok +test test::test_cursor_move_peek ... FAILED +test test::test_cursor_mut_insert ... FAILED +test test::test_iterator ... ok +test test::test_mut_iter ... ok +test test::test_eq ... ok +test test::test_rev_iter ... ok +test test::test_iterator_double_end ... ok +test test::test_hashmap ... ok +test test::test_ord_nan ... ok + +failures: + +---- test::test_cursor_move_peek stdout ---- +thread 'test::test_cursor_move_peek' panicked at 'assertion failed: `(left == right)` + left: `None`, + right: `Some(1)`', src\lib.rs:1079:9 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +---- test::test_cursor_mut_insert stdout ---- +thread 'test::test_cursor_mut_insert' panicked at 'assertion failed: `(left == right)` + left: `[200, 201, 202, 203, 10, 100, 101, 102, 103, 7, 1, 8, 2, 3, 4, 5, 6, 9]`, + right: `[200, 201, 202, 203, 1, 100, 101, 102, 103, 8, 2, 3, 4, 5, 6]`', src\lib.rs:1153:9 + + +failures: + test::test_cursor_move_peek + test::test_cursor_mut_insert + +test result: FAILED. 12 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +我得承认,我在这里有些自负,希望自己能成功。这就是我们写测试的原因(但也许我只是在移植测试时做得不好?) + +第一次失败是什么? + +```rust +let mut m: LinkedList = LinkedList::new(); +m.extend([1, 2, 3, 4, 5, 6]); +let mut cursor = m.cursor_mut(); + +cursor.move_next(); +assert_eq!(cursor.current(), Some(&mut 1)); +assert_eq!(cursor.peek_next(), Some(&mut 2)); +assert_eq!(cursor.peek_prev(), None); +assert_eq!(cursor.index(), Some(0)); + +cursor.move_prev(); +assert_eq!(cursor.current(), None); +assert_eq!(cursor.peek_next(), Some(&mut 1)); // DIES HERE +``` + +```rust +pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).back) + .map(|node| &mut (*node.as_ptr()).elem) + } +} +``` + +就是这错了。如果 `self.cur` 是 None, 我们不应该就这样放弃,我们还需要检查 self.list.front,因为我们在幽灵节点上!因此,我们只需在链中添加一个 or_else: + +```rust +pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).back) + .or_else(|| self.list.front) + .map(|node| &mut (*node.as_ptr()).elem) + } +} + +pub fn peek_prev(&mut self) -> Option<&mut T> { + unsafe { + self.cur + .and_then(|node| (*node.as_ptr()).front) + .or_else(|| self.list.back) + .map(|node| &mut (*node.as_ptr()).elem) + } +} +``` + +这样就修复好了吗? + +```text +---- test::test_cursor_move_peek stdout ---- +thread 'test::test_cursor_move_peek' panicked at 'assertion failed: `(left == right)` + left: `Some(6)`, + right: `None`', src\lib.rs:1078:9 +``` + +又错了。好吧,显然这比我想象的要难得多。盲目地把这些情况串联起来简直是一场灾难,让我们对幽灵节点与非幽灵节点的情况做不同的判断吧: + +```rust +pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + let next = if let Some(cur) = self.cur { + // Normal case, try to follow the cur node's back pointer + (*cur.as_ptr()).back + } else { + // Ghost case, try to use the list's front pointer + self.list.front + }; + + // Yield the element if the next node exists + next.map(|node| &mut (*node.as_ptr()).elem) + } +} + +pub fn peek_prev(&mut self) -> Option<&mut T> { + unsafe { + let prev = if let Some(cur) = self.cur { + // Normal case, try to follow the cur node's front pointer + (*cur.as_ptr()).front + } else { + // Ghost case, try to use the list's back pointer + self.list.back + }; + + // Yield the element if the prev node exists + prev.map(|node| &mut (*node.as_ptr()).elem) + } +} +``` + +我对这一次充满信心! + +```rust + + cargo test + Compiling linked-list v0.0.3 + Finished test [unoptimized + debuginfo] target(s) in 0.70s + Running unittests src\lib.rs + +running 14 tests +test test::test_basic_front ... ok +test test::test_basic ... ok +test test::test_cursor_move_peek ... ok +test test::test_eq ... ok +test test::test_cursor_mut_insert ... ok +test test::test_iterator ... ok +test test::test_iterator_double_end ... ok +test test::test_ord_nan ... ok +test test::test_mut_iter ... ok +test test::test_hashmap ... ok +test test::test_debug ... ok +test test::test_ord ... ok +test test::test_iterator_mut_double_end ... ok +test test::test_rev_iter ... ok + +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests linked-list + +running 1 test +test src\lib.rs - assert_properties::iter_mut_invariant (line 803) - compile fail ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s +``` + +嘿嘿,看看这个......好吧,现在我开始疑神疑鬼了。让我们正确填写 check_links,并在 miri 下进行测试: + +```rust +fn check_links(list: &LinkedList) { + let from_front: Vec<_> = list.iter().collect(); + let from_back: Vec<_> = list.iter().rev().collect(); + let re_reved: Vec<_> = from_back.into_iter().rev().collect(); + + assert_eq!(from_front, re_reved); +} +``` + +```text +$env:MIRIFLAGS="-Zmiri-tag-raw-pointers" +cargo miri test + Compiling linked-list v0.0.3 + Finished test [unoptimized + debuginfo] target(s) in 0.25s + Running unittests src\lib.rs + +running 14 tests +test test::test_basic ... ok +test test::test_basic_front ... ok +test test::test_cursor_move_peek ... ok +test test::test_cursor_mut_insert ... ok +test test::test_debug ... ok +test test::test_eq ... ok +test test::test_hashmap ... ok +test test::test_iterator ... ok +test test::test_iterator_double_end ... ok +test test::test_iterator_mut_double_end ... ok +test test::test_mut_iter ... ok +test test::test_ord ... ok +test test::test_ord_nan ... ok +test test::test_rev_iter ... ok + +test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out + + Doc-tests linked-list + +running 1 test +test src\lib.rs - assert_properties::iter_mut_invariant (line 803) - compile fail ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s +``` + +完成。 + +我们成功了 我们做出了一个具有生产质量的 LinkedList,其功能与 std 中的 LinkedList 基本相同。我们是否在这里或那里缺少了一些小的便利方法?当然有。我会把它们添加到最终发布的版本中吗?也许会! + +但是,我已经非常累了。 + +所以。我们赢了 + +等等 我们正在生产质量。好吧,最后一个步骤: clippy。 + +```text +cargo clippy + +cargo clippy + Checking linked-list v0.0.3 (C:\Users\ninte\dev\contain\linked-list) +warning: redundant pattern matching, consider using `is_some()` + --> src\lib.rs:189:19 + | +189 | while let Some(_) = self.pop_front() { } + | ----------^^^^^^^------------------- help: try this: `while self.pop_front().is_some()` + | + = note: `#[warn(clippy::redundant_pattern_matching)]` on by default + = note: this will change drop order of the result, as well as all temporaries + = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching + +warning: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter` + --> src\lib.rs:210:5 + | +210 | / pub fn into_iter(self) -> IntoIter { +211 | | IntoIter { +212 | | list: self +213 | | } +214 | | } + | |_____^ + | + = note: `#[warn(clippy::should_implement_trait)]` on by default + = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait + +warning: redundant pattern matching, consider using `is_some()` + --> src\lib.rs:228:19 + | +228 | while let Some(_) = self.pop_front() { } + | ----------^^^^^^^------------------- help: try this: `while self.pop_front().is_some()` + | + = note: this will change drop order of the result, as well as all temporaries + = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching + +warning: re-implementing `PartialEq::ne` is unnecessary + --> src\lib.rs:275:5 + | +275 | / fn ne(&self, other: &Self) -> bool { +276 | | self.len() != other.len() || self.iter().ne(other) +277 | | } + | |_____^ + | + = note: `#[warn(clippy::partialeq_ne_impl)]` on by default + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl + +warning: `linked-list` (lib) generated 4 warnings + Finished dev [unoptimized + debuginfo] target(s) in 0.29s +``` + +好的 clippy, 按照你的要求修改。 + +再来一次: + +```text +cargo clippy + Finished dev [unoptimized + debuginfo] target(s) in 0.00s +``` + +太棒了,称为生产品质的最后一件事: fmt. + +```text +cargo fmt +``` + +**我们现在终于真正的完成啦!!!!!!!!!!!!!!!!!!!!!** diff --git a/src/too-many-lists/production-unsafe-deque/testing.md b/src/too-many-lists/production-unsafe-deque/testing.md new file mode 100644 index 00000000..b7926768 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/testing.md @@ -0,0 +1,328 @@ +# Testing + +好吧,我推迟了一段时间测试,因为我们都知道,我们现在是 Rust 的主人,不会再犯错了!另外,这是对一个旧 crate 的重写,所以我已经有了所有的测试。你已经看过很多测试了。它们就在这里: + +```rust +#[cfg(test)] +mod test { + use super::LinkedList; + + fn generate_test() -> LinkedList { + list_from(&[0, 1, 2, 3, 4, 5, 6]) + } + + fn list_from(v: &[T]) -> LinkedList { + v.iter().map(|x| (*x).clone()).collect() + } + + #[test] + fn test_basic_front() { + let mut list = LinkedList::new(); + + // Try to break an empty list + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Try to break a one item list + list.push_front(10); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Mess around + list.push_front(10); + assert_eq!(list.len(), 1); + list.push_front(20); + assert_eq!(list.len(), 2); + list.push_front(30); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(30)); + assert_eq!(list.len(), 2); + list.push_front(40); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(40)); + assert_eq!(list.len(), 2); + assert_eq!(list.pop_front(), Some(20)); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + } + + #[test] + fn test_basic() { + let mut m = LinkedList::new(); + assert_eq!(m.pop_front(), None); + assert_eq!(m.pop_back(), None); + assert_eq!(m.pop_front(), None); + m.push_front(1); + assert_eq!(m.pop_front(), Some(1)); + m.push_back(2); + m.push_back(3); + assert_eq!(m.len(), 2); + assert_eq!(m.pop_front(), Some(2)); + assert_eq!(m.pop_front(), Some(3)); + assert_eq!(m.len(), 0); + assert_eq!(m.pop_front(), None); + m.push_back(1); + m.push_back(3); + m.push_back(5); + m.push_back(7); + assert_eq!(m.pop_front(), Some(1)); + + let mut n = LinkedList::new(); + n.push_front(2); + n.push_front(3); + { + assert_eq!(n.front().unwrap(), &3); + let x = n.front_mut().unwrap(); + assert_eq!(*x, 3); + *x = 0; + } + { + assert_eq!(n.back().unwrap(), &2); + let y = n.back_mut().unwrap(); + assert_eq!(*y, 2); + *y = 1; + } + assert_eq!(n.pop_front(), Some(0)); + assert_eq!(n.pop_front(), Some(1)); + } + + #[test] + fn test_iterator() { + let m = generate_test(); + for (i, elt) in m.iter().enumerate() { + assert_eq!(i as i32, *elt); + } + let mut n = LinkedList::new(); + assert_eq!(n.iter().next(), None); + n.push_front(4); + let mut it = n.iter(); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next().unwrap(), &4); + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_iterator_double_end() { + let mut n = LinkedList::new(); + assert_eq!(n.iter().next(), None); + n.push_front(4); + n.push_front(5); + n.push_front(6); + let mut it = n.iter(); + assert_eq!(it.size_hint(), (3, Some(3))); + assert_eq!(it.next().unwrap(), &6); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(it.next_back().unwrap(), &4); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next_back().unwrap(), &5); + assert_eq!(it.next_back(), None); + assert_eq!(it.next(), None); + } + + #[test] + fn test_rev_iter() { + let m = generate_test(); + for (i, elt) in m.iter().rev().enumerate() { + assert_eq!(6 - i as i32, *elt); + } + let mut n = LinkedList::new(); + assert_eq!(n.iter().rev().next(), None); + n.push_front(4); + let mut it = n.iter().rev(); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next().unwrap(), &4); + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_mut_iter() { + let mut m = generate_test(); + let mut len = m.len(); + for (i, elt) in m.iter_mut().enumerate() { + assert_eq!(i as i32, *elt); + len -= 1; + } + assert_eq!(len, 0); + let mut n = LinkedList::new(); + assert!(n.iter_mut().next().is_none()); + n.push_front(4); + n.push_back(5); + let mut it = n.iter_mut(); + assert_eq!(it.size_hint(), (2, Some(2))); + assert!(it.next().is_some()); + assert!(it.next().is_some()); + assert_eq!(it.size_hint(), (0, Some(0))); + assert!(it.next().is_none()); + } + + #[test] + fn test_iterator_mut_double_end() { + let mut n = LinkedList::new(); + assert!(n.iter_mut().next_back().is_none()); + n.push_front(4); + n.push_front(5); + n.push_front(6); + let mut it = n.iter_mut(); + assert_eq!(it.size_hint(), (3, Some(3))); + assert_eq!(*it.next().unwrap(), 6); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(*it.next_back().unwrap(), 4); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(*it.next_back().unwrap(), 5); + assert!(it.next_back().is_none()); + assert!(it.next().is_none()); + } + + #[test] + fn test_eq() { + let mut n: LinkedList = list_from(&[]); + let mut m = list_from(&[]); + assert!(n == m); + n.push_front(1); + assert!(n != m); + m.push_back(1); + assert!(n == m); + + let n = list_from(&[2, 3, 4]); + let m = list_from(&[1, 2, 3]); + assert!(n != m); + } + + #[test] + fn test_ord() { + let n = list_from(&[]); + let m = list_from(&[1, 2, 3]); + assert!(n < m); + assert!(m > n); + assert!(n <= n); + assert!(n >= n); + } + + #[test] + fn test_ord_nan() { + let nan = 0.0f64 / 0.0; + let n = list_from(&[nan]); + let m = list_from(&[nan]); + assert!(!(n < m)); + assert!(!(n > m)); + assert!(!(n <= m)); + assert!(!(n >= m)); + + let n = list_from(&[nan]); + let one = list_from(&[1.0f64]); + assert!(!(n < one)); + assert!(!(n > one)); + assert!(!(n <= one)); + assert!(!(n >= one)); + + let u = list_from(&[1.0f64, 2.0, nan]); + let v = list_from(&[1.0f64, 2.0, 3.0]); + assert!(!(u < v)); + assert!(!(u > v)); + assert!(!(u <= v)); + assert!(!(u >= v)); + + let s = list_from(&[1.0f64, 2.0, 4.0, 2.0]); + let t = list_from(&[1.0f64, 2.0, 3.0, 2.0]); + assert!(!(s < t)); + assert!(s > one); + assert!(!(s <= one)); + assert!(s >= one); + } + + #[test] + fn test_debug() { + let list: LinkedList = (0..10).collect(); + assert_eq!(format!("{:?}", list), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"); + + let list: LinkedList<&str> = vec!["just", "one", "test", "more"] + .iter().copied() + .collect(); + assert_eq!(format!("{:?}", list), r#"["just", "one", "test", "more"]"#); + } + + #[test] + fn test_hashmap() { + // Check that HashMap works with this as a key + + let list1: LinkedList = (0..10).collect(); + let list2: LinkedList = (1..11).collect(); + let mut map = std::collections::HashMap::new(); + + assert_eq!(map.insert(list1.clone(), "list1"), None); + assert_eq!(map.insert(list2.clone(), "list2"), None); + + assert_eq!(map.len(), 2); + + assert_eq!(map.get(&list1), Some(&"list1")); + assert_eq!(map.get(&list2), Some(&"list2")); + + assert_eq!(map.remove(&list1), Some("list1")); + assert_eq!(map.remove(&list2), Some("list2")); + + assert!(map.is_empty()); + } +} +``` + +现在是关键时刻: + +```text +cargo test + Finished test [unoptimized + debuginfo] target(s) in 0.00s + Running unittests src\lib.rs + +running 12 tests +test test::test_basic ... ok +test test::test_basic_front ... ok +test test::test_eq ... ok +test test::test_iterator ... ok +test test::test_iterator_mut_double_end ... ok +test test::test_ord_nan ... ok +test test::test_iterator_double_end ... ok +test test::test_mut_iter ... ok +test test::test_rev_iter ... ok +test test::test_hashmap ... ok +test test::test_ord ... ok +test test::test_debug ... ok + +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +$env:MIRIFLAGS="-Zmiri-tag-raw-pointers" +cargo miri test + Compiling linked-list v0.0.3 + Finished test [unoptimized + debuginfo] target(s) in 0.35s + Running unittests src\lib.rs + +running 12 tests +test test::test_basic ... ok +test test::test_basic_front ... ok +test test::test_debug ... ok +test test::test_eq ... ok +test test::test_hashmap ... ok +test test::test_iterator ... ok +test test::test_iterator_double_end ... ok +test test::test_iterator_mut_double_end ... ok +test test::test_mut_iter ... ok +test test::test_ord ... ok +test test::test_ord_nan ... ok +test test::test_rev_iter ... ok + +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +``` + +😭 + +我们做到了,我们真的没有搞砸。这不是小把戏!我们所有的练习和训练终于值得了!我们终于写出了好代码 + +现在,我们可以回到 "有趣的事情 "上来了! diff --git a/src/too-many-lists/production-unsafe-deque/variance-and-phantomData.md b/src/too-many-lists/production-unsafe-deque/variance-and-phantomData.md new file mode 100644 index 00000000..40133f32 --- /dev/null +++ b/src/too-many-lists/production-unsafe-deque/variance-and-phantomData.md @@ -0,0 +1,186 @@ +# Variance and PhantomData + +如果现在不做,等以后再修,会很麻烦,所以我们现在要做的是硬核布局。 + +建造 Rust collections 时,有这五个可怕的难题: + +1. [Variance](https://doc.rust-lang.org/nightly/nomicon/subtyping.html) +2. [Drop Check](https://doc.rust-lang.org/nightly/nomicon/dropck.html) +3. [NonNull Optimizations](https://doc.rust-lang.org/nightly/std/ptr/struct.NonNull.html) +4. [The isize::MAX Allocation Rule](https://doc.rust-lang.org/nightly/nomicon/vec/vec-alloc.html) +5. [Zero-Sized Types](https://doc.rust-lang.org/nightly/nomicon/vec/vec-zsts.html) + +幸好,后面2个对我们来说都不是问题。 + +我们可以把第三个问题变成我们的问题,但这带来的麻烦比它的价值更多。 + +第二个问题是我以前一直坚持认为非常重要的,std 也会乱用它,但默认值是安全的,而且你需要非常努力才能注意到默认值的限制,所以不用担心这个问题。 + +所以只剩下了 Variance(型变)。 + +Rust 有子类型了。通常,`&'big T` 是 `&'small T` 的子类型。因为如果某些代码需要在程序的某个特定区域存活的引用,那么通常完全可以给它一个存在*时间更长的*引用。直觉上这是正确的,对吧? + +为什么这很重要?想象一下,一些代码采用两个具有相同类型的值: + +```rust +fn take_two(_val1: T, _val2: T) { } +``` + +这是一些非常无聊的代码,并且我们期望它能够很好地与 T=&u32 一起使用,对吧? + +```rust +fn two_refs<'big: 'small, 'small>( + big: &'big u32, + small: &'small u32, +) { + take_two(big, small); +} + +fn take_two(_val1: T, _val2: T) { } +``` + +是的,编译得很好! + +现在让我们找点乐子,把它包起来:`std::cell::Cell` + +```rust +use std::cell::Cell; + +fn two_refs<'big: 'small, 'small>( + // NOTE: these two lines changed + big: Cell<&'big u32>, + small: Cell<&'small u32>, +) { + take_two(big, small); +} + +fn take_two(_val1: T, _val2: T) { } +error[E0623]: lifetime mismatch + --> src/main.rs:7:19 + | +4 | big: Cell<&'big u32>, + | --------- +5 | small: Cell<&'small u32>, + | ----------- these two types are declared with different lifetimes... +6 | ) { +7 | take_two(big, small); + | ^^^^^ ...but data from `small` flows into `big` here +``` + +哼???我们没有碰过生命周期,为什么编译器现在生气了!? + +啊,好吧,生命周期的“子类型”必须非常简单,所以如果你将引用包装在任何东西中,它就会被破坏,看看 Vec: + +```rust +fn two_refs<'big: 'small, 'small>( + big: Vec<&'big u32>, + small: Vec<&'small u32>, +) { + take_two(big, small); +} + +fn take_two(_val1: T, _val2: T) { } + Finished dev [unoptimized + debuginfo] target(s) in 1.07s + Running `target/debug/playground` +``` + +看到它没有编译成功 ——等等???Vec是魔术?????? + +是的。这种魔力就是✨*Variance*✨。 + +如果您想要所有细节,请阅读 [nomicon 关于子类型的章节](https://doc.rust-lang.org/nightly/nomicon/subtyping.html),但基本上子类型*并不总是*安全的。特别是,当涉及可变引用时,它就更不安全了,。因为你可能会使用诸如`mem::swap`的东西,突然哎呀,悬空指针! + +可变引用是 *invariant(不变的)*,这意味着它们会阻止对泛型参数子类型化。因此,为了安全起见, `&mut T` 在 T 上是不变的,并且 `Cell` 在 T 上也是不变的(因为内部可变性),因为 `&Cell` 本质上就像 `&mut T`。 + +几乎所有不是 *invariant* 的东西都是 *covariant(协变的)* ,这意味着子类型可以正常工作(也有 *contravariant(逆变的)* 的类型使子类型倒退,但它们真的很少见,没有人喜欢它们,所以我不会再提到它们)。 + +集合通常包含指向其数据的可变指针,因此你可能希望它们也是不变的,但事实上,它们并不需要不变!由于 Rust 的所有权系统,`Vec` 在语义上等同于 `T`,这意味着它可以安全地保持*covariant(协变的)* ! + +不幸的的是,下面的定义是 *invariant(不变的)*: + +```rust +pub struct LinkedList { + front: Link, + back: Link, + len: usize, +} + +type Link = *mut Node; + +struct Node { + front: Link, + back: Link, + elem: T, +} +``` + +所以我们的类型定义中哪里惹 Rust 编译器不高兴了? `*mut`! + +Rust 中的裸指针其实就是让你可以做任何事情,但它们只有一个安全特性:因为大多数人都不知道 Rust 中还有 *Variance(型变)* 和子类型,而错误地使用 *covariant(协变的)* 会非常危险,所以 `*mut T` 是*invariant(不变的)*,因为它很有可能被 "作为" `&mut T` 使用。 + +作为一个花了大量时间在 Rust 中编写集合的人,这让我感到厌烦。这就是为什么我在制作 [std::ptr::NonNull](https://doc.rust-lang.org/std/ptr/struct.NonNull.html), 时添加了这个小魔法: + +> 与 *mut T 不同,NonNull 在 T 上是 *covariant(协变的)*。这使得使用 NonNull 构建*covariant(协变的)*类型成为可能,但如果在不应该是 *covariant(协变的)* 的地方中使用,则会带来不健全的风险。 + +这是一个围绕着 `*mut T` 构建的类型。真的是魔法吗?让我们来看一下: + +```rust +pub struct NonNull { + pointer: *const T, +} + + +impl NonNull { + pub unsafe fn new_unchecked(ptr: *mut T) -> Self { + // SAFETY: the caller must guarantee that `ptr` is non-null. + unsafe { NonNull { pointer: ptr as *const T } } + } +} +``` + +不,这里没有魔法!NonNull 只是滥用了 *const T 是 *covariant(协变的)* 这一事实,并将其存储起来。这就是 Rust 中集合的协变方式!这可真是惨不忍睹!所以我为你做了这个 Good Pointer Type !不客气好好享受子类型吧 + +解决你所有问题的办法就是使用 NonNull,然后如果你想再次使用可空指针,就使用 Option>。我们真的要这么做吗? + +是的!这很糟糕,但我们要做的是生产级的链表,所以我们要吃尽千辛万苦(我们可以直接使用裸*const T,然后在任何地方都进行转换,但我真的想看看这样做有多痛苦......为了人体工程学科学)。 + +下面就是我们最终的类型定义: + +```rust +use std::ptr::NonNull; + +// !!!This changed!!! +pub struct LinkedList { + front: Link, + back: Link, + len: usize, +} + +type Link = Option>>; + +struct Node { + front: Link, + back: Link, + elem: T, +} +``` + +...等等,不,最后一件事。每当你使用裸指针时,你都应该添加一个 Ghost 来保护你的指针: + +```rust +use std::marker::PhantomData; + +pub struct LinkedList { + front: Link, + back: Link, + len: usize, + /// We semantically store values of T by-value. + _boo: PhantomData, +} +``` + +在这种情况下,我认为我们*实际上*不需要 [PhantomData](https://doc.rust-lang.org/std/marker/struct.PhantomData.html),但每当你使用 NonNull(或一般的裸指针)时,为了安全起见,你都应该始终添加它,并向编译器和其他人清楚地表明你的想法,你在做什么。 + +PhantomData 是我们给编译器提供一个额外的 "示例 "字段的方法,这个字段在概念上存在于你的类型中,但由于各种原因(间接、类型擦除......)并不存在。在本例中,我们使用 NonNull 是因为我们声称我们的类型 "好像 "存储了一个值 T,所以我们添加了一个 PhantomData 来明确这一点。 + +...好吧,我们现在已经完成了布局!进入实际的基本功能!