I am trying to implement Red-Black Tree in Rust. After 2 days of battling with the compiler, I am ready to give up and am here asking for help.
This question helped me quite a bit: How do I handle/circumvent "Cannot assign to ... which is behind a & reference" in Rust?
I looked at existing sample code for RB-Trees in Rust, but all of the ones I saw use some form of unsafe operations or null, which we are not supposed to use here.
I have the following code:
#[derive(Debug, Clone, PartialEq)]
pub enum Colour {
Red,
Black,
}
type T_Node<T> = Option<Box<Node<T>>>;
#[derive(Debug, Clone, PartialEq)]
pub struct Node<T: Copy + Clone + Ord> {
value: T,
colour: Colour,
parent: T_Node<T>,
left: T_Node<T>,
right: T_Node<T>,
}
impl<T: Copy + Clone + Ord> Node<T>
{
pub fn new(value: T) -> Node<T>
{
Node {
value: value,
colour: Colour::Red, // add a new node as red, then fix violations
parent: None,
left: None,
right: None,
// height: 1,
}
}
pub fn insert(&mut self, value: T)
{
if self.value == value
{
return;
}
let mut leaf = if value < self.value { &mut self.left } else { &mut self.right };
match leaf
{
None =>
{
let mut new_node = Node::new(value);
new_node.parent = Some(Box::new(self));
new_node.colour = Colour::Red;
(*leaf) = Some(Box::new(new_node));
},
Some(ref mut leaf) =>
{
leaf.insert(value);
}
};
}
}
The line new_node.parent = Some(Box::new(self)); gives me the error.
I understand understand why the error happens (self is declared as a mutable reference) and I have no idea how to fix this, but I need self to be a mutable reference so I can modify my tree (unless you can suggest something better).
I tried to declare the T_Node to have a mutable reference instead of just Node, but that just created more problems.
I am also open to suggestions for a better choice of variable types and what not.
Any help is appreciated.
There are some faults in the design which makes it impossible to go any further without making some changes.
First,
Boxdoesn't support shared ownership but you require that because the same node is referenced by parent (rbtree.right/rbtree.left) and child (rbtree.parent). For that you needRc.So instead of
Box, you will need to switch toRc:But this doesn't solve the problem. Now your node is inside
RcandRcdoesn't allow mutation to it's contents (you can mutate byget_mutbut that requires it to be unique which is not a constant in your case). You won't be able to do much with your tree unless you can mutate a node.So you need to use interior mutability pattern. For that we will add an additional layer of
RefCell.Now, this will allow us to mutate the contents inside.
But this doesn't solve it. Because you need to hold a reference from the child to the parent as well, you will end up creating a reference cycle.
Luckily, the rust book explains how to fix reference cycle for the exact same scenario:
So we need child to hold a weak reference to parent. This can be done as:
Now we have fixed majority of the design.
One more thing that we should do is, instead of exposing
Nodedirectly, we will encapsulate it in a structRBTreewhich will hold therootof the tree and operations likeinsert,search,delete, etc. can be called onRBtree. This will make things simple and implementation will become more logical.Now, let's write an
insertimplementation similar to yours:I defined an
insertfunction inside theRBTree::insertjust for the sake of simplicity of recursive calls. The outer functions tests for root and further insertions are carried out inside nestedinsertfunctions.Basically, we start with:
This creates a new node.
Then,
If root is
None, insert atroot, otherwise callinsertwithrootitself. So the nestedinsertfunction basically receives the parent in which left and right child are checked and the insertion is made.Then, the control moves to the nested
insertfunction.We define the following two lines for making it convenient to access inner data:
Just like in your implementation, we return if value is already there:
Now we store a mutable reference to either left or right child:
Now, a check is made on the child if it is
NoneorSome. In case ofNone, we make the insertion. Otherwise, we callinsertrecursively:Rc::downgrade(&child)is for generating a weak reference.Here is a working sample: Playground