adlist structure

adlist(A generic doubly linked list):

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    unsigned long len;
} list;

从结构来看adlist比较简单,就一个常规的双向链表,不过实现时增删节点还是要谨慎,很容易漏掉更新前一节点的next指针或后一节点的prev指针,也容易忽略头节点或尾节点要特殊对待。

adlist和rust标准库里的LinkedList很像,LinkedList也是双向链表,LinkedList支持泛型,数据内容通常由LinkedList持有所有权:

// rust 1.50
pub struct LinkedList<T> {
    head: Option<NonNull<Node<T>>>,
    tail: Option<NonNull<Node<T>>>,
    len: usize,
    marker: PhantomData<Box<Node<T>>>,
}

struct Node<T> {
    next: Option<NonNull<Node<T>>>,
    prev: Option<NonNull<Node<T>>>,
    element: T,
}

不过rust版本的adlist只是对着redis的版本来实现,节点通过裸指针连在一起,节点数据也定义为泛型:

pub struct List<T: Copy + PartialEq> {
    head: *const Node<T>,
    tail: *const Node<T>,
    len: usize,
    value_clone: Option<fn(T)->T>,
    value_drop: Option<fn(T)>,
    value_equals: Option<fn(T, T)->bool>,
}

pub struct Node<T: Copy + PartialEq> {
    prev: *const Node<T>,
    next: *const Node<T>,
    pub value: T,
}

不用 Option<NonNull> 主要考虑操纵裸指针更加直观,也不用Option包裹裸指针,Option包裹裸指针后占用内存大小为两倍指针大小(指针可null,无法对None优化),节点数据类型用一个Copy(用于复制)和PartialEq(用于相等判断)约束,这样不用考虑Drop的问题,也不用考虑所有权,缺点是放复杂的数据只能用裸指针(如果只是临时存放,用借用也可以),value字段类型是T而不是*T,因为指针也是类型的一种,T也能表达指针类型。

创建列表:

    // same as
    // list *listCreate(void)
    pub fn new() -> Self {
        let list = Self {
            head: null(),
            tail: null(),
            len: 0,
            value_clone: None,
            value_drop: None,
            value_equals: None,
        };

        list
    }

List直接在栈上创建,这和C版本不一样,当然,后面在嵌套使用时如果不方便可能会再做修改。

在列表头或尾添加元素:

    pub fn push_front(&mut self, value: T) -> &mut Self {
        let node = unsafe { z_malloc_of_type::<Node<T>>() as *mut Node<T> };
        if node.is_null() {
            panic!("z_malloc_of_type fail");
        }

        let node = unsafe { &mut *node };
        node.value = value;
        node.prev = null();

        if self.len == 0 {
            self.head = node;
            self.tail = node;
            node.next = null();
        } else {
            node.next = self.head;
            unsafe { (*(self.head as *mut Node<T>)).prev = node; }
            self.head = node;
        }
        self.len += 1;
        self
    }

    pub fn push_back(&mut self, value: T) -> &mut Self {
        let node = unsafe { z_malloc_of_type::<Node<T>>() as *mut Node<T> };
        if node.is_null() {
            panic!("z_malloc_of_type fail");
        }

        let node = unsafe { &mut *node };
        node.value = value;
        if self.len == 0 {
            self.head = node;
            self.tail = node;
            node.prev = null();
            node.next = null();
        } else {
            node.prev = self.tail;
            node.next = null();
            unsafe { (*(self.tail as *mut Node<T>)).next = node; }
            self.tail = node;
        }
        self.len += 1;
        self
    }

unsafe rust写起来比C麻烦得多;可以看出列表头的prev和列表尾的next指向null,即列表并非循环列表。

查找元素:

    // same as
    // listNode *listSearchKey(list *list, void *key)
    pub fn search(&self, value: T) -> *const Node<T> {
        for n in self.iter() {
            unsafe {
                if let Some(value_equals) = self.value_equals {
                    if value_equals((*n).value, value) {
                        return n;
                    }
                } else if (*n).value == value {
                    return n;
                }
            }
        }

        null()
    }
    
    pub fn iter(&self) -> It<T> {
        It{next: self.head, direction: ItDirection::HeadToTail}
    }

利用迭代器来遍历列表查找元素,迭代器实现和C版本完全一样,只是rust可以用for来迭代。

impl<T: Copy + PartialEq> Iterator for It<T> {
    type Item = *const Node<T>;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.next;
        if current.is_null() {
            return None;
        }

        match self.direction {
            ItDirection::HeadToTail => {
                unsafe { self.next = (*current).next; }
            }
            ItDirection::TailToHead => {
                unsafe { self.next = (*current).prev; }
            }
        }

