gleam-lang/gleam · error

pattern index

Error message

pattern index

What it means

Panic while registering all subject patterns of a multi-subject case expression: `patterns.get(i)` returned None for index i. The surrounding loop iterates `0..patterns.len()` against the same vec, so a failure means `patterns` was mutated during iteration or the loop bounds came from a different collection.

Solutions

  1. Make sure `self.register` does not mutate or replace `self.patterns` during the loop (take a snapshot or use an iterator over the original vec).
  2. Use `patterns.iter().zip(subject_variables.iter())` with collected results instead of index-based iteration to avoid shared-borrow workarounds.
  3. Add a debug assert that `patterns.len()` matches `subject_variables.len()` before the loop (the adjacent 'wrong number of subjects' expect covers the other side).

Example fix

// before
let pattern = self.register(patterns.get(i).expect("pattern index"));
// after
let Some(p) = patterns.get(i) else {
    panic!("pattern index {} out of {} patterns", i, patterns.len())
};
let pattern = self.register(p);
Defensive patterns

Strategy: validation

Validate before calling

assert_eq!(patterns.len(), subject_variables.len(), "subjects and variables out of sync");
for (pattern, var) in patterns.iter().zip(subject_variables.iter()) { /* ... */ }

Type guard

fn paired_subjects<'a>(p: &'a [Id<Pattern>], v: &'a [Variable]) -> Option<std::vec::Vec<(&'a Id<Pattern>, &'a Variable)>> {
    if p.len() == v.len() { Some(p.iter().zip(v.iter()).collect()) } else { None }
}

Try / catch

std::panic::catch_unwind(|| register_subjects(patterns)).map_err(|_| Bug::PatternIndexOutOfRange)

Prevention

When it happens

Trigger: Compiling a case expression with multiple subjects where `register` (called inside the loop) recursively triggers re-entry that drains/mutates `patterns`, or where the loop's `patterns.len()` and the vec being indexed are different collections that diverged.

Common situations: Gleam compiler contributors hit this when adding side effects inside `register` that mutate the pattern store, or after refactoring subject pattern collection so the vec is rebuilt mid-loop.

Related errors


AI-assisted analysis of gleam-lang/gleam@49f8762da5 (2026-09-14). Data as JSON: /api/errors/a9fa8c55a2508fc7. Report an issue: GitHub.

Appendix: source

Thrown at compiler-core/src/exhaustiveness.rs:3660

    }

    /// Registers a `TypedClause` as one of the branches to be compiled.
    ///
    /// If you don't have a clause and just have a simple `TypedPattern` you want
    /// to generate a decision tree for you can use `add_pattern`.
    ///
    pub fn add_clause(&mut self, branch: &TypedClause) {
        let all_patterns =
            std::iter::once(&branch.pattern).chain(branch.alternative_patterns.iter());

        for (alternative_index, patterns) in all_patterns.enumerate() {
            let mut checks = Vec::with_capacity(patterns.len());

            // We're doing the zipping ourselves instead of using iters.zip as the
            // borrow checker would complain and the only workaround would be to
            // allocate an entire new vector each time.
            for i in 0..patterns.len() {
                let pattern = self.register(patterns.get(i).expect("pattern index"));
                let var = self
                    .subject_variables
                    .get(i)
                    .expect("wrong number of subjects");
                checks.push(var.is(pattern));
            }

            let has_guard = branch.guard.is_some();
            let branch = Branch::new(self.number_of_clauses, alternative_index, checks, has_guard);
            self.branches.push(branch);
        }

        self.number_of_clauses += 1;
    }

    /// Add a single pattern as a branch to be compiled.
    ///
    /// This is useful in case one wants to check exhaustiveness of a single

View on GitHub (pinned to 49f8762da5)