You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was doing a bad safe deque and it really hit me that we cannot even have iterators. I spent some time and came up with an ok-ish safe deque variant with iterators and fewer implementation detail leaks. Here's the list of changes:
re-declare Link to have RefCell on top to have immutable nodes with mutable next/prev pointers (as a downside, Link becomes 16 bytes each)
change the bad safe deque's code accordingly by mostly swapping borrow() and next/prev member access
use custom Ref wrapper with cloned Rc to Node, it also allows to hold to a single Ref with pop_*() panicking
provide lifetime with variance for Ref by using PhantomData => allows for List borrowing by an iterator
implement double-ended iterator by checking Node's address to identify the same (last) node during iteration
as a net result, the API is more or less clean, no internals exposed, basic functionality works correctly, no unsafe code
some other minor tweaks include revised Drop and IntoIterator trait for List
And here's the code:
use std::cell::RefCell;use std::marker::PhantomData;use std::ops::Deref;use std::rc::Rc;pubstructList<T>{head:Link<T>,tail:Link<T>,}typeLink<T> = RefCell<Option<Rc<Node<T>>>>;pubstructRef<'a,T>{node:Rc<Node<T>>,phantom:PhantomData<&'aT>,}impl<T>DerefforRef<'_,T>{typeTarget = T;#[inline]fnderef(&self) -> &T{&self.node.elem}}structNode<T>{elem:T,next:Link<T>,prev:Link<T>,}impl<T>Node<T>{fnnew(elem:T) -> Rc<Self>{Rc::new(Node{
elem,next:RefCell::new(None),prev:RefCell::new(None),})}}impl<T>List<T>{pubfnnew() -> Self{List{head:RefCell::new(None),tail:RefCell::new(None),}}pubfnpush_front(&mutself,elem:T){let new_head = Node::new(elem);matchself.head.take(){Some(old_head) => {*old_head.prev.borrow_mut() = Some(new_head.clone());*new_head.next.borrow_mut() = Some(old_head);self.head.replace(Some(new_head));}None => {self.tail.replace(Some(new_head.clone()));self.head.replace(Some(new_head));}}}pubfnpush_back(&mutself,elem:T){let new_tail = Node::new(elem);matchself.tail.take(){Some(old_tail) => {*old_tail.next.borrow_mut() = Some(new_tail.clone());*new_tail.prev.borrow_mut() = Some(old_tail);self.tail.replace(Some(new_tail));}None => {self.head.replace(Some(new_tail.clone()));self.tail.replace(Some(new_tail));}}}pubfnpop_front(&mutself) -> Option<T>{self.head.take().map(|old_head| {match old_head.next.borrow_mut().take(){Some(new_head) => {
new_head.prev.borrow_mut().take();self.head.replace(Some(new_head));}None => {// Empty list, clean up the tail as well.self.tail.take();}}Rc::try_unwrap(old_head).ok().unwrap().elem})}pubfnpop_back(&mutself) -> Option<T>{self.tail.take().map(|old_tail| {match old_tail.prev.borrow_mut().take(){Some(new_tail) => {
new_tail.next.borrow_mut().take();self.tail.replace(Some(new_tail));}None => {// Empty list, clean up the head as well.self.head.take();}}Rc::try_unwrap(old_tail).ok().unwrap().elem})}pubfnpeek_front(&self) -> Option<Ref<T>>{self.head.borrow().as_ref().map(|node| Ref{node: node.clone(),phantom:PhantomData,})}pubfnpeek_back(&self) -> Option<Ref<T>>{self.tail.borrow().as_ref().map(|node| Ref{node: node.clone(),phantom:PhantomData,})}pubfniter(&self) -> Iter<'_,T>{Iter{next:self.peek_front(),prev:self.peek_back(),}}}impl<T>DropforList<T>{fndrop(&mutself){letmut count = 0usize;// Drop forward links.letmut cur_head = self.head.take();whileletSome(node) = cur_head {
cur_head = node.next.borrow_mut().take();
count += 1;}// Drop backward links.letmut cur_tail = self.tail.take();whileletSome(node) = cur_tail {
cur_tail = node.prev.borrow_mut().take();
count -= 1;}// Running both ways is equal.assert_eq!(count,0);}}impl<T>DefaultforList<T>{fndefault() -> Self{Self::new()}}pubstructIntoIter<T>(List<T>);impl<T>IteratorforIntoIter<T>{typeItem = T;fnnext(&mutself) -> Option<Self::Item>{self.0.pop_front()}}impl<T>DoubleEndedIteratorforIntoIter<T>{fnnext_back(&mutself) -> Option<Self::Item>{self.0.pop_back()}}impl<T>IntoIteratorforList<T>{typeItem = T;typeIntoIter = IntoIter<Self::Item>;fninto_iter(self) -> Self::IntoIter{IntoIter(self)}}pubstructIter<'a,T>{next:Option<Ref<'a,T>>,prev:Option<Ref<'a,T>>,}impl<'a,T>IteratorforIter<'a,T>{typeItem = Ref<'a,T>;fnnext(&mutself) -> Option<Self::Item>{self.next.take().map(|node_ref| {// We're on the same node, so it's the last one.ifRc::as_ptr(&node_ref.node) == Rc::as_ptr(&self.prev.as_ref().unwrap().node){// Clean up the other end.self.prev.take();}else{self.next = node_ref.node.next.borrow().as_ref().map(|next| Ref{node: next.clone(),phantom:PhantomData,});}
node_ref
})}}impl<T>DoubleEndedIteratorforIter<'_,T>{fnnext_back(&mutself) -> Option<Self::Item>{self.prev.take().map(|node_ref| {// We're on the same node, so it's the last one.ifRc::as_ptr(&node_ref.node) == Rc::as_ptr(&self.next.as_ref().unwrap().node){// Clean up the other end.self.next.take();}else{self.prev = node_ref.node.prev.borrow().as_ref().map(|prev| Ref{node: prev.clone(),phantom:PhantomData,});}
node_ref
})}}#[cfg(test)]mod tests {usesuper::List;#[test]fnsmoke(){letmut l = List::new();assert_eq!(l.pop_front(),None);
l.push_front(1);
l.push_front(2);
l.push_front(3);assert_eq!(l.pop_front(),Some(3));assert_eq!(l.pop_front(),Some(2));
l.push_front(4);
l.push_front(5);assert_eq!(l.pop_front(),Some(5));assert_eq!(l.pop_front(),Some(4));assert_eq!(l.pop_front(),Some(1));assert_eq!(l.pop_front(),None);assert_eq!(l.pop_back(),None);
l.push_back(1);
l.push_back(2);
l.push_back(3);assert_eq!(l.pop_back(),Some(3));assert_eq!(l.pop_back(),Some(2));
l.push_back(4);
l.push_back(5);assert_eq!(l.pop_back(),Some(5));assert_eq!(l.pop_back(),Some(4));assert_eq!(l.pop_back(),Some(1));assert_eq!(l.pop_back(),None);}#[test]fnpeek(){letmut l = List::new();assert!(l.peek_front().is_none());assert!(l.peek_back().is_none());
l.push_front(1);
l.push_front(2);
l.push_front(3);assert_eq!(&*l.peek_front().unwrap(),&3);assert_eq!(&*l.peek_back().unwrap(),&1);}#[test]fninto_iter(){letmut l = List::new();
l.push_front(1);
l.push_front(2);
l.push_front(3);letmut iter = l.into_iter();assert_eq!(iter.next(),Some(3));assert_eq!(iter.next_back(),Some(1));assert_eq!(iter.next(),Some(2));assert_eq!(iter.next_back(),None);assert_eq!(iter.next(),None);}#[test]fnfor_iter(){letmut l = List::new();
l.push_front(1);
l.push_front(1);
l.push_front(1);for v in l {assert_eq!(v,1);}}#[test]fniter(){letmut l = List::new();// Empty list case.letmut iter = l.iter();assert_eq!(iter.next().as_deref(),None);assert_eq!(iter.next_back().as_deref(),None);
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.push_back(5);
l.push_back(6);letmut iter = l.iter();assert_eq!(iter.next().as_deref(),Some(&1));assert_eq!(iter.next_back().as_deref(),Some(&6));assert_eq!(iter.next_back().as_deref(),Some(&5));assert_eq!(iter.next().as_deref(),Some(&2));assert_eq!(iter.next().as_deref(),Some(&3));assert_eq!(iter.next_back().as_deref(),Some(&4));assert_eq!(iter.next().as_deref(),None);assert_eq!(iter.next_back().as_deref(),None);}}
This variant may become a separate chapter with some new concepts described (Deref, PhantomData etc).
If you are Ok with adding such a chapter to the book, I could prepare a draft as a PR.
What do you think?
The text was updated successfully, but these errors were encountered:
First of all, thanks for the great book!
I was doing a bad safe deque and it really hit me that we cannot even have iterators. I spent some time and came up with an ok-ish safe deque variant with iterators and fewer implementation detail leaks. Here's the list of changes:
Link
to haveRefCell
on top to have immutable nodes with mutablenext/prev
pointers (as a downside,Link
becomes 16 bytes each)borrow()
andnext/prev
member accessRef
wrapper with clonedRc
toNode
, it also allows to hold to a singleRef
withpop_*()
panickingRef
by usingPhantomData
=> allows forList
borrowing by an iteratorNode
's address to identify the same (last) node during iterationDrop
andIntoIterator
trait forList
And here's the code:
This variant may become a separate chapter with some new concepts described (
Deref
,PhantomData
etc).If you are Ok with adding such a chapter to the book, I could prepare a draft as a PR.
What do you think?
The text was updated successfully, but these errors were encountered: