louis-e/arnis · info

scrap metal list is non-empty

Error message

scrap metal list is non-empty

What it means

`build_scrap_metal_item` picks a metal from a hard-coded 3-element array with `metals.choose(rng).expect("scrap metal list is non-empty")`. `slice::choose` returns None only for an empty slice, so this is an internal invariant assertion — it can never fire while the literal array stays non-empty.

Source

Thrown at src/element_processing/amenities.rs:791

            "minecraft:paper",
            slot,
            rng.random_range(1..=10),
        )),
        RecyclingLootKind::GlassBlock => Some(build_glass_item(false, slot, rng)),
        RecyclingLootKind::GlassPane => Some(build_glass_item(true, slot, rng)),
        RecyclingLootKind::LeatherArmor => {
            Some(build_leather_item(random_leather_piece(rng), slot, rng))
        }
        RecyclingLootKind::EmptyBucket => Some(make_basic_item("minecraft:bucket", slot, 1)),
        RecyclingLootKind::LeatherBoots => Some(build_leather_item(LeatherPiece::Boots, slot, rng)),
        RecyclingLootKind::ScrapMetal => Some(build_scrap_metal_item(slot, rng)),
        RecyclingLootKind::GreenWaste => Some(build_green_waste_item(slot, rng)),
    }
}

fn build_scrap_metal_item(slot: i8, rng: &mut impl Rng) -> HashMap<String, Value> {
    let metals = ["copper_ingot", "iron_ingot", "gold_ingot"];
    let metal = metals.choose(rng).expect("scrap metal list is non-empty");
    let count = rng.random_range(1..=3);
    make_basic_item(&format!("minecraft:{metal}"), slot, count)
}

fn build_green_waste_item(slot: i8, rng: &mut impl Rng) -> HashMap<String, Value> {
    #[allow(clippy::match_same_arms)]
    let (id, count) = match rng.random_range(0..8) {
        0 => ("minecraft:tall_grass", rng.random_range(1..=4)),
        1 => ("minecraft:sweet_berries", rng.random_range(2..=6)),
        2 => ("minecraft:oak_sapling", rng.random_range(1..=2)),
        3 => ("minecraft:birch_sapling", rng.random_range(1..=2)),
        4 => ("minecraft:spruce_sapling", rng.random_range(1..=2)),
        5 => ("minecraft:jungle_sapling", rng.random_range(1..=2)),
        6 => ("minecraft:acacia_sapling", rng.random_range(1..=2)),
        _ => ("minecraft:dark_oak_sapling", rng.random_range(1..=2)),
    };

    // 25% chance to replace with seeds instead

View on GitHub (pinned to 34048924d9)

Solutions

  1. Keep the `metals` literal non-empty; it is an invariant of the function
  2. If the list becomes dynamic, check `!metals.is_empty()` before `choose` and return a default item otherwise
  3. Replace `expect` with a compile-time or debug_assert guard during refactors

Example fix

// before
let metal = metals.choose(rng).expect("scrap metal list is non-empty");
// after
assert!(!metals.is_empty());
let metal = metals.choose(rng).expect("scrap metal list is non-empty");
Defensive patterns

Strategy: validation

Validate before calling

assert!(!metals.is_empty(), "scrap metals palette must not be empty");

Type guard

fn non_empty<T>(s: &[T]) -> bool { !s.is_empty() }

Prevention

When it happens

Trigger: Only if the `metals` array is edited to be empty (e.g. an over-eager refactor removes all entries) or the function is changed to build the list dynamically and it ends up empty.

Common situations: Code modification/regression, not runtime user input — users of the released library should never see this panic.

Related errors


AI-assisted analysis of louis-e/arnis@34048924d9 (2026-09-03). Data as JSON: /api/errors/93560c495c3c3f5e. Report an issue: GitHub.