From bfba9bdb2709e75f2677213217a151d3327ad157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Sun, 5 Apr 2026 13:30:48 -0700 Subject: [PATCH] docs: document invariants behind load-bearing expect()s in get_digest_of The hasher-stack machinery in get_digest_of contains several expect() calls whose soundness depends on walkdir's sorted pre-order traversal and the structure of the loop. The invariants held but were not written down, making future refactors risky. Document at each site why the expect is unreachable, and explain the overall stack invariant at the top of the function. --- src/lib.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 25102b9..f19bc14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,14 +211,13 @@ where D: digest::Digest + digest::FixedOutput, { pub fn get_digest_of(&self, root_path: &Path) -> Result, DigestError> { + // Stack of one hasher per open directory from root to the current entry. let mut hashers = vec![]; - // pop the top hasher and output it to the one just above it + // pop the top hasher and fold it into the one above fn flush_up_one_level(hashers: &mut Vec) { - let hasher = hashers.pop().expect("must not be empty yet"); - let h2 = hashers - .last_mut() - .expect("must not happen"); + let hasher = hashers.pop().expect("len >= 2 at call site"); + let h2 = hashers.last_mut().expect("len >= 2 at call site"); ::update(h2, hasher.finalize_fixed().as_slice()); } @@ -263,10 +262,10 @@ where // names and additional data go the hasher above the one we just prepared if 0 < depth { let mut name_hasher = D::new(); - // name + // name — non-root entries always have a file_name hash_osstr( &mut name_hasher, - entry.path().file_name().expect("must have a file_name"), + entry.path().file_name().expect("non-root entry"), )?; // additional data (optional) — folded into the entry-name hash // per the README algorithm: H(entry_name || 0 || additional_data) @@ -277,26 +276,26 @@ where used: false, }, )?; - let parent_hasher = hashers.get_mut(depth - 1).expect("must not happen"); + let parent_hasher = hashers.get_mut(depth - 1).expect("parent exists for depth > 0"); ::update( parent_hasher, name_hasher.finalize_fixed().as_slice(), ); } - // content + // content — hasher for this entry was just pushed above if file_type.is_file() { self.read_content_of_file( entry.path(), - hashers.last_mut().expect("must not happen"), + hashers.last_mut().expect("just pushed"), )?; } else if file_type.is_symlink() { self.read_content_of_symlink( entry.path(), - hashers.last_mut().expect("must not happen"), + hashers.last_mut().expect("just pushed"), )?; } else if file_type.is_dir() { - let hasher = hashers.last_mut().expect("must not happen"); + let hasher = hashers.last_mut().expect("just pushed"); ::update(hasher, b"D"); } else { return Err(DigestError::FileNotSupported( @@ -305,11 +304,12 @@ where } } + // Drain nested hashers upward until only the root remains. loop { if hashers.len() == 1 { return Ok(hashers .pop() - .expect("must not fail") + .expect("len == 1") .finalize_fixed() .to_vec()); }