diff --git a/src/collections/mod.rs b/src/collections/mod.rs
index 5689cd0..36e8c92 100644
--- a/src/collections/mod.rs
+++ b/src/collections/mod.rs
@@ -10,6 +10,8 @@ pub use allocator_api2::{
     vec::Vec,
 };
 
+pub use queue::Queue;
 pub use rbtree::RbTreeMap;
 
+pub mod queue;
 pub mod rbtree;
diff --git a/src/collections/queue.rs b/src/collections/queue.rs
new file mode 100644
index 0000000..757d6d0
--- /dev/null
+++ b/src/collections/queue.rs
@@ -0,0 +1,431 @@
+//! Types and utilities for working with [ngx_queue_t], an intrusive doubly-linked list.
+//!
+//! This module provides both the tools for interaction with the existing `ngx_queue_t` objects in
+//! the NGINX, and useful high-level types built on top of the `ngx_queue_t`.
+//!
+//! See <https://nginx.org/en/docs/dev/development_guide.html#queue>.
+
+use core::alloc::Layout;
+use core::marker::PhantomData;
+use core::mem;
+use core::ptr::{self, NonNull};
+
+use nginx_sys::{
+    ngx_queue_data, ngx_queue_empty, ngx_queue_init, ngx_queue_insert_after,
+    ngx_queue_insert_before, ngx_queue_remove, ngx_queue_t,
+};
+
+use crate::allocator::{AllocError, Allocator};
+
+/// Trait for pointer conversions between the queue entry and its container.
+///
+/// # Safety
+///
+/// This trait must only be implemented on types that contain a queue link or wrappers with
+/// compatible layout. The type then can be used to access elements of a raw queue type
+/// [NgxQueue] linked via specified field.
+///
+/// If the struct can belong to several queues through multiple embedded `ngx_queue_t` fields,
+/// a separate [NgxQueueEntry] implementation via wrapper type should be used for each queue.
+pub unsafe trait NgxQueueEntry {
+    /// Gets a container pointer from queue node.
+    fn from_queue(queue: NonNull<ngx_queue_t>) -> NonNull<Self>;
+    /// Gets a queue node from a container reference.
+    fn to_queue(&mut self) -> &mut ngx_queue_t;
+}
+
+unsafe impl NgxQueueEntry for ngx_queue_t {
+    fn from_queue(queue: NonNull<ngx_queue_t>) -> NonNull<Self> {
+        queue
+    }
+
+    fn to_queue(&mut self) -> &mut ngx_queue_t {
+        self
+    }
+}
+
+/// A wrapper over a raw `ngx_queue_t`, an intrusive doubly-linked list.
+///
+/// This wrapper is defined in terms of type `T` that embeds and can be converted from or to the
+/// list entries.
+///
+/// Example:
+/// ```rust,no_run
+/// # use core::ptr::{NonNull, addr_of_mut};
+/// # use nginx_sys::{ngx_event_t, ngx_posted_events, ngx_queue_data, ngx_queue_t};
+/// # use ngx::collections::queue::{NgxQueue, NgxQueueEntry};
+/// // We need a wrapper type to define [NgxQueueEntry] on.
+/// #[repr(transparent)]
+/// struct PostedEvent(ngx_event_t);
+///
+/// unsafe impl NgxQueueEntry for PostedEvent {
+///     fn from_queue(queue: NonNull<ngx_queue_t>) -> NonNull<Self> {
+///         // We can safely cast obtained ngx_event_t to a transparent wrapper.
+///         unsafe { ngx_queue_data!(queue, ngx_event_t, queue) }.cast()
+///     }
+///
+///     fn to_queue(&mut self) -> &mut ngx_queue_t {
+///         &mut self.0.queue
+///     }
+/// }
+///
+/// // SAFETY: `ngx_posted_events` global static is a list of `ngx_event_t` linked via
+/// // `ngx_event_t.queue`.
+/// // NGINX is single-threaded, so we get exclusive access to the static.
+/// let posted: &mut NgxQueue<PostedEvent> =
+///         unsafe { NgxQueue::from_ptr_mut(addr_of_mut!(ngx_posted_events)) };
+/// ```
+///
+/// See <https://nginx.org/en/docs/dev/development_guide.html#queue>.
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct NgxQueue<T> {
+    head: ngx_queue_t,
+    _type: PhantomData<T>,
+}
+
+impl<T> NgxQueue<T>
+where
+    T: NgxQueueEntry,
+{
+    /// Creates a queue reference from a pointer to [ngx_queue_t].
+    ///
+    /// # Safety
+    ///
+    /// `head` is a valid pointer to a list head, and `T::from_queue` on the list entries results in
+    /// valid pointers to `T`.
+    pub unsafe fn from_ptr<'a>(head: *const ngx_queue_t) -> &'a Self {
+        &*head.cast()
+    }
+
+    /// Creates a mutable queue reference from a pointer to [ngx_queue_t].
+    ///
+    /// # Safety
+    ///
+    /// `head` is a valid pointer to a list head, and `T::from_queue` on the list entries results in
+    /// valid pointers to `T`.
+    pub unsafe fn from_ptr_mut<'a>(head: *mut ngx_queue_t) -> &'a mut Self {
+        &mut *head.cast()
+    }
+
+    /// Returns `true` if the queue contains no elements.
+    pub fn is_empty(&self) -> bool {
+        self.head.prev.is_null() || unsafe { ngx_queue_empty(&self.head) }
+    }
+
+    /// Appends an element to the end of the queue.
+    pub fn push_back(&mut self, entry: &mut T) {
+        if self.head.prev.is_null() {
+            unsafe { ngx_queue_init(&mut self.head) }
+        }
+
+        unsafe { ngx_queue_insert_before(&mut self.head, entry.to_queue()) }
+    }
+
+    /// Appends an element to the beginning of the queue.
+    pub fn push_front(&mut self, entry: &mut T) {
+        if self.head.prev.is_null() {
+            unsafe { ngx_queue_init(&mut self.head) }
+        }
+
+        unsafe { ngx_queue_insert_after(&mut self.head, entry.to_queue()) }
+    }
+
+    /// Returns an iterator over the entries of the queue.
+    pub fn iter(&self) -> NgxQueueIter<'_, T> {
+        NgxQueueIter::new(&self.head)
+    }
+
+    /// Returns a mutable iterator over the entries of the queue.
+    pub fn iter_mut(&mut self) -> NgxQueueIterMut<'_, T> {
+        NgxQueueIterMut::new(&mut self.head)
+    }
+}
+
+/// An iterator for the queue.
+pub struct NgxQueueIter<'a, T> {
+    head: NonNull<ngx_queue_t>,
+    current: NonNull<ngx_queue_t>,
+    _lifetime: PhantomData<&'a T>,
+}
+
+impl<'a, T> NgxQueueIter<'a, T>
+where
+    T: NgxQueueEntry,
+{
+    /// Creates a new queue iterator.
+    pub fn new(head: &'a ngx_queue_t) -> Self {
+        let head = NonNull::from(head);
+        NgxQueueIter {
+            head,
+            current: head,
+            _lifetime: PhantomData,
+        }
+    }
+}
+
+impl<'a, T> Iterator for NgxQueueIter<'a, T>
+where
+    T: NgxQueueEntry + 'a,
+{
+    type Item = &'a T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            let next = NonNull::new(self.current.as_ref().next)?;
+            if next == self.head {
+                return None;
+            }
+
+            self.current = next;
+            Some(T::from_queue(self.current).as_ref())
+        }
+    }
+}
+
+/// A mutable iterator for the queue.
+pub struct NgxQueueIterMut<'a, T> {
+    head: NonNull<ngx_queue_t>,
+    current: NonNull<ngx_queue_t>,
+    _lifetime: PhantomData<&'a T>,
+}
+
+impl<'a, T> NgxQueueIterMut<'a, T>
+where
+    T: NgxQueueEntry,
+{
+    /// Creates a new mutable queue iterator.
+    pub fn new(head: &'a mut ngx_queue_t) -> Self {
+        let head = NonNull::from(head);
+        NgxQueueIterMut {
+            head,
+            current: head,
+            _lifetime: PhantomData,
+        }
+    }
+}
+
+impl<'a, T> Iterator for NgxQueueIterMut<'a, T>
+where
+    T: NgxQueueEntry + 'a,
+{
+    type Item = &'a mut T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            let next = NonNull::new(self.current.as_ref().next)?;
+            if next == self.head {
+                return None;
+            }
+
+            self.current = next;
+            Some(T::from_queue(self.current).as_mut())
+        }
+    }
+}
+
+/// A doubly-linked list that owns elements of type `T` backed by the specified allocator `A`.
+#[derive(Debug)]
+pub struct Queue<T, A>
+where
+    A: Allocator,
+{
+    // The address of the NgxQueue with queue head has to be stable, as the queue elements will
+    // contain pointers to the head.
+    raw: NonNull<NgxQueue<QueueEntry<T>>>,
+    len: usize,
+    alloc: A,
+}
+
+impl<T, A> Drop for Queue<T, A>
+where
+    A: Allocator,
+{
+    fn drop(&mut self) {
+        while self.pop_front().is_some() {}
+
+        let layout = Layout::for_value(unsafe { self.raw.as_ref() });
+        unsafe { self.allocator().deallocate(self.raw.cast(), layout) };
+    }
+}
+
+unsafe impl<T, A> Send for Queue<T, A>
+where
+    A: Send + Allocator,
+    T: Send,
+{
+}
+
+unsafe impl<T, A> Sync for Queue<T, A>
+where
+    A: Sync + Allocator,
+    T: Sync,
+{
+}
+
+impl<T, A: Allocator> Queue<T, A> {
+    /// Creates a new list with specified allocator.
+    pub fn try_new_in(alloc: A) -> Result<Self, AllocError> {
+        let raw = NgxQueue {
+            head: unsafe { mem::zeroed() },
+            _type: PhantomData,
+        };
+        let raw = crate::allocator::allocate(raw, &alloc)?;
+        Ok(Self { raw, len: 0, alloc })
+    }
+
+    /// Returns a reference to the underlying allocator.
+    pub fn allocator(&self) -> &A {
+        &self.alloc
+    }
+
+    /// Returns `true` if the list contains no elements.
+    pub fn is_empty(&self) -> bool {
+        self.raw().is_empty()
+    }
+
+    /// Returns the number of elements in the queue.
+    pub fn len(&self) -> usize {
+        self.len
+    }
+
+    /// Returns an iterator over the entries of the list.
+    pub fn iter(&self) -> QueueIter<'_, T> {
+        QueueIter::new(&self.raw().head)
+    }
+
+    /// Returns a mutable iterator over the entries of the list.
+    pub fn iter_mut(&mut self) -> QueueIterMut<'_, T> {
+        QueueIterMut::new(&mut self.raw_mut().head)
+    }
+
+    /// Removes the last element and returns it or `None` if the list is empty.
+    pub fn pop_back(&mut self) -> Option<T> {
+        if self.is_empty() {
+            return None;
+        }
+        let node = NonNull::new(self.raw_mut().head.prev)?;
+        Some(unsafe { self.remove(node) })
+    }
+
+    /// Removes the first element and returns it or `None` if the list is empty.
+    pub fn pop_front(&mut self) -> Option<T> {
+        if self.is_empty() {
+            return None;
+        }
+        let node = NonNull::new(self.raw_mut().head.next)?;
+        Some(unsafe { self.remove(node) })
+    }
+
+    /// Appends an element to the end of the list.
+    pub fn push_back(&mut self, item: T) -> Result<&mut T, AllocError> {
+        let mut entry = QueueEntry::new_in(item, self.allocator())?;
+        let entry = unsafe { entry.as_mut() };
+        self.raw_mut().push_back(entry);
+        self.len += 1;
+        Ok(&mut entry.item)
+    }
+
+    /// Appends an element to the beginning of the list.
+    pub fn push_front(&mut self, item: T) -> Result<&mut T, AllocError> {
+        let mut entry = QueueEntry::new_in(item, self.allocator())?;
+        let entry = unsafe { entry.as_mut() };
+        self.raw_mut().push_front(entry);
+        self.len += 1;
+        Ok(&mut entry.item)
+    }
+
+    fn raw(&self) -> &NgxQueue<QueueEntry<T>> {
+        // SAFETY: we allocated this pointer as well-aligned and convertible to reference.
+        unsafe { self.raw.as_ref() }
+    }
+
+    fn raw_mut(&mut self) -> &mut NgxQueue<QueueEntry<T>> {
+        // SAFETY: we allocated this pointer as well-aligned and convertible to reference.
+        unsafe { self.raw.as_mut() }
+    }
+
+    /// Removes a node from the queue and returns the contained value.
+    ///
+    /// # Safety
+    ///
+    /// `node` must be an element of this list.
+    unsafe fn remove(&mut self, node: NonNull<ngx_queue_t>) -> T {
+        ngx_queue_remove(node.as_ptr());
+        self.len -= 1;
+
+        let entry = QueueEntry::<T>::from_queue(node);
+        let copy = entry.read();
+        // Skip drop as QueueEntry is already copied to `x`.
+        self.allocator()
+            .deallocate(entry.cast(), Layout::for_value(entry.as_ref()));
+        copy.item
+    }
+}
+
+#[derive(Debug)]
+struct QueueEntry<T> {
+    queue: ngx_queue_t,
+    item: T,
+}
+
+unsafe impl<T> NgxQueueEntry for QueueEntry<T> {
+    fn from_queue(queue: NonNull<ngx_queue_t>) -> NonNull<Self> {
+        unsafe { ngx_queue_data!(queue, Self, queue) }
+    }
+
+    fn to_queue(&mut self) -> &mut ngx_queue_t {
+        &mut self.queue
+    }
+}
+
+impl<T> QueueEntry<T> {
+    pub fn new_in(item: T, alloc: &impl Allocator) -> Result<NonNull<Self>, AllocError> {
+        let p: NonNull<Self> = alloc.allocate(Layout::new::<Self>())?.cast();
+
+        unsafe {
+            let u = p.cast::<mem::MaybeUninit<Self>>().as_mut();
+            // does not read the uninitialized data
+            ngx_queue_init(&mut u.assume_init_mut().queue);
+            ptr::write(&mut u.assume_init_mut().item, item);
+        }
+
+        Ok(p)
+    }
+}
+
+/// An iterator for the linked list [Queue].
+pub struct QueueIter<'a, T>(NgxQueueIter<'a, QueueEntry<T>>);
+
+impl<'a, T> QueueIter<'a, T> {
+    /// Creates a new iterator for the linked list.
+    pub fn new(head: &'a ngx_queue_t) -> Self {
+        Self(NgxQueueIter::new(head))
+    }
+}
+
+impl<'a, T> Iterator for QueueIter<'a, T> {
+    type Item = &'a T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        Some(&self.0.next()?.item)
+    }
+}
+
+/// A mutable iterator for the linked list [Queue].
+pub struct QueueIterMut<'a, T>(NgxQueueIterMut<'a, QueueEntry<T>>);
+
+impl<'a, T> QueueIterMut<'a, T> {
+    /// Creates a new mutable iterator for the linked list.
+    pub fn new(head: &'a mut ngx_queue_t) -> Self {
+        Self(NgxQueueIterMut::new(head))
+    }
+}
+
+impl<'a, T> Iterator for QueueIterMut<'a, T> {
+    type Item = &'a mut T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        Some(&mut self.0.next()?.item)
+    }
+}
diff --git a/src/collections/rbtree.rs b/src/collections/rbtree.rs
index 9d824f7..29462a6 100644
--- a/src/collections/rbtree.rs
+++ b/src/collections/rbtree.rs
@@ -19,6 +19,96 @@ use nginx_sys::{
 
 use crate::allocator::{self, AllocError, Allocator};
 
+/// Trait for pointer conversions between the tree entry and its container.
+///
+/// # Safety
+///
+/// This trait must only be implemented on types that contain a tree node or wrappers with
+/// compatible layout. The type then can be used to access elements of a raw rbtree type
+/// [NgxRbTree] linked via specified field.
+///
+/// If the struct can belong to several trees through multiple embedded `ngx_rbtree_node_t` fields,
+/// a separate [NgxRbTreeEntry] implementation via wrapper type should be used for each tree.
+pub unsafe trait NgxRbTreeEntry {
+    /// Gets a container pointer from tree node.
+    fn from_rbtree_node(node: NonNull<ngx_rbtree_node_t>) -> NonNull<Self>;
+    /// Gets an rbtree node from a container reference.
+    fn to_rbtree_node(&mut self) -> &mut ngx_rbtree_node_t;
+}
+
+unsafe impl NgxRbTreeEntry for ngx_rbtree_node_t {
+    fn from_rbtree_node(node: NonNull<ngx_rbtree_node_t>) -> NonNull<Self> {
+        node
+    }
+
+    fn to_rbtree_node(&mut self) -> &mut ngx_rbtree_node_t {
+        self
+    }
+}
+
+/// A wrapper over a raw `ngx_rbtree_t`, a red-black tree implementation.
+///
+/// This wrapper is defined in terms of type `T` that embeds and can be converted from or to the
+/// tree nodes.
+///
+/// See <https://nginx.org/en/docs/dev/development_guide.html#red_black_tree>.
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct NgxRbTree<T> {
+    inner: ngx_rbtree_t,
+    _type: PhantomData<T>,
+}
+
+impl<T> NgxRbTree<T>
+where
+    T: NgxRbTreeEntry,
+{
+    /// Creates a tree reference from a pointer to [ngx_rbtree_t].
+    ///
+    /// # Safety
+    ///
+    /// `tree` is a valid pointer to [ngx_rbtree_t], and `T::from_rbtree_node` on the tree nodes
+    /// results in valid pointers to `T`.
+    pub unsafe fn from_ptr<'a>(tree: *const ngx_rbtree_t) -> &'a Self {
+        &*tree.cast()
+    }
+
+    /// Creates a mutable tree reference from a pointer to [ngx_rbtree_t].
+    ///
+    /// # Safety
+    ///
+    /// `tree` is a valid pointer to [ngx_rbtree_t], and `T::from_rbtree_node` on the tree nodes
+    /// results in valid pointers to `T`.
+    pub unsafe fn from_ptr_mut<'a>(tree: *mut ngx_rbtree_t) -> &'a mut Self {
+        &mut *tree.cast()
+    }
+
+    /// Returns `true` if the tree contains no elements.
+    pub fn is_empty(&self) -> bool {
+        ptr::addr_eq(self.inner.root, self.inner.sentinel)
+    }
+
+    /// Appends a node to the tree.
+    pub fn insert(&mut self, node: &mut T) {
+        unsafe { ngx_rbtree_insert(&mut self.inner, node.to_rbtree_node()) };
+    }
+
+    /// Removes the specified node from the tree.
+    pub fn remove(&mut self, node: &mut T) {
+        unsafe { ngx_rbtree_delete(&mut self.inner, node.to_rbtree_node()) };
+    }
+
+    /// Returns an iterator over the nodes of the tree.
+    pub fn iter(&self) -> NgxRbTreeIter<'_> {
+        unsafe { NgxRbTreeIter::new(NonNull::from(&self.inner)) }
+    }
+
+    /// Returns a mutable iterator over the nodes of the tree.
+    pub fn iter_mut(&mut self) -> NgxRbTreeIter<'_> {
+        unsafe { NgxRbTreeIter::new(NonNull::from(&mut self.inner)) }
+    }
+}
+
 /// Raw iterator over the `ngx_rbtree_t` nodes.
 ///
 /// This iterator type can be used to access elements of any correctly initialized `ngx_rbtree_t`
