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 insteadView on GitHub (pinned to 34048924d9)
Solutions
- Keep the `metals` literal non-empty; it is an invariant of the function
- If the list becomes dynamic, check `!metals.is_empty()` before `choose` and return a default item otherwise
- 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
- Unit-test that all loot palettes are non-empty
- Never refactor constant arrays to empty during feature gating
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
- glass color array is non-empty
- component pushed above
- Invalid id
- Failed to create tile XZBBox
- bundled font atlas is valid
AI-assisted analysis of louis-e/arnis@34048924d9 (2026-09-03).
Data as JSON: /api/errors/93560c495c3c3f5e.
Report an issue: GitHub.