Rust basic one-way linked list

Posted by victor78 on Wed, 26 Jan 2022 05:20:59 +0100

Use Rust to complete one-way linked list

I've been idle recently. I saw the rise of rust, a language with a considerable salary. I heard that some linux began to write with rust, but I heard that learning had a curve. At that time, I didn't believe in evil. After learning, I knew that there was a curve... The linked list alone has been done for several days. There are still too few articles in this field in China. It even gives me the illusion that trust is not mature. However, since linux bosses are using it and claim to be a modern language with the advantages of various languages, I can't help but want to try it.

Let's first analyze why it is difficult and why some people think it is "more difficult" than C/C + +.

  1. Many concepts
    Because t rust is a collection of hundreds of schools, it has absorbed many advanced ideas and concepts, which is most intuitively reflected in the release of occupation. There are many concepts that do not exist in other languages, such as life cycle, ownership, etc.

  2. Many rules
    For C + +, we all apply for it ourselves and release it ourselves or with the help of the stack. For java, python and other languages, their occupation is managed by the language itself. gc (garbage collection) is often criticized as a global pause. T rust ensures security and stifles the possibility of insecurity during compilation. Programmers need to know more rules, such as stack and recycling mechanism. These rules are difficult for novices (me) to detect without using unsafe.

  3. Little domestic data
    Domestic trust is still in its infancy, and the well written tutorials are provided by the community. Zhihushang's trust Bible Series is very good, but because it is still updated, it is not comprehensive enough and is not suitable for pure novices to learn.

Tell me about my own feelings. I have learned a lot of mainstream programming languages, of which C + + is used to write acm, which mainly reflects logical thinking (I thought I knew C + +) and didn't have a deep understanding of the bottom layer. python and java have written many projects. Although they are not big, they are different from the concerns of trust. In this way, I went to learn trust and was said by the trust leaders in the group that I was a novice (crying) who had never learned programming. It can be seen that the learning gradient of trust is very large. Please be prepared and ask more people if you don't understand. Let's officially begin:

Problem restatement

Use t rust to implement one-way linked list. The methods are add and show. Add is used to add a node at the end, and show is used to cycle through the contents of the linked list.

The elders in the group said that it was annoying to implement the linked list with t rust. I suggested that I use unsafe directly, but I think the best way to be familiar with a language is to practice the data structure. If the data structure can be written, it will basically implement other secure codes according to my own ideas. So I still wrote the linked list.

data structure

There are mainly two data structures. One is the linked list Node, which stores the value and next; One is the linked list itself, which stores head and tail.

The structure is as follows

#[derive(Debug)]
pub struct Node<T>{
    pub value:T,
    next:Option<Rc<RefCell<Node<T>>>>,
}
#[derive(Debug)]
pub struct List<T>{
    head:Option<Rc<RefCell<Node<T>>>>,
    tail:Option<Rc<RefCell<Node<T>>>>
}

method

For the List class, you need to implement the add and show methods.

add method

impl<T> List<T>{
    pub fn new()->Self{
        List{
            head:None,
            tail:None
        }
    }
    pub fn add(&mut self,value:T){
        let new_node=
            Rc::new(RefCell::new(
                Node{value:value,next:None}
            ));

        match self.tail.take(){
            Some(V)=>{
                (*V).borrow_mut().next=Some(Rc::clone(&new_node));
            },
            None=>{
                self.head = Some(Rc::clone(&new_node));
            }
        }
        self.tail = Some(new_node);

    }


}

show method

impl<T:Display> List<T>{
    pub fn show(&mut self){
        let mut now;
        if let Some(v)=&self.head{
            now=Rc::clone(v);
        }
        else { return; }
        loop{
            let old=now;
            println!("{}",(*old).borrow().value);
            if let Some(v)=&(*old).borrow().next{
                now=Rc::clone(v);
            }else{return;};
        }

    }
}

Students who are good at C language data structure will find it troublesome for me to write, but I will talk about why I write like this and whether other writing methods are OK. Although it is not necessarily the best way to write, but with my personal talent and no guidance, I can only write like this. If there is a better way, or if you think what I said is wrong, you are welcome to send a private letter.

It should be noted that for println! T is required to implement Display. Since I test i32 here, it implements Display, so T:Display.

Main function

fn main() {
    let mut list:List<i32>=List::new();
    list.add(32);
    list.add(54);

    list.show();
    //println!("{:#?}",list);


}

When running, you can see that the linked list data is output.

Problems encountered and analysis

data structure

At the beginning of the data structure, I thought that, like c, just pass in the reference directly, that is & node. Change the allocation on the heap to box < node >, and to solve the ownership problem, change to RC < node >, because it needs to add or modify the data, change to RC < refcell < node > >, and then change to option < RC < refcell < node > >, and finally form the type of data structure.

About show

The biggest problem I encountered when writing code was show, which I started with

