slint-ui/slint · error · ModelError

the row index is out of bounds

Error message

the row index is out of bounds (the model has {} rows)

What it means

ModelError::OutOfBounds is returned by the Model trait's row mutation functions (set_row_data, insert_rows, remove_rows) when the given row index is not valid for the model's current row count. Models in Slint are the data source behind `for` loops and repeaters; mutating a row that does not exist would corrupt the repeater's bookkeeping, so the operation is refused and the model is left unchanged.

Solutions

  1. Check `model.row_count()` before mutating and clamp/skip when the index is out of range: `if row < model.row_count() { model.set_row_data(row, v); }`.
  2. For insertion, use `model.insert_rows(model.row_count(), 1)` to append at the end rather than an arbitrary index.
  3. Re-fetch indices after any model change (use ModelNotifier/ModelRc change callbacks) instead of caching row positions across mutations.

Example fix

// before
model.set_row_data(index, new_item); // panics-free but returns OutOfBounds
// after
if index < model.row_count() {
    model.set_row_data(index, new_item);
}
Defensive patterns

Strategy: validation

Validate before calling

if row >= model.row_count() {
    return; // or clamp
}
model.set_row_data(row, value);

Type guard

fn valid_row(model: &dyn Model<R>, row: usize) -> bool {
    row < model.row_count()
}

Try / catch

match model.set_row_data(row, value) {
    Ok(()) => {},
    Err(ModelError::OutOfBounds(_)) => eprintln!("row {row} no longer exists"),
}

Prevention

When it happens

Trigger: Calling `model.set_row_data(i, ...)` with `i >= model.row_count()`; `insert_rows`/`remove_rows` with a row index greater than the current row count; mutating a model from a stale index captured before another thread or binding shrank the model.

Common situations: Race between reading `row_count()` and mutating; off-by-one when appending (using `row_count()` instead of `row_count()` as insert position is fine, but `row_count()+1` is not); removing rows while a UI callback still holds an old index; wrapping a filtered/subset model and using indices from the parent model.

Related errors


AI-assisted analysis of slint-ui/slint@3a7e700487 (2026-09-16). Data as JSON: /api/errors/fd3d02506e57edb1. Report an issue: GitHub.

Appendix: source

Thrown at internal/core/model.rs:31

use alloc::vec::Vec;
use core::cell::OnceCell;
use core::cell::{Cell, RefCell};
use core::pin::Pin;
#[allow(unused)]
use euclid::num::{Ceil, Floor};
pub use model_peer::*;
use pin_project::pin_project;

mod adapters;
mod model_peer;
mod repeater;

pub use repeater::{Conditional, ListViewProperties, RepeatedItemTree, Repeater, RepeaterTracker};

/// Error returned by the row mutation functions of [`Model`] when the modification
/// was not applied.
#[derive(Debug, Clone, Eq, PartialEq, derive_more::Error, derive_more::Display)]
pub struct ModelError(#[error(not(source))] ErrorImpl);

#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
enum ErrorImpl {
    #[display("the row index is out of bounds (the model has {_0} rows)")]
    OutOfBounds(usize),
    #[display("the model {_0} does not support this modification")]
    Unsupported(alloc::borrow::Cow<'static, str>),
}

impl ModelError {
    /// The row index is out of the model's bounds. `row_count` is the model's number of rows.
    pub fn out_of_bounds(row_count: usize) -> Self {
        Self(ErrorImpl::OutOfBounds(row_count))
    }

    /// The model does not support this modification.
    pub fn unsupported(model: &(impl Model + ?Sized)) -> Self {
        Self(ErrorImpl::Unsupported(core::any::type_name_of_val(model).into()))

View on GitHub (pinned to 3a7e700487)