Skip to content

Creating a Linked List

To create a linked list, we need two things:

  • A way to create a node and allocate it on the heap
  • A head pointer that points to the first node

An empty linked list is simply a head pointer that points to nothing.

In C

c
// Creates a new node on the heap
Node *create_node(int data) {
    // Allocate memory for a new node
    Node *node = (Node *)malloc(sizeof(Node));

    // Set the data
    node->data = data;

    // Initially, the node doesn't point to anything
    node->next = NULL;

    return node;
}

// Start with head pointing to NULL
Node *head = NULL;

// Create three nodes
Node *a = create_node(1);
Node *b = create_node(2);
Node *c = create_node(3);

// Link them together
a->next = b;    // Node a points to node b
b->next = c;    // Node b points to node c
// c->next is already NULL, so c is the end of list

// Point head at the first node
head = a;

After linking, the linked list looks like this:

HEAD -> [1 | next] -> [2 | next] -> [3 | NULL]

Node *create_node(int data) takes an integer data and creates a new node on the heap with that data. It returns a pointer to the newly created node.

a->next = b links node a to node b by storing b's memory address in a's next pointer. Same goes with node b and node c.

head is just a pointer that points to node a.

WARNING

malloc can fail and return NULL if the system is out of memory. In production code we should always check if node is NULL before using it to avoid dereferencing a NULL pointer, which would crash the program.

In Rust

rust
// Implement a constructor for Node that returns a heap allocated Box<Node>
impl Node {
    fn new(data: i32) -> Box<Node> {
        Box::new(Node { data, next: None })
    }
}

// Create three nodes
let mut a = Node::new(1);
let mut b = Node::new(2);
let c = Node::new(3);

// Link them together
b.next = Some(c);   // Node b points to node c
a.next = Some(b);   // Node a points to node b

// Point head at the first node
let head = Some(a);

After linking, the list looks like this:

HEAD -> [1 | SOME] -> [2 | SOME] -> [3 | NONE]

Notice that we link the nodes in reverse order (we wrote b.next = Some(c) before a.next = Some(b)). This is because of a concept in Rust known as ownership. Once we move b into a.next, we can no longer access b directly to set its next.

What is impl?

impl stands for "implementation". It lets us define methods (functions) that belong to a specific type.

In C, if we want functions for a Node, we write standalone functions like this:

c
Node *create_node(int data) { ... }
void insert_node(Node **head, int data) { ... }

In Rust, we can group these functions inside an impl block:

rust
impl Node {
    fn new(data: i32) -> Box<Node> { ... }
    fn insert(&mut self, data: i32) { ... }
}

This makes it clear these functions "belong to" Node. Instead of calling create_node(data), we call Node::new(data). We could create standalone functions in Rust, but using impl is cleaner and more organized.

Key Difference

CRust
Empty listNode *head = NULLlet head: Option<Box<Node>> = None
Create nodecreate_node(data)Node::new(data)
Allocate on heapmalloc(sizeof(Node))Box::new(Node { ... })
Link nodesa->next = ba.next = Some(b)
Allocation failureReturns NULL silentlyPanics immediately
Free nodesfree() each manuallyAutomatic

Personal study notes for learning data structures