SeaQL/sea-orm · error

index out of bounds: the HasMany is Unloaded (index: {index}

Error message

index out of bounds: the HasMany is Unloaded (index: {index})

What it means

The `Index<usize>` implementation for `HasMany<E>` (the loaded-model relation wrapper) panics when the relation is `Unloaded` — no child models were fetched — and code indexes into it. Without a loaded collection there is no element to return, so it is reported as out of bounds.

Source

Thrown at sea-orm-sync/src/entity/compound/has_many.rs:102

    }
}

impl<E: EntityTrait> From<HasMany<E>> for Option<Vec<E::ModelEx>> {
    fn from(value: HasMany<E>) -> Self {
        match value {
            HasMany::Loaded(models) => Some(models),
            HasMany::Unloaded => None,
        }
    }
}

impl<E: EntityTrait> Index<usize> for HasMany<E> {
    type Output = E::ModelEx;

    fn index(&self, index: usize) -> &Self::Output {
        match self {
            HasMany::Unloaded => {
                panic!("index out of bounds: the HasMany is Unloaded (index: {index})")
            }
            HasMany::Loaded(items) => items.index(index),
        }
    }
}

impl<E: EntityTrait> IntoIterator for HasMany<E> {
    type Item = E::ModelEx;
    type IntoIter = std::vec::IntoIter<E::ModelEx>;

    fn into_iter(self) -> Self::IntoIter {
        match self {
            HasMany::Loaded(models) => models.into_iter(),
            HasMany::Unloaded => Vec::new().into_iter(),
        }
    }
}

View on GitHub (pinned to e29bcd1b41)

Solutions

  1. Eager-load the relation: add `.with(children::Entity)` to the loader query before indexing.
  2. Match on `HasMany::Loaded(items)` vs `Unloaded` and handle the unloaded case.
  3. Iterate only when loaded, or fall back to a separate query fetching children by foreign key.
  4. Update the code path that was supposed to populate the relation.

Example fix

// before
let user = user::Entity::load().filter_by_id(id).one(db)?.unwrap();
let first = &user.posts[0]; // panics: posts Unloaded
// after
let user = user::Entity::load().filter_by_id(id).with(post::Entity).one(db)?.unwrap();
let first = user.posts.get(0);
Defensive patterns

Strategy: validation

Validate before calling

// Guard: only index when the HasMany is Loaded
let items = match &user.posts {
    HasMany::Loaded(v) => Some(v.as_slice()),
    HasMany::Unloaded => None,
};

Type guard

fn as_models<'a, E: EntityTrait>(h: &'a HasMany<E>) -> Option<&'a [E::ModelEx]> {
    match h { HasMany::Loaded(v) => Some(v), HasMany::Unloaded => None }
}

Try / catch

// Avoid catching the panic; check load state first
if let Some(posts) = as_models(&user.posts) {
    if let Some(first) = posts.get(0) { /* use first */ }
}

Prevention

When it happens

Trigger: `parent.children[index]` on a `HasMany` field in `Unloaded` state, i.e. the query did not include `.with(children::Entity)` or the relation was never assigned.

Common situations: Accessing relation collections on models loaded without eager loading; models built in memory (tests, fixtures) where relations were never populated; assuming relations auto-load lazily.

Related errors


AI-assisted analysis of SeaQL/sea-orm@e29bcd1b41 (2026-09-10). Data as JSON: /api/errors/9eb53ae9fbc35cc0. Report an issue: GitHub.