        Some(current)
    }
}

删除节点:

    // same as
    // void listDelNode(list *list, listNode *node)
    pub unsafe fn remove(&mut self, node: *mut Node<T>) {
        let node = &mut *node;
        // if prev is null, it is the head node
        if node.prev.is_null() {
            self.head = node.next;
        } else {
            (*(node.prev as *mut Node<T>)).next = node.next;
        }

        // if next is null, it is the tail node
        if node.next.is_null() {
            self.tail = node.prev;
        } else {
            (*(node.next as *mut Node<T>)).prev = node.prev;
        }

        if let Some(value_drop) = self.value_drop {
            value_drop(node.value);
        }

        z_free(node as *mut Node<T> as *const u8);
        self.len -= 1;
    }

清空列表:

    // same as
    // void listEmpty(list *list)
    // unsafe cuz free_value
    pub unsafe fn clear(&mut self) {
        let len = self.len;
        let mut current = self.head;
        for _ in 0..len {
            let next = (*current).next;
            if let Some(value_drop) = self.value_drop {
                value_drop((*current).value);
            }
            z_free(current as *const u8);
            current = next;
        }

        self.head = null();
        self.tail = null();
        self.len = 0;
    }

销毁列表很简单,因为List是在栈上分配的,无需手动释放List占用的内存,只需要销毁列表节点即可,实现Drop让rust自动销毁:

impl<T: Copy + PartialEq> Drop for List<T> {
    // same as
    // void listRelease(list *list)
    fn drop(&mut self) {
        unsafe { self.clear(); }
    }
}

最终示例代码:

#[test]
fn test_basic() {
    let mut list = List::new();
    assert!(list.is_empty());

    list.push_front(1);
    assert!(!list.is_empty());
    unsafe {
        assert_eq!((*list.first()).value, 1);
    }

    list.push_back(2);
    unsafe {
        assert_eq!((*list.last()).value, 2);
        assert_eq!(list.len(), 2);
    }

    let elements: Vec<_> = list.iter()
        .map(|n| unsafe{(*n).value})
        .collect();
    assert_eq!(elements.as_slice(), &[1, 2]);

    unsafe {
        assert_eq!((*list.get(0)).value, 1);
        assert_eq!((*list.get(-1)).value, 2);
        assert!(list.get(2).is_null());
        assert!(list.get(-3).is_null());
    }

    list.move_head_to_tail();
    unsafe {
        assert_eq!((*list.first()).value, 2);
        assert_eq!((*list.last()).value, 1);
    }

    list.move_tail_to_head();
    unsafe {
        assert_eq!((*list.first()).value, 1);
        assert_eq!((*list.last()).value, 2);
    }

    let mut other = list.clone();
    other.move_tail_to_head();
    list.push_back(3).append(&mut other);
    assert!(other.is_empty());

    let elements: Vec<_> = list.iter()
        .map(|n| unsafe{(*n).value})
        .collect();
    assert_eq!(elements.as_slice(), &[1, 2, 3, 2, 1]);

    unsafe { list.remove(list.search(3) as *mut Node<_>); }
    let elements: Vec<_> = list.iter()
        .map(|n| unsafe{(*n).value})
        .collect();
    assert_eq!(elements.as_slice(), &[1, 2, 2, 1]);

    list.move_head_to_tail();
    let elements: Vec<_> = list.rev_iter()
        .map(|n| unsafe{(*n).value})
        .collect();
    assert_eq!(elements.as_slice(), &[1, 1, 2, 2]);

    unsafe {
        list.insert_node(list.first() as *mut Node<_>, 3, false);
        list.insert_node(list.last() as *mut Node<_>, 3, true);
    }
    let elements: Vec<_> = list.rev_iter()
        .map(|n| unsafe{(*n).value})
        .collect();
    assert_eq!(elements.as_slice(), &[3, 1, 1, 2, 2, 3]);
}

rust并不建议使用LinkedList,因为对缓存不友好,不过链表还是有其优势的,比如删除节点的复杂度很低。

总结:对外暴露Node容易导致Node被多次释放内存或Node内存被释放后还在使用,此外Node没有记录挂在哪个List的,难免会误将属于A列表的节点传入B列表去删除;操作裸指针非常繁琐。总的来说就是“unrusty“,后面根据其他模块的使用场景来修改。

完整代码 https://github.com/iiibui/redis-rust-copy/blob/main/src/ad_list.rs

https://mp.weixin.qq.com/s?__biz=MzIxNzE5NDUyNQ==&mid=2247483689&idx=1&sn=744348c9b897147b67681aaac58b6a69