-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
77ecac3
commit 9a19aa2
Showing
1 changed file
with
22 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,126 +1,37 @@ | ||
# Intrusive Circular Doubly Linked List in Rust | ||
# Intrusive Circular Linked List in Rust | ||
|
||
Learning how to do cdlist in Rust: | ||
Let the elements hold the ownership, instead of the collection. | ||
## Introduction | ||
|
||
## Usage | ||
The `cdlist` crate implements a non-thread-safe, intrusive, doubly-linked list in Rust. Its primary characteristic is the inclusion of link pointers within the data structures themselves, rather than in separate node wrappers. This approach enhances memory and performance efficiency but requires careful handling of ownership and safety, which this crate has taken care of. | ||
|
||
See tests as examples. | ||
## Characteristics | ||
|
||
```rust | ||
use cdlist::LinkNode; | ||
- **Intrusive Design**: Nodes contain links to their neighbors, reducing overhead. | ||
- **Non-Thread-Safe**: Optimized for single-threaded environments, avoiding the complexity and overhead of synchronization. | ||
- **Self-Ownership**: Nodes own their data and their position within the list. Dropping a data automatically delists it. | ||
- **Memory Safety**: Utilizes pinning to maintain the integrity of self-references within nodes, ensuring safe usage of the data structure. | ||
|
||
#[test] | ||
fn deref_mut() { | ||
let mut node0 = LinkNode::new(1); | ||
let node1 = LinkNode::new(2); | ||
*node0 += *node1; | ||
assert_eq!(3, *node0); | ||
} | ||
|
||
#[test] | ||
fn iter_single() { | ||
let node0 = LinkNode::new(0); | ||
assert_eq!(collect(&node0), vec![0]); | ||
} | ||
## Example Usage | ||
|
||
#[test] | ||
fn iter() { | ||
let mut nodes = (0..10).map(LinkNode::new).collect::<Vec<_>>(); | ||
connect_all(&mut nodes, 0, 10); | ||
assert_eq!(collect(&nodes[0]), (0..10).collect::<Vec<_>>()); | ||
assert_eq!(collect_rev(&nodes[9]), (0..10).rev().collect::<Vec<_>>()); | ||
} | ||
|
||
#[test] | ||
fn iter_mut() { | ||
let mut nodes = (0..10).map(LinkNode::new).collect::<Vec<_>>(); | ||
connect_all(&mut nodes, 0, 10); | ||
let mut j = 0; | ||
nodes[5].for_each_mut(|i| { | ||
*i += j; | ||
j += 1; | ||
}); | ||
j = 0; | ||
assert_eq!(collect(&nodes[0]), vec![5, 7, 9, 11, 13, 5, 7, 9, 11, 13]); | ||
nodes[9].for_each_mut_rev(|i| { | ||
*i += j; | ||
j += 1; | ||
}); | ||
assert_eq!( | ||
collect(&nodes[0]), | ||
vec![14, 15, 16, 17, 18, 9, 10, 11, 12, 13] | ||
); | ||
} | ||
```rust | ||
use cdlist::LinkNode; | ||
|
||
#[test] | ||
fn pop_self() { | ||
let mut node0 = LinkNode::new(0); | ||
node0.take(); | ||
assert_eq!(collect(&node0), vec![0]); | ||
} | ||
fn main() { | ||
let mut node1 = LinkNode::new(1); | ||
let mut node2 = LinkNode::new(2); | ||
|
||
#[test] | ||
fn requeue() { | ||
let mut n0 = LinkNode::new(0); | ||
let mut n1 = LinkNode::new(1); | ||
let mut n2 = LinkNode::new(2); | ||
n0.add(&mut n1); | ||
n1.add(&mut n2); | ||
assert_eq!(collect(&n0), vec![0, 1, 2]); | ||
n2.add(&mut n1); | ||
assert_eq!(collect(&n0), vec![0, 2, 1]); | ||
assert_eq!(collect(&n2), vec![2, 1, 0]); | ||
} | ||
node1.add(&mut node2); // Adds node2 after node1 | ||
|
||
#[test] | ||
fn take() { | ||
let mut nodes = (0..10).map(LinkNode::new).collect::<Vec<_>>(); | ||
connect_all(&mut nodes, 0, 10); | ||
assert_eq!(collect(&nodes[0]), (0..10).collect::<Vec<_>>()); | ||
let to_take = [0, 2, 4, 6, 8]; | ||
for i in to_take { | ||
nodes[i].take(); | ||
} | ||
for i in to_take { | ||
assert_eq!(collect(&nodes[i]), vec![i]); | ||
} | ||
assert_eq!(collect(&nodes[1]), vec![1, 3, 5, 7, 9]); | ||
node1.for_each(|&data| println!("{}", data)); // Prints: 1 2 | ||
} | ||
``` | ||
|
||
#[test] | ||
fn add() { | ||
let mut nodes = (0..10).map(LinkNode::new).collect::<Vec<_>>(); | ||
connect_all(&mut nodes, 0, 5); | ||
connect_all(&mut nodes, 5, 10); | ||
assert_eq!(collect(&nodes[0]), (0..5).collect::<Vec<_>>()); | ||
assert_eq!(collect(&nodes[5]), (5..10).collect::<Vec<_>>()); | ||
let (n0, n1) = nodes.split_at_mut(5); | ||
n0[2].add(&mut n1[2]); | ||
assert_eq!(collect(&nodes[0]), vec![0, 1, 2, 7, 3, 4]); | ||
assert_eq!(collect_rev(&nodes[4]), vec![4, 3, 7, 2, 1, 0]); | ||
assert_eq!(collect(&nodes[5]), vec![5, 6, 8, 9]); | ||
assert_eq!(collect_rev(&nodes[9]), vec![9, 8, 6, 5]); | ||
} | ||
## Implementation Insights | ||
|
||
// helper functions | ||
- **Pinning**: Nodes are pinned (`Pin<Box<Inner<T>>>`) to prevent invalidation of references due to memory movement, crucial for the safety of self-referential structures. | ||
|
||
fn collect<T: Copy>(node: &LinkNode<T>) -> Vec<T> { | ||
let mut vec = vec![]; | ||
node.for_each(|&i| vec.push(i)); | ||
vec | ||
} | ||
## Next Steps | ||
|
||
fn collect_rev<T: Copy>(node: &LinkNode<T>) -> Vec<T> { | ||
let mut vec = vec![]; | ||
node.for_each_rev(|&i| vec.push(i)); | ||
vec | ||
} | ||
To really make this crate useful, it needs to allow multi-threading, which can be enabled behind a feature flag. Though, this would involves a lot of work to ensure racing-conditions are handled correctly. | ||
|
||
fn connect_all<T>(nodes: &mut [LinkNode<T>], start: usize, end: usize) { | ||
for i in start..(end - 1) { | ||
let (ni, nj) = nodes[i..].split_at_mut(1); | ||
ni[0].add(&mut nj[0]) | ||
} | ||
} | ||
``` | ||
This crate is mainly a learning exercise. |