ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Lifetime-Driven API for Linked List Node

Rust

Goal -- WPM

Ready
Exercise Algorithm Area
1use std::ptr::NonNull;
2
3// Represents a node in a singly linked list.
4// 'a is the lifetime parameter for the data stored in the node.
5struct Node<'a, T> {
6data: T,
7next: Option<NonNull<Node<'a, T>>>, // Pointer to the next node
8}
9
10impl<'a, T> Node<'a, T> {
11// Creates a new node with the given data.
12fn new(data: T) -> Self {
13Node { data, next: None }
14}
15
16// Gets an immutable reference to the next node, if it exists.
17// The lifetime of the returned reference is tied to the lifetime of self.
18fn get_next(&self) -> Option<&'a Node<'a, T>> {
19self.next.map(|ptr| unsafe { &*ptr.as_ptr() })
20}
21
22// Sets the next node. Takes ownership of the pointer to the next node.
23// This method requires careful lifetime management to prevent dangling pointers.
24// For simplicity, we use NonNull, but a more robust implementation might use Rc/Arc or Box.
25fn set_next(&mut self, next_node_ptr: Option<NonNull<Node<'a, T>>>) {
26self.next = next_node_ptr;
27}
28
29// Safely gets a mutable reference to the next node.
30// This is complex with raw pointers and requires careful unsafe code.
31// In a real-world scenario, consider using safe abstractions like Box or Rc.
32fn get_next_mut(&mut self) -> Option<&'a mut Node<'a, T>> {
33self.next.map(|mut ptr| unsafe { &mut *ptr.as_ptr() })
34}
35
36// Helper to get data.
37fn get_data(&self) -> &T {
38&self.data
39}
40}
41
42// Example usage demonstrating lifetime constraints.
43fn main() {
44// Create nodes with a specific lifetime scope.
45let mut node1 = Node::new(10);
46let mut node2 = Node::new(20);
47let mut node3 = Node::new(30);
48
49// Convert raw pointers for linking. This requires unsafe blocks.
50let node2_ptr = NonNull::from(&mut node2);
51let node3_ptr = NonNull::from(&mut node3);
52
53node1.set_next(Some(node2_ptr));
54node2.set_next(Some(node3_ptr));
55
56// Accessing data and next nodes.
57println!("Node 1 data: {}", node1.get_data());
58if let Some(next_node) = node1.get_next() {
59println!("Node 1 next data: {}", next_node.get_data());
60if let Some(next_next_node) = next_node.get_next() {
61println!("Node 1 next next data: {}", next_next_node.get_data());
62}
63}
64
65// Demonstrating mutable access (use with extreme caution).
66if let Some(next_node_mut) = node1.get_next_mut() {
67// This is where lifetime issues can easily arise if not managed properly.
68// For instance, if node2 went out of scope here, this would be a dangling pointer.
69println!("Node 1 next data (mutable access): {}", next_node_mut.get_data());
70}
71
72// The lifetimes of node1, node2, and node3 are tied together.
73// If node2 were dropped before node1's `next` pointer was cleared, it would be UB.
74}
Algorithm description viewbox

Lifetime-Driven API for Linked List Node

Algorithm description:

This Rust code defines a `Node` struct for a singly linked list, focusing on lifetime management for its `next` pointer. It uses `NonNull` for raw pointer manipulation, requiring `unsafe` blocks for dereferencing. The API includes methods to safely get immutable references to the next node and, with caution, mutable references. This pattern is fundamental for building safe, memory-managed data structures in Rust where object lifetimes are critical.

Algorithm explanation:

The `Node` struct uses a lifetime parameter `'a` to ensure that any `Node` it points to via `next` lives at least as long as the current `Node`. `NonNull<Node<'a, T>>` is used to represent a non-null raw pointer to the next node, enabling shared mutable access patterns that are tricky with Rust's borrow checker. The `get_next` method returns an immutable reference with the same lifetime `'a`, ensuring it doesn't outlive the node it's borrowed from. `set_next` updates the pointer, and `get_next_mut` provides mutable access, both requiring `unsafe` blocks due to raw pointer dereferencing. The primary correctness argument relies on the programmer ensuring that the pointed-to `Node`s outlive the pointers referencing them, a constraint enforced by Rust's lifetime system when used correctly. Time complexity for accessing `next` is O(1). Space complexity is O(1) per node.

Pseudocode:

struct Node<'a, T>:
  data: T
  next: Option<Pointer to Node<'a, T>>

method new(data: T) -> Node<'a, T>:
  create a new Node with data and next as None

method get_next(&self) -> Option<&'a Node<'a, T>>:
  if self.next exists:
    dereference pointer to get reference with lifetime 'a
  return Option of reference

method set_next(&mut self, next_ptr: Option<Pointer to Node<'a, T>>):
  set self.next to next_ptr

method get_next_mut(&mut self) -> Option<&'a mut Node<'a, T>>:
  if self.next exists:
    dereference pointer to get mutable reference with lifetime 'a
  return Option of mutable reference