@@ -31,7 +121,7 @@ pub struct NgxRbTreeIter<'a> {
     _lifetime: PhantomData<&'a ()>,
 }
 
-impl<'a> NgxRbTreeIter<'a> {
+impl NgxRbTreeIter<'_> {
     /// Creates an iterator for the `ngx_rbtree_t`.
     ///
     /// # Safety
@@ -54,7 +144,7 @@ impl<'a> NgxRbTreeIter<'a> {
     }
 }
 
-impl<'a> Iterator for NgxRbTreeIter<'a> {
+impl Iterator for NgxRbTreeIter<'_> {
     type Item = NonNull<ngx_rbtree_node_t>;
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -79,15 +169,15 @@ pub struct RbTreeMap<K, V, A>
 where
     A: Allocator,
 {
-    tree: ngx_rbtree_t,
+    tree: NgxRbTree<MapEntry<K, V>>,
     sentinel: NonNull<ngx_rbtree_node_t>,
     alloc: A,
-    _kv_type: PhantomData<(K, V)>,
 }
 
 /// Entry type for the [RbTreeMap].
 ///
 /// The struct is used from the Rust code only and thus does not need to be compatible with C.
+#[derive(Debug)]
 struct MapEntry<K, V> {
     node: ngx_rbtree_node_t,
     key: K,
@@ -110,20 +200,30 @@ where
     }
 }
 
