Adding entries

The transaction log can now be created and hold entries, but there is no way to add anything to the list. Typically, a list has the ability to add elements to either end—as long as there is a pointer to that end. If that was not the case, any operation would become computationally expensive, since every item has to be looked at to find its successor. With a pointer to the end (tail) of the list, this won't be the case for the append operation; however, to access a random index on the list, it would require some time to go through everything.

Naming is—especially if English is your second language—often tricky. Operations have different names by the language or library used. For example, common names for adding items to a list include push (can add to the front or back), push_back, add, insert (usually comes with a positional parameter), or append . On top of being able to guess method names, some imply completely different processes than others! If you design an interface or library, find the most descriptive and simple name possible and reuse whenever you can!

This is one of the things that a linked list does really well—adding items to either end. There are a few critical things that should not be overlooked, though:

  • Creating the Node object within the method makes for a nicer API and better ownership handling.
  • Edge cases such as empty lists.
  • Incrementing the length is a good idea.
  • The RefCell is used to retrieve mutable ownership for setting a new successor using its borrow_mut() function (interior mutability).

Once that is thought of, the actual implementation is not too bad. Rust's Option type offers a method to retrieve ownership of a value it contains, replacing it with None (see also the documentations for Option.take()https://doc.rust-lang.org/std/option/enum.Option.html#method.take and mem::replace()https://doc.rust-lang.org/stable/std/mem/fn.replace.html), which conveniently shortens the code required to append a new node:

pub fn append(&mut self, value: String) {
let new = Node::new(value);
match self.tail.take() {
Some(old) => old.borrow_mut().next = Some(new.clone()),
None => self.head = Some(new.clone())
};
self.length += 1;
self.tail = Some(new);
}

With that, it's now possible to create a log of any string commands passing through. However, there is something important missing here as well: log replay.