pub fn show(&mut self){
        let mut now=&self.head;
        while let Some(v)=now{
            now=&(**v).borrow().next;
        }
    }

This code is believed to have been written in c language. At first glance, I don't think there is a problem. It is short and clear.

Once the code runs, there are errors

error[E0716]: temporary value dropped while borrowed
  --> src/My_Link_List.rs:51:18
   |
51 |             now=&(**v).borrow().next;
   |                  ^^^^^^^^^^^^^^     -
   |                  |                  |
   |                  |                  temporary value is freed at the end of this statement
   |                  |                  ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, Node<T>>`
   |                  creates a temporary which is freed while still in use
   |                  a temporary with access to the borrow is created here ...
   |
   = note: consider using a `let` binding to create a longer lived value

It means that the row method of this refcell will return the ref type that will be recycled at the end of the semicolon. This is the first time I know, and I don't know what the principle is. Anyway, I'll tell you to extend the life cycle of this Ref. Then it was changed

pub fn show(&mut self){
        let mut now=&self.head;
        while let Some(v)=now{
            let t=(**v).borrow();
            now=&t.next;
        }
    }

It seems right that the wave extends to the end of this cycle. Run again

error[E0597]: `t` does not live long enough
  --> src/My_Link_List.rs:52:18
   |
52 |             now=&t.next;
   |                  ^ borrowed value does not live long enough
53 |         }
   |         -
   |         |
   |         `t` dropped here while still borrowed
   |         borrow might be used here, when `t` is dropped and runs the destructor for type `Ref<'_, Node<T>>`

If t is not alive long enough, t will be called after this loop.??? I don't quite understand, but just let him live longer, right.

pub fn show(&mut self){
        let mut now=&self.head;
        let mut t;
        while let Some(v)=now{
            t=(**v).borrow();
            now=&t.next;
        }
    }

Is it long enough this time?

error[E0506]: cannot assign to `t` because it is borrowed
    --> src/My_Link_List.rs:52:13
     |
52   |             t=(**v).borrow();
     |             ^
     |             |
     |             assignment to borrowed `t` occurs here
     |             borrow later used here
53   |             now=&t.next;
     |                  - borrow of `t` occurs here
     |
     = note: borrow occurs due to deref coercion to `Node<T>`

Good guy? A new error occurs, which means that t is a ref and now borrows T. If t is re assigned, it violates the principle of "ontology cannot be re assigned when ontology borrowing exists". So what?

Think about it. If now in my loop has to be assigned every time, and it must be a reference to a variable inside the loop, and that variable is also changing, it must report this error every time, because the borrowed variable must be modified in the next round. I'm at the end of my rope here. After a while of blind debugging, I found the reason for this problem: because my now is a reference, she must reference a variable that changes in the loop. If my now is not a reference, but an entity, it will not reference other variables, but a new variable composed of other values, which can solve this problem.

Considering this, I want to change the type of now to option < RC < refcell < node > >, self Head cannot be directly used as the initial value, because it will cause the transfer of ownership, so we just extract the things in the option and wrap Some. Thinking about this, I found that the things in it were going to be used in iterations. Why should I take it out, save it and take it out again.... Therefore, I set the type of now as RC < refcell < node > >. The first step is to extract the now of the head, and then cycle the now, because the next has an option. If you want to assign a value to now, you have to remove the option of the next. So write

    pub fn show(&mut self){
        //Extract now
        let mut now;
        if let Some(v)=&self.head{
            now=Rc::clone(v);
        }
        else { return; }//If the head is none, you don't have to traverse


        //Cyclic output
        loop{
            let next=&(*now).borrow().next;
            if let Some(v)=next{
                now=Rc::clone(v);
            }
            else { return; }

        }


    }

run!
pleasantly surprised

error[E0506]: cannot assign to `now` because it is borrowed
    --> src/My_Link_List.rs:61:17
     |
59   |             let next=&(*now).borrow().next;
     |                       ---------------
     |                       | |
     |                       | borrow of `now` occurs here
     |                       a temporary with access to the borrow is created here ...
60   |             if let Some(v)=next{
61   |                 now=Rc::clone(v);
     |                 ^^^ assignment to borrowed `now` occurs here
...
65   |         }
     |         - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, Node<T>>`
     |
     = note: borrow occurs due to deref coercion to `RefCell<Node<T>>`

Why is it still a similar mistake... Take a closer look. Now is quoted this time, but! Now is an ontology, not a reference. We can transfer the reference to now to others. After now loses its ownership, we can concentrate on the assignment. Add a let old=now to transfer the ownership, and then get the above code.

summary

As I said above, I don't quite understand the principle, but I constantly modify the code according to the error report, and finally make it run. I have to study the specific reasons. After all, we can't always think about what to do after problems. If there is an error or correct understanding, please send me a private letter!

Topics: data structure linked list Rust