From af123d8989cf79d0d6f858ec6ef14118594a5717 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Sun, 23 Nov 2025 18:13:03 +0000 Subject: [PATCH 1/4] refactor: `no_std` support Includes-commit: 4103e1c2bee2b0642d0601aebe989642e0d9c926 Replicated-from: https://github.com/cloudflare/trie-hard/pull/18 --- .bleep | 2 +- src/lib.rs | 14 +++++++++++--- src/u256.rs | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.bleep b/.bleep index 4c98b0b..96ca5b9 100644 --- a/.bleep +++ b/.bleep @@ -1 +1 @@ -d3eea0d95bb3d98b7624e6dbc706b117216aa493 \ No newline at end of file +218f6d164d52b11828a0fa6758f9e41838f6b045 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f59af63..751eb24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,15 +21,23 @@ unsafe_code )] #![warn(rust_2018_idioms)] +#![no_std] + +extern crate alloc; + +#[cfg(test)] +extern crate std; mod u256; -use std::{ +use alloc::{ collections::{BTreeMap, BTreeSet, VecDeque}, - ops::RangeFrom, + vec, + vec::Vec, }; +use core::ops::RangeFrom; -use u256::U256; +use self::u256::U256; #[derive(Debug, Clone)] #[repr(transparent)] diff --git a/src/u256.rs b/src/u256.rs index bfee984..48b76d6 100644 --- a/src/u256.rs +++ b/src/u256.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{ +use core::{ cmp::Ordering, ops::{ Add, AddAssign, BitAnd, BitOrAssign, Shl, ShlAssign, Sub, SubAssign, From 878ba71b19ec044dee26c63b03841e72871f329c Mon Sep 17 00:00:00 2001 From: Palash Nigam Date: Mon, 30 Dec 2024 12:19:38 +0000 Subject: [PATCH 2/4] README.md: typo Includes-commit: 696921725dcd13bd2eb2ddebc869a2251725c24f Replicated-from: https://github.com/cloudflare/trie-hard/pull/15 --- .bleep | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bleep b/.bleep index 96ca5b9..fa8be57 100644 --- a/.bleep +++ b/.bleep @@ -1 +1 @@ -218f6d164d52b11828a0fa6758f9e41838f6b045 \ No newline at end of file +d140858209bfa32730683dd6e0b30083f5240fd5 \ No newline at end of file diff --git a/README.md b/README.md index 9655461..1460b67 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ child_index = (( 0b00010 - 1) & 0b00011 ).count_ones() // = 1 The next node is the one at **index = 1** in the current node's child array. -The reason this works may actually easier to understand with a larger example. Consider this node from a larger trie (unrelated to our original example). +The reason this works may actually be easier to understand with a larger example. Consider this node from a larger trie (unrelated to our original example). ```rust TrieNode { From 749e1119243bd05eade1619e03eab890bc5e2e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Flemstr=C3=B6m?= Date: Fri, 7 Nov 2025 10:47:50 +0000 Subject: [PATCH 3/4] Add support for `trie.ancestor()` --- Add rustdoc for ancestor() --- Add another test case for ancestor() Includes-commit: 08e6a18cb7ee73a750fcf17840057f8915aa7f56 Includes-commit: 409cacbf49d6cb9e05518f9708fc4dbe76a177dd Includes-commit: db2ac9da3a7f0561b3eaa5523b4b33963d111267 Replicated-from: https://github.com/cloudflare/trie-hard/pull/17 --- .bleep | 2 +- src/lib.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/.bleep b/.bleep index fa8be57..c18e76a 100644 --- a/.bleep +++ b/.bleep @@ -1 +1 @@ -d140858209bfa32730683dd6e0b30083f5240fd5 \ No newline at end of file +d5e93b09c79b14282a4900ada8d6c485ed8d6577 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 751eb24..6d9a17e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,6 +327,35 @@ where TrieHard::U256(trie) => TrieIter::U256(trie.prefix_search(prefix)), } } + + /// Find the closest ancestor to the given key, where an ancestor is defined as the longest + /// string present in the trie that appears as a prefix of the given key. + /// + /// ``` + /// # use trie_hard::TrieHard; + /// let trie = ["dad", "ant", "and", "dot", "do"] + /// .into_iter() + /// .collect::>(); + /// + /// assert_eq!( + /// trie.ancestor("dada").map(|(_, v)| v), + /// Some("dad") + /// ); + /// assert_eq!( + /// trie.ancestor("an").map(|(_, v)| v), + /// None + /// ); + /// ``` + pub fn ancestor>(&self, key: K) -> Option<(&[u8], T)> { + match self { + TrieHard::U8(trie) => trie.ancestor(key), + TrieHard::U16(trie) => trie.ancestor(key), + TrieHard::U32(trie) => trie.ancestor(key), + TrieHard::U64(trie) => trie.ancestor(key), + TrieHard::U128(trie) => trie.ancestor(key), + TrieHard::U256(trie) => trie.ancestor(key), + } + } } /// Structure used for iterative over the contents of trie @@ -591,6 +620,71 @@ macro_rules! trie_impls { TrieIterSized::new(self, node_index) } + + /// Find the closest ancestor to the given key, where an ancestor is defined as the + /// longest string present in the trie that appears as a prefix of the given key. + /// + /// ``` + /// # use trie_hard::TrieHard; + /// let trie = ["dad", "ant", "and", "dot", "do"] + /// .into_iter() + /// .collect::>(); + /// + /// let TrieHard::U8(sized_trie) = trie else { + /// unreachable!() + /// }; + /// + /// assert_eq!( + /// sized_trie.ancestor("dada").map(|(_, v)| v), + /// Some("dad") + /// ); + /// assert_eq!( + /// sized_trie.ancestor("an").map(|(_, v)| v), + /// None + /// ); + /// ``` + pub fn ancestor>( + &self, + key: K, + ) -> Option<(&[u8], T)> { + self.ancestor_recurse(0, key.as_ref(), self.nodes.get(0)?) + } + + fn ancestor_recurse( + &self, + i: usize, + key: &[u8], + state: &TrieState<'a, T, $int_type>, + ) -> Option<(&[u8], T)> { + match state { + TrieState::Leaf(k, value) => { + ( + k.len() <= key.len() + && k[i..] == key[i..k.len()] + ).then_some((k, *value)) + } + TrieState::Search(search) => { + let c = key.get(i)?; + let next_state_index = search.evaluate(*c, self)?; + self.ancestor_recurse(i + 1, key, &self.nodes[next_state_index]) + } + TrieState::SearchOrLeaf(k, value, search) => { + // lambda to enable using `?` operator + let search = || { + let c = key.get(i)?; + let next_state_index = search.evaluate(*c, self)?; + self.ancestor_recurse(i + 1, key, &self.nodes[next_state_index]) + }; + + search().or_else(|| { + ( + k.len() <= key.len() + && k[i..] == key[i..k.len()] + ).then_some((k, *value)) + }) + } + } + } } impl<'a, T> TrieHardSized<'a, T, $int_type> where T: 'a + Copy { @@ -932,4 +1026,30 @@ mod tests { .collect::>(); assert_eq!(emitted, output); } + + #[rstest] + #[case(&[], "", None)] + #[case(&[""], "", Some(""))] + #[case(&["aaa", "a", ""], "", Some(""))] + #[case(&["aaa", "a", ""], "a", Some("a"))] + #[case(&["aaa", "a", ""], "aa", Some("a"))] + #[case(&["aaa", "a", ""], "aab", Some("a"))] + #[case(&["aaa", "a", ""], "aaa", Some("aaa"))] + #[case(&["aaa", "a", ""], "b", Some(""))] + #[case(&["dad", "ant", "and", "dot", "do"], "d", None)] + #[case(&["dad", "ant", "and", "dot", "do"], "dad", Some("dad"))] + #[case(&["dad", "ant", "and", "dot", "do"], "dada", Some("dad"))] + #[case(&["dad", "ant", "and", "dot", "do"], "do", Some("do"))] + #[case(&["dad", "ant", "and", "dot", "do"], "dot", Some("dot"))] + #[case(&["dad", "ant", "and", "dot", "do"], "dob", Some("do"))] + #[case(&["dad", "ant", "and", "dot", "do"], "doto", Some("dot"))] + fn test_ancestor( + #[case] input: &[&str], + #[case] key: &str, + #[case] output: Option<&str>, + ) { + let trie = input.iter().copied().collect::>(); + let emitted = trie.ancestor(key).map(|(_, v)| v); + assert_eq!(emitted, output); + } } From 06f42ddb4fd10f2c18bf3e60896373267d1a322b Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Thu, 11 Dec 2025 10:28:11 -0500 Subject: [PATCH 4/4] Disable ci tests for msrv since testing dependencies have higher msrvs --- .github/workflows/build.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83a1939..1922f74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ jobs: trie-hard: strategy: matrix: - toolchain: [nightly, 1.74, 1.80.0] + toolchain: [nightly, 1.74, 1.91.0] runs-on: ubuntu-latest # Only run on "pull_request" event for external PRs. This is to avoid # duplicate builds for PRs created from internal branches. @@ -36,13 +36,18 @@ jobs: - name: Run cargo fmt run: cargo fmt --all -- --check + - name: Run cargo build + run: cargo build + - name: Run cargo test - run: cargo test --verbose --lib --bins --tests --no-fail-fast + run: | + [[ ${{ matrix.toolchain }} != 1.74 ]] || (cargo test --verbose --lib --bins --tests --no-fail-fast) # Need to run doc tests separately. # (https://github.com/rust-lang/cargo/issues/6669) - name: Run cargo doc test - run: cargo test --verbose --doc + run: | + [[ ${{ matrix.toolchain }} != 1.74 ]] || (cargo test --verbose --doc) - name: Run cargo clippy run: | @@ -50,4 +55,4 @@ jobs: - name: Run cargo audit run: | - [[ ${{ matrix.toolchain }} != 1.80.0 ]] || (cargo install cargo-audit && cargo audit) + [[ ${{ matrix.toolchain }} == 1.91.0 ]] || (cargo install cargo-audit && cargo audit)