Why does this iterative JSON traversal fail borrow check while the recursive version works?
⚓ Rust 📅 2025-10-31 👤 surdeus 👁️ 6I was trying to traverse and possibly transform a JSON tree (serde_json::Value) without recursion, using an explicit stack. The recursive version works fine, but when I switch to an iterative approach, the borrow checker rejects it with E0506.
I suspect this is due to the lack of regioned borrows in Rust — but I’d like to confirm whether that’s the right way to understand it, and whether there’s a known safe pattern for this kind of traversal.
Minimal reproducer
fn demo_doesnt_work(json: &mut Value) {
fn get_value(_v: &Value) -> Option<Value> {
todo!()
}
let stack = &mut Vec::<&mut Value>::new();
stack.push(json);
while let Some(node) = stack.pop() {
match node {
Value::Object(map) => match map.get("specific-key") {
Some(v) => {
if let Some(v) = get_value(v) {
*node = v;
}
}
None => {
for val in map.values_mut() {
// comment out this to shut up the borrow checker
stack.push(val); // this extends the lifetime of `map`
}
}
},
_ => continue,
}
}
}
fn demo_works(json: &mut Value) {
fn get_value(_v: &Value) -> Option<Value> {
todo!()
}
match json {
Value::Object(map) => match map.get("specific-key") {
Some(v) => {
if let Some(v) = get_value(v) {
*json = v;
}
}
None => {
for val in map.values_mut() {
demo_works(val);
}
}
},
_ => {}
}
}
Error message
error[E0506]: cannot assign to `*node` because it is borrowed
--> src/main.rs:15:25
|
10 | while let Some(node) = stack.pop() {
| ----- borrow later used here
11 | match node {
12 | Value::Object(map) => match map.get("specific-key") {
| --- `*node` is borrowed here
...
15 | *node = v;
| ^^^^^ `*node` is assigned to here but it was already borrowed
My current understanding is that in the iterative version, pushing map.values_mut() into the stack keeps multiple live mutable references to siblings under the same parent Object. So when I later attempt to assign to *node, Rust sees that the parent’s borrow is still active (through other entries in the stack). In contrast, the recursive version only ever holds one mutable borrow at a time, so it passes the borrow checker.
Unsafe attempt
This Passes Miri, but I’m not certain this is fully safe: playground link.
Questions
- Is this indeed the correct way to understand
E0506here? - Is there a known safe, efficient and natural pattern for iterative tree traversal that avoids recursion?
3 posts - 3 participants
🏷️ Rust_feed