+unsafe impl<K, V> NgxRbTreeEntry for MapEntry<K, V> {
+    fn from_rbtree_node(node: NonNull<ngx_rbtree_node_t>) -> NonNull<Self> {
+        unsafe { ngx_rbtree_data!(node, Self, node) }
+    }
+
+    fn to_rbtree_node(&mut self) -> &mut ngx_rbtree_node_t {
+        &mut self.node
+    }
+}
+
 /// An iterator for the [RbTreeMap].
-pub struct Iter<'a, K: 'a, V: 'a>(NgxRbTreeIter<'a>, PhantomData<(K, V)>);
+pub struct MapIter<'a, K: 'a, V: 'a>(NgxRbTreeIter<'a>, PhantomData<(K, V)>);
 
-impl<'a, K: 'a, V: 'a> Iter<'a, K, V> {
+impl<'a, K: 'a, V: 'a> MapIter<'a, K, V> {
     /// Creates an iterator for the [RbTreeMap].
     pub fn new<A: Allocator>(tree: &'a RbTreeMap<K, V, A>) -> Self {
         // msrv(1.89.0): NonNull::from_ref()
-        let rbtree = NonNull::from(&tree.tree);
+        let rbtree = NonNull::from(&tree.tree.inner);
         // SAFETY: Iter borrows from the tree, ensuring that the tree would outlive it.
         Self(unsafe { NgxRbTreeIter::new(rbtree) }, Default::default())
     }
 }
 
-impl<'a, K: 'a, V: 'a> Iterator for Iter<'a, K, V> {
+impl<'a, K: 'a, V: 'a> Iterator for MapIter<'a, K, V> {
     type Item = (&'a K, &'a V);
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -134,24 +234,24 @@ impl<'a, K: 'a, V: 'a> Iterator for Iter<'a, K, V> {
 }
 
 /// A mutable iterator for the [RbTreeMap].
-pub struct IterMut<'a, K: 'a, V: 'a>(NgxRbTreeIter<'a>, PhantomData<(K, V)>);
+pub struct MapIterMut<'a, K: 'a, V: 'a>(NgxRbTreeIter<'a>, PhantomData<(K, V)>);
 
-impl<'a, K: 'a, V: 'a> IterMut<'a, K, V> {
+impl<'a, K: 'a, V: 'a> MapIterMut<'a, K, V> {
     /// Creates an iterator for the [RbTreeMap].
     pub fn new<A: Allocator>(tree: &'a mut RbTreeMap<K, V, A>) -> Self {
         // msrv(1.89.0): NonNull::from_mut()
-        let rbtree = NonNull::from(&mut tree.tree);
+        let rbtree = NonNull::from(&mut tree.tree.inner);
         // SAFETY: IterMut borrows from the tree, ensuring that the tree would outlive it.
         Self(unsafe { NgxRbTreeIter::new(rbtree) }, Default::default())
     }
 }
 
-impl<'a, K: 'a, V: 'a> Iterator for IterMut<'a, K, V> {
+impl<'a, K: 'a, V: 'a> Iterator for MapIterMut<'a, K, V> {
     type Item = (&'a K, &'a mut V);
 
     fn next(&mut self) -> Option<Self::Item> {
-        let item = self.0.next()?;
-        let item = unsafe { ngx_rbtree_data!(item, MapEntry<K, V>, node).as_mut() };
+        let mut item = MapEntry::<K, V>::from_rbtree_node(self.0.next()?);
+        let item = unsafe { item.as_mut() };
         Some((&item.key, &mut item.value))
     }
 }
@@ -168,14 +268,14 @@ where
     /// Clears the tree, removing all elements.
     pub fn clear(&mut self) {
         // SAFETY: the iter lives until the end of the scope
-        let iter = unsafe { NgxRbTreeIter::new(NonNull::from(&self.tree)) };
+        let iter = unsafe { NgxRbTreeIter::new(NonNull::from(&self.tree.inner)) };
         let layout = Layout::new::<MapEntry<K, V>>();
 
         for node in iter {
             unsafe {
-                let mut data = ngx_rbtree_data!(node, MapEntry<K, V>, node);
+                let mut data = MapEntry::<K, V>::from_rbtree_node(node);
 
-                ngx_rbtree_delete(&mut self.tree, &mut data.as_mut().node);
+                ngx_rbtree_delete(&mut self.tree.inner, &mut data.as_mut().node);
                 ptr::drop_in_place(data.as_mut());
                 self.allocator().deallocate(data.cast(), layout)
             }
@@ -184,19 +284,19 @@ where
 
     /// Returns true if the tree contains no entries.
     pub fn is_empty(&self) -> bool {
-        ptr::addr_eq(self.tree.root, self.tree.sentinel)
+        self.tree.is_empty()
     }
 
     /// Returns an iterator over the entries of the tree.
     #[inline]
-    pub fn iter(&self) -> Iter<'_, K, V> {
-        Iter::new(self)
+    pub fn iter(&self) -> MapIter<'_, K, V> {
+        MapIter::new(self)
     }
 
     /// Returns a mutable iterator over the entries of the tree.
     #[inline]
-    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
-        IterMut::new(self)
+    pub fn iter_mut(&mut self) -> MapIterMut<'_, K, V> {
+        MapIterMut::new(self)
     }
 }
 
@@ -210,14 +310,24 @@ where
         let layout = Layout::new::<ngx_rbtree_node_t>();
         let sentinel: NonNull<ngx_rbtree_node_t> = alloc.allocate_zeroed(layout)?.cast();
 
+        let tree = NgxRbTree {
+            inner: unsafe { mem::zeroed() },
+            _type: PhantomData,
+        };
+
         let mut this = RbTreeMap {
-            tree: unsafe { mem::zeroed() },
+            tree,
             sentinel,
             alloc,
-            _kv_type: PhantomData,
         };
 
-        unsafe { ngx_rbtree_init(&mut this.tree, this.sentinel.as_ptr(), Some(Self::insert)) };
+        unsafe {
+            ngx_rbtree_init(
+                &mut this.tree.inner,
+                this.sentinel.as_ptr(),
+                Some(Self::insert),
+            )
+        };
 
         Ok(this)
     }
@@ -260,7 +370,8 @@ where
     {
         let mut node = self.lookup(key)?;
         unsafe {
-            ngx_rbtree_delete(&mut self.tree, &mut node.as_mut().node);
+            self.tree.remove(node.as_mut());
+
             let layout = Layout::for_value(node.as_ref());
             // SAFETY: we make a bitwise copy of the node and dispose of the original value without
             // dropping it.
@@ -278,7 +389,7 @@ where
         } else {
             let node = MapEntry::new(key, value);
             let mut node = allocator::allocate(node, self.allocator())?;
-            unsafe { ngx_rbtree_insert(&mut self.tree, &mut node.as_mut().node) };
+            self.tree.insert(unsafe { node.as_mut() });
             node
         };
 
@@ -324,10 +435,10 @@ where
         K: borrow::Borrow<Q>,
         Q: Hash + Ord + ?Sized,
     {
-        let mut node = self.tree.root;
+        let mut node = self.tree.inner.root;
         let hash = BuildMapHasher::default().hash_one(key) as ngx_rbtree_key_t;
 
-        while !ptr::addr_eq(node, self.tree.sentinel) {
+        while !ptr::addr_eq(node, self.tree.inner.sentinel) {
             let n = unsafe { NonNull::new_unchecked(ngx_rbtree_data!(node, MapEntry<K, V>, node)) };
             let nr = unsafe { n.as_ref() };
 
