iced-rs/iced · error
Caches must not be empty
Error message
Caches must not be empty
What it means
The syntect-backed Highlighter keeps parser snapshots (ParseState, ScopeStack) every LINES_PER_SNAPSHOT = 50 lines in caches: Vec<...>. highlight_line asserts with caches.last().expect("Caches must not be empty") (highlighter/src/lib.rs:89) that at least one snapshot exists. The stock lifecycle (new, update, change_line) always keeps the vec non-empty - change_line truncates then pushes - so this panic signals a broken invariant: a bug in the iced/highlighter version in use or a hand-written Highlighter trait implementation driving the editor.
Solutions
- Use only the stock lifecycle: Highlighter::new(Settings), update, change_line, highlight_line
- If you implement the Highlighter trait yourself, guarantee caches always holds at least one entry before any highlight_line call
- Call change_line(line) whenever highlighting jumps to a new region instead of only letting current_line advance
- If hit with unmodified iced, upgrade iced/iced_highlighter and report the panic with a reproducer
Example fix
// before - custom Highlighter impl with empty caches
fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> {
let (parser, stack) = self.caches.last().expect("Caches must not be empty");
// ...
}
// after - keep the invariant: always seed one snapshot
fn new(settings: &Settings) -> Self {
Self {
caches: vec![(ParseState::new(syntax), ScopeStack::new())],
current_line: 0,
// ...
}
} Defensive patterns
Strategy: validation
Validate before calling
// when highlighting jumps to a new region, resync snapshots first
if target_line / 50 != highlighter.current_line() / 50 {
highlighter.change_line(target_line);
}
highlighter.highlight_line(line_text); Prevention
- Drive the highlighter only through new/update/change_line/highlight_line
- In custom Highlighter impls, seed one snapshot in the constructor and keep it non-empty
- Call change_line when jumping instead of relying on current_line advancing
- Report stock-code hits upstream and upgrade iced
When it happens
Trigger: highlight_line called when the caches vec is empty - not producible via Highlighter::new/update/change_line; the line-89 expect also fires when current_line has advanced past the available snapshots (current_line / 50 >= caches.len()) while caches is somehow empty, e.g. a custom trait impl that cleared it or a fork with an off-by-one in change_line.
Common situations: Custom core::text::highlighter::Highlighter implementations that manage snapshots themselves; older/forked iced versions with change_line bugs; driving highlight_line directly in tests or fuzzing outside the editor widget lifecycle.
Related errors
- Downcast widget state
- Node configuration and count do not match
- Node configuration and split count do not match
- Compositor must be initialized
- Create color
AI-assisted analysis of iced-rs/iced@3de451447b (2026-08-17).
Data as JSON: /api/errors/456b1cb313a077a0.
Report an issue: GitHub.
Appendix: source
Thrown at highlighter/src/lib.rs:89
self.current_line = snapshot * LINES_PER_SNAPSHOT;
} else {
self.caches.truncate(1);
self.current_line = 0;
}
let (parser, stack) = self.caches.last().cloned().unwrap_or_else(|| {
(
parsing::ParseState::new(self.syntax),
parsing::ScopeStack::new(),
)
});
self.caches.push((parser, stack));
}
fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> {
if self.current_line / LINES_PER_SNAPSHOT >= self.caches.len() {
let (parser, stack) = self.caches.last().expect("Caches must not be empty");
self.caches.push((parser.clone(), stack.clone()));
}
self.current_line += 1;
let (parser, stack) = self.caches.last_mut().expect("Caches must not be empty");
let ops = parser.parse_line(line, &SYNTAXES).unwrap_or_default();
Box::new(scope_iterator(ops, line, stack, &self.highlighter))
}
fn current_line(&self) -> usize {
self.current_line
}
}
View on GitHub (pinned to 3de451447b)