手搓一个简单的链表

已经学习rust一周了,是时候尝试一下链表了

按照TDD的规范,先写第一个测试:

//lib.rs
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_list_new(){
        let list : List <i32> = List::new();
        assert!(list.isempty())
    }
}

第一个细节是 use super::*,rust项目在一个crate里的组织是以mod为单元的,也就是说虽然tests模块写在了lib.rs,它和直接写在lib.rs里的函数仍然是分离的。use super::*表示把父模块的全部方法引入进当前模块

接下来定义List结构体,完成new和isempty方法

//lib.rs
pub struct List <T>{
    head : Option<Box<Node<T>>>,
}

impl<T> List<T>{
    pub fn new() -> List<T>{
        List{head : None,}
    }
    pub fn isempty(&self) -> bool{
        self.head.is_none()
    }
}

Option<Box<Node<T>>>,好冗长的东西。head应该是一个指向链表首地址的指针,而链表的本体要放在堆上。Box用来封装到堆上,类型是,元素为T类型的节点

type Link <T> = Option<Box<Node<T> >>;

所以补充这一行简化代码

另一个细节是impl<T> List<T>这里有两个< T>,第一个是声明这里要用到一个泛型模板T,第二个则是跟着每个List出现的类型说明

该写下一个测试了,关于pop和push方法,仅在头部进出元素:

/*other tests*/
    #[test]
    fn test_list_push_and_pop(){
        let mut list : List <i32> = List::new();
        list.push(3);
        list.push(336699);
        assert_eq!(list.pop(),Some(336699));
        assert_eq!(list.pop(),Some(3));
    }

如下:

/*existing code*/
    pub fn push(&mut self,val : T) {
        let new_node = Node{val : val,next : self.head.take()};
        self.head = Some(Box::new(new_node));
    }
    
    pub fn pop(&mut self) -> Option<T>{
        self.head.take().map(|node| {
            self.head = node.next;
            node.val
        })
    }

Option类型的take方法,可以把其中的值取出来。map方法,对取出来的值进行一个闭包形式的操作。这里take出来一个作为参数node,给self重新赋值

以上逻辑看着很理所当然,实际上却没有那么简单。试想如果写成:

pub fn pop(&mut self) -> Option<T> {
    if let Some(node) = self.head {
        self.head = node.next;
        Some(node.val)
    } else {
        None
    }
}

编译器就会提醒self是不可移动的,因为let Some(node) = self.head涉及所有权的转移,而take方法在把值转移出去之后会补充一个None以避免安全问题。

那么,下一个测试要实现peek函数功能,返回头节点的引用:

#[test]
fn test_peek() {
    let mut list = List::new();
    assert_eq!(list.peek(), None);

    list.push(42);
    assert_eq!(list.peek(), Some(&42));

    list.push(100);
    assert_eq!(list.peek(), Some(&100)); 
    assert_eq!(list.pop(), Some(100));
    assert_eq!(list.peek(), Some(&42));
}
pub fn peek(&mut self) -> Option<&T>{
    self.head.as_ref().map(|node| {
        &node.val
    })
}

as_ref()方法是必须的,因为函数返回了一个内部的引用,这可太危险了,参数出了函数就没了,而引用(指针)还在。

接下来是迭代器,rust中分为三种,移交所有权的,借用型的和可变借用型的。

首先实现转移所有权的into版本,还是先写测试:

#[test]
fn test_into_iter(){
    let mut list = List::new();
    list.push(3);
    list.push(2);
    list.push(1);
    let mut iter = list.into_iter();
    assert_eq!(iter.next(), Some(1));
    assert_eq!(iter.next(), Some(2));
    assert_eq!(iter.next(), Some(3));
    assert_eq!(iter.next(), None);
}

取出来的是数值,接下来要实现的有:一个迭代器结构体IntoIter,迭代器特征,以及List结构体的构造迭代器的方法

/*……*/
pub struct IntoIter<T>(List<T>);
/*……*/
    pub fn into_iter(self) -> IntoIter<T>{
        IntoIter(self)
    }
/*……*/
impl<T> Iterator for IntoIter<T>{
    type Item = T;
    fn next(&mut self) -> Option<Self::Item>{
        self.0.pop()
    }
}

IntoIter是一个元组结构体,类型是一个List,也就是一个指针

Into的版本在迭代时也把链表清空了,而下一个Iter,返回一个不可变引用,就没有这个问题了,为了展现这一特点,测试写成这样:

#[test]
fn test_iter() {
    let mut list = List::new();
    list.push(1);
    list.push(2);
    list.push(3);

    let mut iter = list.iter();

    assert_eq!(iter.next(), Some(&3));
    assert_eq!(iter.next(), Some(&2));
    assert_eq!(iter.next(), Some(&1));
    assert_eq!(iter.next(), None);
    
    iter = list.iter();
    assert_eq!(iter.next(), Some(&3));
}

要实现的东西不变,一个结构体定义,一个特征,一个方法

pub struct Iter<'a, T>{
    next: Option<&'a Node<T>>,
}

/*List*/
    pub fn iter(&self) -> Iter<'_, T>{
        Iter{
            next: self.head.as_ref().map(|node| &**node)
        }
    }
    
/*other code*/    

impl<'a,T> Iterator for Iter<'a, T>{
    type Item =&'a T;
    fn next(&mut self) -> Option<Self::Item>{
        self.next.map(|node| {
            self.next = node.next.as_ref().map(|node| &**node);
            &node.val
        })
    }
}

因为涉及到了引用,所以编译器撂挑子了,把生命周期的难题还了回来,不过在此之前,&**node实在是丑陋,其表示对node做两次解引用再返回其引用,可以用as_deref简化:

pub fn iter(&self) -> Iter<'_, T>{
    Iter{
        next: self.head.as_deref()
    }
}

fn next(&mut self) -> Option<Self::Item>{
    self.next.map(|node| {
        self.next = node.next.as_deref();
        &node.val
    })
}

简洁多了

引用的生命周期要保证引用活得不能比其本体久,在iter函数中就是返回的迭代器活得不长于self,也就是链表头。
在迭代器特征的实现中,要保证self至少和Item活得一样久

接下来是更复杂一点的可变借用IterMut,测试:

#[test]
fn test_iter_mut() {
    let mut list = List::new();
    list.push(1);
    list.push(2);
    list.push(3);

    for val in list.iter_mut() {
        *val *= 10;
    }

    let mut iter = list.iter();
    assert_eq!(iter.next(), Some(&30));
    assert_eq!(iter.next(), Some(&20));
    assert_eq!(iter.next(), Some(&10));
}
pub struct IterMut<'a, T>{
    next: Option<&'a mut Node<T>>,
}
//……
    pub fn iter_mut(&mut self) -> IterMut<'_, T>{
        IterMut{
            next: self.head.as_deref_mut()
        }
    }
//……
impl<'a, T> Iterator for IterMut<'a, T>{
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item>{
        self.next.take().map(|node| {
            self.next = node.next.as_deref_mut();
            &mut node.val
        })
    }
}

可变引用的难点在于同时只能有一个,是不可copy的,所以在next函数中要使用take()获取值

至此,一个很基本的链表就算是完成了,难怪不建议新手尝试😵‍💫