Swap arrayvec crate for heapless to use LenT optimization (#47101)

Gnome! and Piotr Osiewicz created

Swaps the `arrayvec` dependency for `heapless`, as the `heapless`
library allows changing the type used for the `len` field, which
`arrayvec` hard-codes to `usize`. This means that, for all the
`ArrayVec`s in Zed, we can save 7 bytes on 64 bit platforms by just
storing the length as a `u8`.

I have not benchmarked this change locally, as I don't know what
benchmarking tools are in this project.

As a small bit of context, I wrote the PR to `heapless` to add this
`LenT` generic after seeing a PR on the `arrayvec` crate that seems to
be dead now. Once I saw some of Zed's blog posts about the `rope` crate
and noticed the usage of `arrayvec`, I thought this might be a welcome
change.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>

Change summary

Cargo.lock                                           |  30 ++
Cargo.toml                                           |   2 
crates/agent_ui/Cargo.toml                           |   2 
crates/agent_ui/src/conversation_view.rs             |   1 
crates/agent_ui/src/conversation_view/thread_view.rs |   5 
crates/edit_prediction/Cargo.toml                    |   2 
crates/edit_prediction/src/edit_prediction.rs        |  30 ++-
crates/rope/Cargo.toml                               |   2 
crates/rope/src/chunk.rs                             |  18 +
crates/rope/src/rope.rs                              |   6 
crates/sum_tree/Cargo.toml                           |   2 
crates/sum_tree/src/cursor.rs                        | 110 ++++++++-----
crates/sum_tree/src/sum_tree.rs                      |  82 ++++++----
13 files changed, 176 insertions(+), 116 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -334,7 +334,6 @@ dependencies = [
  "agent_settings",
  "ai_onboarding",
  "anyhow",
- "arrayvec",
  "assistant_slash_command",
  "assistant_slash_commands",
  "assistant_text_thread",
@@ -363,6 +362,7 @@ dependencies = [
  "git",
  "gpui",
  "gpui_tokio",
+ "heapless",
  "html_to_markdown",
  "http_client",
  "image",
@@ -733,9 +733,6 @@ name = "arrayvec"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-dependencies = [
- "serde",
-]
 
 [[package]]
 name = "as-raw-xcb-connection"
@@ -5238,7 +5235,6 @@ version = "0.1.0"
 dependencies = [
  "ai_onboarding",
  "anyhow",
- "arrayvec",
  "brotli",
  "buffer_diff",
  "client",
@@ -5256,6 +5252,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "gpui",
+ "heapless",
  "indoc",
  "itertools 0.14.0",
  "language",
@@ -8027,6 +8024,15 @@ dependencies = [
  "smallvec",
 ]
 
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
@@ -8111,6 +8117,16 @@ dependencies = [
  "http 0.2.12",
 ]
 
+[[package]]
+name = "heapless"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
+dependencies = [
+ "hash32",
+ "stable_deref_trait",
+]
+
 [[package]]
 name = "heck"
 version = "0.3.3"
@@ -14672,10 +14688,10 @@ dependencies = [
 name = "rope"
 version = "0.1.0"
 dependencies = [
- "arrayvec",
  "criterion",
  "ctor",
  "gpui",
+ "heapless",
  "log",
  "rand 0.9.2",
  "rayon",
@@ -16736,8 +16752,8 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
 name = "sum_tree"
 version = "0.1.0"
 dependencies = [
- "arrayvec",
  "ctor",
+ "heapless",
  "log",
  "proptest",
  "rand 0.9.2",

Cargo.toml 🔗

@@ -480,7 +480,6 @@ aho-corasick = "1.1"
 alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "9d9640d4" }
 any_vec = "0.14"
 anyhow = "1.0.86"
-arrayvec = { version = "0.7.4", features = ["serde"] }
 ashpd = { version = "0.13", default-features = false, features = [
     "async-io",
     "notification",
@@ -564,6 +563,7 @@ futures-lite = "1.13"
 gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "37f3c0575d379c218a9c455ee67585184e40d43f" }
 git2 = { version = "0.20.1", default-features = false, features = ["vendored-libgit2"] }
 globset = "0.4"
+heapless = "0.9.2"
 handlebars = "4.3"
 heck = "0.5"
 heed = { version = "0.21.0", features = ["read-txn-no-tls"] }

crates/agent_ui/Cargo.toml 🔗

@@ -34,7 +34,7 @@ agent_servers.workspace = true
 agent_settings.workspace = true
 ai_onboarding.workspace = true
 anyhow.workspace = true
-arrayvec.workspace = true
+heapless.workspace = true
 assistant_text_thread.workspace = true
 assistant_slash_command.workspace = true
 assistant_slash_commands.workspace = true

crates/agent_ui/src/conversation_view.rs 🔗

@@ -14,7 +14,6 @@ use agent_servers::AgentServerDelegate;
 use agent_servers::{AgentServer, GEMINI_TERMINAL_AUTH_METHOD_ID};
 use agent_settings::{AgentProfileId, AgentSettings};
 use anyhow::{Result, anyhow};
-use arrayvec::ArrayVec;
 use audio::{Audio, Sound};
 use buffer_diff::BufferDiff;
 use client::zed_urls;

crates/agent_ui/src/conversation_view/thread_view.rs 🔗

@@ -8,6 +8,7 @@ use editor::actions::OpenExcerpts;
 use crate::StartThreadIn;
 use crate::message_editor::SharedSessionCapabilities;
 use gpui::{Corner, List};
+use heapless::Vec as ArrayVec;
 use language_model::{LanguageModelEffortLevel, Speed};
 use settings::update_settings_file;
 use ui::{ButtonLike, SplitButton, SplitButtonStyle, Tab};
@@ -6367,7 +6368,7 @@ impl ThreadView {
         focus_handle: &FocusHandle,
         cx: &Context<Self>,
     ) -> Div {
-        let mut seen_kinds: ArrayVec<acp::PermissionOptionKind, 3> = ArrayVec::new();
+        let mut seen_kinds: ArrayVec<acp::PermissionOptionKind, 3, u8> = ArrayVec::new();
 
         div()
             .p_1()
@@ -6417,7 +6418,7 @@ impl ThreadView {
                             return this;
                         }
 
-                        seen_kinds.push(option.kind);
+                        seen_kinds.push(option.kind).unwrap();
 
                         this.key_binding(
                             KeyBinding::for_action_in(action, focus_handle, cx)

crates/edit_prediction/Cargo.toml 🔗

@@ -17,7 +17,7 @@ cli-support = []
 [dependencies]
 ai_onboarding.workspace = true
 anyhow.workspace = true
-arrayvec.workspace = true
+heapless.workspace = true
 brotli.workspace = true
 buffer_diff.workspace = true
 client.workspace = true

crates/edit_prediction/src/edit_prediction.rs 🔗

@@ -1,5 +1,4 @@
 use anyhow::Result;
-use arrayvec::ArrayVec;
 use client::{Client, EditPredictionUsage, UserStore};
 use cloud_api_types::{OrganizationId, SubmitEditPredictionFeedbackBody};
 use cloud_llm_client::predict_edits_v3::{
@@ -27,6 +26,7 @@ use gpui::{
     http_client::{self, AsyncBody, Method},
     prelude::*,
 };
+use heapless::Vec as ArrayVec;
 use language::language_settings::all_language_settings;
 use language::{Anchor, Buffer, File, Point, TextBufferSnapshot, ToOffset, ToPoint};
 use language::{BufferSnapshot, OffsetRangeExt};
@@ -332,7 +332,7 @@ struct ProjectState {
     registered_buffers: HashMap<gpui::EntityId, RegisteredBuffer>,
     current_prediction: Option<CurrentEditPrediction>,
     next_pending_prediction_id: usize,
-    pending_predictions: ArrayVec<PendingPrediction, 2>,
+    pending_predictions: ArrayVec<PendingPrediction, 2, u8>,
     debug_tx: Option<mpsc::UnboundedSender<DebugEvent>>,
     last_edit_prediction_refresh: Option<(EntityId, Instant)>,
     last_jump_prediction_refresh: Option<(EntityId, Instant)>,
@@ -2311,18 +2311,24 @@ impl EditPredictionStore {
         });
 
         if project_state.pending_predictions.len() < max_pending_predictions {
-            project_state.pending_predictions.push(PendingPrediction {
-                id: pending_prediction_id,
-                task,
-                drop_on_cancel,
-            });
+            project_state
+                .pending_predictions
+                .push(PendingPrediction {
+                    id: pending_prediction_id,
+                    task,
+                    drop_on_cancel,
+                })
+                .unwrap();
         } else {
             let pending_prediction = project_state.pending_predictions.pop().unwrap();
-            project_state.pending_predictions.push(PendingPrediction {
-                id: pending_prediction_id,
-                task,
-                drop_on_cancel,
-            });
+            project_state
+                .pending_predictions
+                .push(PendingPrediction {
+                    id: pending_prediction_id,
+                    task,
+                    drop_on_cancel,
+                })
+                .unwrap();
             project_state.cancel_pending_prediction(pending_prediction, cx);
         }
     }

crates/rope/Cargo.toml 🔗

@@ -12,7 +12,7 @@ workspace = true
 path = "src/rope.rs"
 
 [dependencies]
-arrayvec = "0.7.1"
+heapless.workspace = true
 log.workspace = true
 rayon.workspace = true
 sum_tree.workspace = true

crates/rope/src/chunk.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{OffsetUtf16, Point, PointUtf16, TextSummary, Unclipped};
-use arrayvec::ArrayString;
+use heapless::String as ArrayString;
 use std::{cmp, ops::Range};
 use sum_tree::Bias;
 use unicode_segmentation::GraphemeCursor;
@@ -29,7 +29,7 @@ pub struct Chunk {
     newlines: Bitmap,
     /// If bit[i] is set, then the character at index i is an ascii tab.
     tabs: Bitmap,
-    pub text: ArrayString<MAX_BASE>,
+    pub text: ArrayString<MAX_BASE, u8>,
 }
 
 #[inline(always)]
@@ -47,7 +47,11 @@ impl Chunk {
 
     #[inline(always)]
     pub fn new(text: &str) -> Self {
-        let text = ArrayString::from(text).unwrap();
+        let text = {
+            let mut buf = ArrayString::new();
+            buf.push_str(text).unwrap();
+            buf
+        };
 
         const CHUNK_SIZE: usize = 8;
 
@@ -118,7 +122,7 @@ impl Chunk {
         self.chars_utf16 |= slice.chars_utf16 << base_ix;
         self.newlines |= slice.newlines << base_ix;
         self.tabs |= slice.tabs << base_ix;
-        self.text.push_str(slice.text);
+        self.text.push_str(slice.text).unwrap();
     }
 
     #[inline(always)]
@@ -137,9 +141,9 @@ impl Chunk {
         self.newlines = slice.newlines | (self.newlines << shift);
         self.tabs = slice.tabs | (self.tabs << shift);
 
-        let mut new_text = ArrayString::<MAX_BASE>::new();
-        new_text.push_str(slice.text);
-        new_text.push_str(&self.text);
+        let mut new_text = ArrayString::<MAX_BASE, u8>::new();
+        new_text.push_str(slice.text).unwrap();
+        new_text.push_str(&self.text).unwrap();
         self.text = new_text;
     }
 

crates/rope/src/rope.rs 🔗

@@ -4,7 +4,7 @@ mod point;
 mod point_utf16;
 mod unclipped;
 
-use arrayvec::ArrayVec;
+use heapless::Vec as ArrayVec;
 use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
 use std::{
     cmp, fmt, io, mem,
@@ -184,7 +184,7 @@ impl Rope {
             return self.push_large(text);
         }
         // 16 is enough as otherwise we will hit the branch above
-        let mut new_chunks = ArrayVec::<_, NUM_CHUNKS>::new();
+        let mut new_chunks = ArrayVec::<_, NUM_CHUNKS, u8>::new();
 
         while !text.is_empty() {
             let mut split_ix = cmp::min(chunk::MAX_BASE, text.len());
@@ -192,7 +192,7 @@ impl Rope {
                 split_ix -= 1;
             }
             let (chunk, remainder) = text.split_at(split_ix);
-            new_chunks.push(chunk);
+            new_chunks.push(chunk).unwrap();
             text = remainder;
         }
         self.chunks

crates/sum_tree/Cargo.toml 🔗

@@ -14,7 +14,7 @@ path = "src/sum_tree.rs"
 doctest = false
 
 [dependencies]
-arrayvec = "0.7.1"
+heapless.workspace = true
 rayon.workspace = true
 log.workspace = true
 ztracing.workspace = true

crates/sum_tree/src/cursor.rs 🔗

@@ -1,5 +1,5 @@
 use super::*;
-use arrayvec::ArrayVec;
+use heapless::Vec as ArrayVec;
 use std::{cmp::Ordering, mem, sync::Arc};
 use ztracing::instrument;
 
@@ -29,7 +29,7 @@ impl<T: Item + fmt::Debug, D: fmt::Debug> fmt::Debug for StackEntry<'_, T, D> {
 #[derive(Clone)]
 pub struct Cursor<'a, 'b, T: Item, D> {
     tree: &'a SumTree<T>,
-    stack: ArrayVec<StackEntry<'a, T, D>, 16>,
+    stack: ArrayVec<StackEntry<'a, T, D>, 16, u8>,
     pub position: D,
     did_seek: bool,
     at_end: bool,
@@ -53,7 +53,7 @@ where
 
 pub struct Iter<'a, T: Item> {
     tree: &'a SumTree<T>,
-    stack: ArrayVec<StackEntry<'a, T, ()>, 16>,
+    stack: ArrayVec<StackEntry<'a, T, ()>, 16, u8>,
 }
 
 impl<'a, 'b, T, D> Cursor<'a, 'b, T, D>
@@ -231,11 +231,13 @@ where
             self.position = D::zero(self.cx);
             self.at_end = self.tree.is_empty();
             if !self.tree.is_empty() {
-                self.stack.push(StackEntry {
-                    tree: self.tree,
-                    index: self.tree.0.child_summaries().len() as u32,
-                    position: D::from_summary(self.tree.summary(), self.cx),
-                });
+                self.stack
+                    .push(StackEntry {
+                        tree: self.tree,
+                        index: self.tree.0.child_summaries().len() as u32,
+                        position: D::from_summary(self.tree.summary(), self.cx),
+                    })
+                    .unwrap_oob();
             }
         }
 
@@ -267,11 +269,13 @@ where
                 Node::Internal { child_trees, .. } => {
                     if descending {
                         let tree = &child_trees[entry.index()];
-                        self.stack.push(StackEntry {
-                            position: D::zero(self.cx),
-                            tree,
-                            index: tree.0.child_summaries().len() as u32 - 1,
-                        })
+                        self.stack
+                            .push(StackEntry {
+                                position: D::zero(self.cx),
+                                tree,
+                                index: tree.0.child_summaries().len() as u32 - 1,
+                            })
+                            .unwrap_oob();
                     }
                 }
                 Node::Leaf { .. } => {
@@ -297,11 +301,13 @@ where
 
         if self.stack.is_empty() {
             if !self.at_end {
-                self.stack.push(StackEntry {
-                    tree: self.tree,
-                    index: 0,
-                    position: D::zero(self.cx),
-                });
+                self.stack
+                    .push(StackEntry {
+                        tree: self.tree,
+                        index: 0,
+                        position: D::zero(self.cx),
+                    })
+                    .unwrap_oob();
                 descend = true;
             }
             self.did_seek = true;
@@ -361,11 +367,13 @@ where
 
             if let Some(subtree) = new_subtree {
                 descend = true;
-                self.stack.push(StackEntry {
-                    tree: subtree,
-                    index: 0,
-                    position: self.position.clone(),
-                });
+                self.stack
+                    .push(StackEntry {
+                        tree: subtree,
+                        index: 0,
+                        position: self.position.clone(),
+                    })
+                    .unwrap_oob();
             } else {
                 descend = false;
                 self.stack.pop();
@@ -467,11 +475,13 @@ where
 
         if !self.did_seek {
             self.did_seek = true;
-            self.stack.push(StackEntry {
-                tree: self.tree,
-                index: 0,
-                position: D::zero(self.cx),
-            });
+            self.stack
+                .push(StackEntry {
+                    tree: self.tree,
+                    index: 0,
+                    position: D::zero(self.cx),
+                })
+                .unwrap_oob();
         }
 
         let mut ascending = false;
@@ -503,11 +513,13 @@ where
                             entry.index += 1;
                             entry.position = self.position.clone();
                         } else {
-                            self.stack.push(StackEntry {
-                                tree: child_tree,
-                                index: 0,
-                                position: self.position.clone(),
-                            });
+                            self.stack
+                                .push(StackEntry {
+                                    tree: child_tree,
+                                    index: 0,
+                                    position: self.position.clone(),
+                                })
+                                .unwrap_oob();
                             ascending = false;
                             continue 'outer;
                         }
@@ -578,11 +590,13 @@ impl<'a, T: Item> Iterator for Iter<'a, T> {
         let mut descend = false;
 
         if self.stack.is_empty() {
-            self.stack.push(StackEntry {
-                tree: self.tree,
-                index: 0,
-                position: (),
-            });
+            self.stack
+                .push(StackEntry {
+                    tree: self.tree,
+                    index: 0,
+                    position: (),
+                })
+                .unwrap_oob();
             descend = true;
         }
 
@@ -611,11 +625,13 @@ impl<'a, T: Item> Iterator for Iter<'a, T> {
 
             if let Some(subtree) = new_subtree {
                 descend = true;
-                self.stack.push(StackEntry {
-                    tree: subtree,
-                    index: 0,
-                    position: (),
-                });
+                self.stack
+                    .push(StackEntry {
+                        tree: subtree,
+                        index: 0,
+                        position: (),
+                    })
+                    .unwrap_oob();
             } else {
                 descend = false;
                 self.stack.pop();
@@ -748,8 +764,8 @@ trait SeekAggregate<'a, T: Item> {
 
 struct SliceSeekAggregate<T: Item> {
     tree: SumTree<T>,
-    leaf_items: ArrayVec<T, { 2 * TREE_BASE }>,
-    leaf_item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
+    leaf_items: ArrayVec<T, { 2 * TREE_BASE }, u8>,
+    leaf_item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8>,
     leaf_summary: T::Summary,
 }
 
@@ -786,8 +802,8 @@ impl<T: Item> SeekAggregate<'_, T> for SliceSeekAggregate<T> {
         summary: &T::Summary,
         cx: <T::Summary as Summary>::Context<'_>,
     ) {
-        self.leaf_items.push(item.clone());
-        self.leaf_item_summaries.push(summary.clone());
+        self.leaf_items.push(item.clone()).unwrap_oob();
+        self.leaf_item_summaries.push(summary.clone()).unwrap_oob();
         Summary::add_summary(&mut self.leaf_summary, summary, cx);
     }
     fn push_tree(

crates/sum_tree/src/sum_tree.rs 🔗

@@ -3,8 +3,8 @@ mod cursor;
 pub mod property_test;
 mod tree_map;
 
-use arrayvec::ArrayVec;
 pub use cursor::{Cursor, FilterCursor, Iter};
+use heapless::Vec as ArrayVec;
 use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator as _};
 use std::marker::PhantomData;
 use std::mem;
@@ -17,6 +17,17 @@ pub const TREE_BASE: usize = 2;
 #[cfg(not(test))]
 pub const TREE_BASE: usize = 6;
 
+// Helper for when we cannot use ArrayVec::<T>::push().unwrap() as T doesn't impl Debug
+trait CapacityResultExt {
+    fn unwrap_oob(self);
+}
+
+impl<T> CapacityResultExt for Result<(), T> {
+    fn unwrap_oob(self) {
+        self.unwrap_or_else(|_| panic!("item should fit into fixed size ArrayVec"))
+    }
+}
+
 /// An item that can be stored in a [`SumTree`]
 ///
 /// Must be summarized by a type that implements [`Summary`]
@@ -243,8 +254,9 @@ impl<T: Item> SumTree<T> {
 
         let mut iter = iter.into_iter().fuse().peekable();
         while iter.peek().is_some() {
-            let items: ArrayVec<T, { 2 * TREE_BASE }> = iter.by_ref().take(2 * TREE_BASE).collect();
-            let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> =
+            let items: ArrayVec<T, { 2 * TREE_BASE }, u8> =
+                iter.by_ref().take(2 * TREE_BASE).collect();
+            let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8> =
                 items.iter().map(|item| item.summary(cx)).collect();
 
             let mut summary = item_summaries[0].clone();
@@ -284,8 +296,8 @@ impl<T: Item> SumTree<T> {
                 };
                 let child_summary = child_node.summary();
                 <T::Summary as Summary>::add_summary(summary, child_summary, cx);
-                child_summaries.push(child_summary.clone());
-                child_trees.push(child_node);
+                child_summaries.push(child_summary.clone()).unwrap_oob();
+                child_trees.push(child_node.clone()).unwrap_oob();
 
                 if child_trees.len() == 2 * TREE_BASE {
                     parent_nodes.extend(current_parent_node.take());
@@ -315,8 +327,8 @@ impl<T: Item> SumTree<T> {
             .into_par_iter()
             .chunks(2 * TREE_BASE)
             .map(|items| {
-                let items: ArrayVec<T, { 2 * TREE_BASE }> = items.into_iter().collect();
-                let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> =
+                let items: ArrayVec<T, { 2 * TREE_BASE }, u8> = items.into_iter().collect();
+                let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8> =
                     items.iter().map(|item| item.summary(cx)).collect();
                 let mut summary = item_summaries[0].clone();
                 for item_summary in &item_summaries[1..] {
@@ -337,9 +349,9 @@ impl<T: Item> SumTree<T> {
                 .into_par_iter()
                 .chunks(2 * TREE_BASE)
                 .map(|child_nodes| {
-                    let child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }> =
+                    let child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }, u8> =
                         child_nodes.into_iter().collect();
-                    let child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> = child_trees
+                    let child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8> = child_trees
                         .iter()
                         .map(|child_tree| child_tree.summary().clone())
                         .collect();
@@ -798,14 +810,16 @@ impl<T: Item> SumTree<T> {
                 <T::Summary as Summary>::add_summary(summary, other_node.summary(), cx);
 
                 let height_delta = *height - other_node.height();
-                let mut summaries_to_append = ArrayVec::<T::Summary, { 2 * TREE_BASE }>::new();
-                let mut trees_to_append = ArrayVec::<SumTree<T>, { 2 * TREE_BASE }>::new();
+                let mut summaries_to_append = ArrayVec::<T::Summary, { 2 * TREE_BASE }, u8>::new();
+                let mut trees_to_append = ArrayVec::<SumTree<T>, { 2 * TREE_BASE }, u8>::new();
                 if height_delta == 0 {
                     summaries_to_append.extend(other_node.child_summaries().iter().cloned());
                     trees_to_append.extend(other_node.child_trees().iter().cloned());
                 } else if height_delta == 1 && !other_node.is_underflowing() {
-                    summaries_to_append.push(other_node.summary().clone());
-                    trees_to_append.push(other)
+                    summaries_to_append
+                        .push(other_node.summary().clone())
+                        .unwrap_oob();
+                    trees_to_append.push(other).unwrap_oob();
                 } else {
                     let tree_to_append = child_trees
                         .last_mut()
@@ -815,15 +829,17 @@ impl<T: Item> SumTree<T> {
                         child_trees.last().unwrap().0.summary().clone();
 
                     if let Some(split_tree) = tree_to_append {
-                        summaries_to_append.push(split_tree.0.summary().clone());
-                        trees_to_append.push(split_tree);
+                        summaries_to_append
+                            .push(split_tree.0.summary().clone())
+                            .unwrap_oob();
+                        trees_to_append.push(split_tree).unwrap_oob();
                     }
                 }
 
                 let child_count = child_trees.len() + trees_to_append.len();
                 if child_count > 2 * TREE_BASE {
-                    let left_summaries: ArrayVec<_, { 2 * TREE_BASE }>;
-                    let right_summaries: ArrayVec<_, { 2 * TREE_BASE }>;
+                    let left_summaries: ArrayVec<_, { 2 * TREE_BASE }, u8>;
+                    let right_summaries: ArrayVec<_, { 2 * TREE_BASE }, u8>;
                     let left_trees;
                     let right_trees;
 
@@ -868,7 +884,7 @@ impl<T: Item> SumTree<T> {
                     let left_items;
                     let right_items;
                     let left_summaries;
-                    let right_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>;
+                    let right_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8>;
 
                     let midpoint = (child_count + child_count % 2) / 2;
                     {
@@ -933,8 +949,10 @@ impl<T: Item> SumTree<T> {
             *child_summaries.first_mut().unwrap() = first.summary().clone();
             if let Some(tree) = res {
                 if child_trees.len() < 2 * TREE_BASE {
-                    child_summaries.insert(0, tree.summary().clone());
-                    child_trees.insert(0, tree);
+                    child_summaries
+                        .insert(0, tree.summary().clone())
+                        .unwrap_oob();
+                    child_trees.insert(0, tree).unwrap_oob();
                     None
                 } else {
                     let new_child_summaries = {
@@ -1016,7 +1034,7 @@ impl<T: Item> SumTree<T> {
                         .iter()
                         .chain(child_summaries.iter())
                         .cloned();
-                    let left_summaries: ArrayVec<_, { 2 * TREE_BASE }> =
+                    let left_summaries: ArrayVec<_, { 2 * TREE_BASE }, u8> =
                         all_summaries.by_ref().take(midpoint).collect();
                     *child_summaries = all_summaries.collect();
 
@@ -1065,7 +1083,7 @@ impl<T: Item> SumTree<T> {
                         .iter()
                         .chain(item_summaries.iter())
                         .cloned();
-                    let left_summaries: ArrayVec<_, { 2 * TREE_BASE }> =
+                    let left_summaries: ArrayVec<_, { 2 * TREE_BASE }, u8> =
                         all_summaries.by_ref().take(midpoint).collect();
                     *item_summaries = all_summaries.collect();
 
@@ -1088,11 +1106,11 @@ impl<T: Item> SumTree<T> {
     ) -> Self {
         let height = left.0.height() + 1;
         let mut child_summaries = ArrayVec::new();
-        child_summaries.push(left.0.summary().clone());
-        child_summaries.push(right.0.summary().clone());
+        child_summaries.push(left.0.summary().clone()).unwrap_oob();
+        child_summaries.push(right.0.summary().clone()).unwrap_oob();
         let mut child_trees = ArrayVec::new();
-        child_trees.push(left);
-        child_trees.push(right);
+        child_trees.push(left).unwrap_oob();
+        child_trees.push(right).unwrap_oob();
         SumTree(Arc::new(Node::Internal {
             height,
             summary: sum(child_summaries.iter(), cx),
@@ -1252,13 +1270,13 @@ pub enum Node<T: Item> {
     Internal {
         height: u8,
         summary: T::Summary,
-        child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
-        child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }>,
+        child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8>,
+        child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }, u8>,
     },
     Leaf {
         summary: T::Summary,
-        items: ArrayVec<T, { 2 * TREE_BASE }>,
-        item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
+        items: ArrayVec<T, { 2 * TREE_BASE }, u8>,
+        item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }, u8>,
     },
 }
 
@@ -1323,14 +1341,14 @@ impl<T: Item> Node<T> {
         }
     }
 
-    fn child_trees(&self) -> &ArrayVec<SumTree<T>, { 2 * TREE_BASE }> {
+    fn child_trees(&self) -> &ArrayVec<SumTree<T>, { 2 * TREE_BASE }, u8> {
         match self {
             Node::Internal { child_trees, .. } => child_trees,
             Node::Leaf { .. } => panic!("Leaf nodes have no child trees"),
         }
     }
 
-    fn items(&self) -> &ArrayVec<T, { 2 * TREE_BASE }> {
+    fn items(&self) -> &ArrayVec<T, { 2 * TREE_BASE }, u8> {
         match self {
             Node::Leaf { items, .. } => items,
             Node::Internal { .. } => panic!("Internal nodes have no items"),