You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中BTreeMap无法正确更新值内向量的问题排查

Rust BTreeMap更新Vec异常问题分析

问题描述

在使用Rust的BTreeMap管理Monke结构体时,程序无法正确更新结构体中inventory字段(Vec类型)的内容。逻辑上预期猴子处理完物品后会将投掷的物品添加到目标猴子的inventory中,自身inventory清空,但实际输出与预期不符。

相关代码

fn monke_do_monke_bisnis(mut monkes: BTreeMap<i32, Monke>) -> BTreeMap<i32, Monke> {
    for mut monke in monkes.to_owned() {
        println!("Monkey: {:?}", monke.0);
        let mut new_prio: f32 = 0.0;
        for i in monke.1.inventory.to_owned() {
            println!("Inventory length: {:?}", monke.1.inventory.len());
            println!("Inspected item: {:?}", i);

            let last = parse_last(i, monke.1.operation.last.clone());
            match monke.1.operation.operand {
                '*' => {
                    new_prio = ((i * last)) as f32
                }
                '/' => {
                    new_prio = ((i / last)) as f32
                }
                '+' => {
                    new_prio = ((i + last)) as f32
                }
                '-' => {
                    new_prio = ((i - last)) as f32
                }
                _ => panic!("need op bruv"),
            }
            println!("New worry level of item: {:?}", new_prio);
            
            new_prio /= 3.0;
            new_prio = new_prio.floor();
            println!("New worry level of item / 3: {:?}", new_prio);
            

            if (new_prio as i32) % monke.1.test == 0 {
                monkes.entry(monke.1.true_outcome).and_modify(|monk| {
                    monk.inventory.push(new_prio as i32);
                    println!("TRUE: Thrown to: {:?}", monk.id);                    
                    println!("Inventory of {:?}: {:?}", monk.id, monk.inventory);
                });
            } else {
                monkes.entry(monke.1.false_outcome).and_modify(|monk| {
                    monk.inventory.push(new_prio as i32);     
                    println!("FALSE: Thrown to: {:?}", monk.id);
                    println!("Inventory of {:?}: {:?}", monk.id, monk.inventory);

                });
            }
            //remove item from original monke
            monkes
                .entry(monke.1.id)
                .and_modify(|monk| {
                    monk.inventory.remove(0);
                    monk.inspect_count += 1;
                });
        }
    }
    return monkes;
}

结构体定义

#[derive(Default, Debug, Clone)]
struct Monke {
    id: i32,
    inventory: Vec<i32>,
    operation: Operation,
    test: i32,
    true_outcome: i32,
    false_outcome: i32,
    inspect_count: i32,
}

#[derive(Default, Debug, Clone)]
struct Operation {
    operand: char,
    last: String,
}

实际输出

Monke { id: 0, inventory: [20, 23, 27, 26], operation: Operation { operand: '*', last: "19" }, test: 23, true_outcome: 2, false_outcome: 3, inspect_count: 2 }
Monke { id: 1, inventory: [25], operation: Operation { operand: '+', last: "6" }, test: 19, true_outcome: 2, false_outcome: 0, inspect_count: 4 }
Monke { id: 2, inventory: [], operation: Operation { operand: '*', last: "old" }, test: 13, true_outcome: 1, false_outcome: 3, inspect_count: 3 }
Monke { id: 3, inventory: [500, 620, 1200, 3136], operation: Operation { operand: '+', last: "3" }, test: 17, true_outcome: 0, false_outcome: 1, inspect_count: 1 }

预期输出

Monke { id: 0, inventory: [20, 23, 27, 26], operation: Operation { operand: '*', last: "19" }, test: 23, true_outcome: 2, false_outcome: 3, inspect_count: 2 }
Monke { id: 1, inventory: [2080, 25, 167, 207, 401, 1046], operation: Operation { operand: '+', last: "6" }, test: 19, true_outcome: 2, false_outcome: 0, inspect_count: 4 }
Monke { id: 2, inventory: [], operation: Operation { operand: '*', last: "old" }, test: 13, true_outcome: 1, false_outcome: 3, inspect_count: 3 }
Monke { id: 3, inventory: [], operation: Operation { operand: '+', last: "3" }, test: 17, true_outcome: 0, false_outcome: 1, inspect_count: 1 }

问题成因

  1. 遍历克隆副本而非原BTreeMap
    代码中使用monkes.to_owned()创建了整个BTreeMap的深拷贝,循环处理的是函数初始状态下的猴子克隆体,完全忽略了处理过程中其他猴子投掷过来的新物品。例如猴子0处理完物品后投掷给猴子1,但循环遍历的是克隆的猴子1(初始inventory仅为[25]),不会处理原Map中猴子1已收到的新物品,最终导致猴子1的inventory状态不符合预期。

  2. 错误的元素移除逻辑
    每次处理完物品后调用monkes.entry(monke.1.id).and_modify(|monk| monk.inventory.remove(0))来移除原猴子的第一个元素,但遍历的是克隆猴子的inventory(初始状态)。如果原猴子在处理过程中收到新物品,remove(0)会错误地移除新物品而非原始待处理物品;即使没有新物品,这种依赖顺序的移除方式也容易因状态变化导致错误。

修复方案

修改循环逻辑,遍历原BTreeMap的键集合,直接操作原猴子的inventory,使用drain(..)一次性消费所有物品,避免手动移除的错误:

fn monke_do_monke_bisnis(mut monkes: BTreeMap<i32, Monke>) -> BTreeMap<i32, Monke> {
    // 克隆所有键,避免遍历原Map时的借用冲突
    let ids: Vec<i32> = monkes.keys().cloned().collect();

    for id in ids {
        // 取出原猴子的所有权,处理完后再放回Map
        let Some(mut monke) = monkes.remove(&id) else {
            continue;
        };
        println!("Monkey: {:?}", id);
        let mut new_prio: f32 = 0.0;

        // 使用drain(..)消费整个inventory,处理所有物品
        for i in monke.inventory.drain(..) {
            println!("Inspected item: {:?}", i);

            let last = parse_last(i, monke.operation.last.clone());
            new_prio = match monke.operation.operand {
                '*' => (i * last) as f32,
                '/' => (i / last) as f32,
                '+' => (i + last) as f32,
                '-' => (i - last) as f32,
                _ => panic!("need op bruv"),
            };
            println!("New worry level of item: {:?}", new_prio);
            
            new_prio /= 3.0;
            new_prio = new_prio.floor();
            println!("New worry level of item / 3: {:?}", new_prio);
            
            let target_id = if (new_prio as i32) % monke.test == 0 {
                monke.true_outcome
            } else {
                monke.false_outcome
            };

            // 向目标猴子添加物品
            monkes.entry(target_id).and_modify(|monk| {
                monk.inventory.push(new_prio as i32);
                println!("Thrown to: {:?}", monk.id);                    
                println!("Inventory of {:?}: {:?}", monk.id, monk.inventory);
            });

            monke.inspect_count += 1;
        }

        // 将处理完的猴子放回Map(inventory已被清空)
        monkes.insert(id, monke);
    }

    monkes
}

修复说明

  • 遍历初始键集合,确保每个猴子都被处理一次,且处理的是原Map中的实时状态。
  • 使用drain(..)一次性取出并消费猴子的所有inventory物品,处理后自动清空inventory,避免手动移除的错误。
  • 取出猴子的所有权进行处理,完成后放回Map,避免借用冲突问题。

内容的提问来源于stack exchange,提问作者Wadafacc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 16:06:01