SeaQL/sea-orm · error

called `BelongsTo::unwrap()` on an `Unloaded` value

Error message

called `BelongsTo::unwrap()` on an `Unloaded` value

What it means

`BelongsTo::unwrap` on the non-optional variant returns the loaded parent model but panics when the relation is `Unloaded`, i.e. the belongs-to relation was never fetched. Like `Option::unwrap`, it is intended only after confirming the relation is `Loaded`.

Source

Thrown at sea-orm-sync/src/entity/compound/belongs_to.rs:137

            Self::Unloaded => None,
        }
    }

    /// Convert into an `Option<ModelEx>`
    pub fn into_option(self) -> Option<E::ModelEx> {
        match self {
            Self::Loaded(model) => Some(*model),
            Self::Unloaded => None,
        }
    }

    /// # Panics
    ///
    /// Panics if called on an `Unloaded` value.
    pub fn unwrap(self) -> E::ModelEx {
        match self {
            Self::Loaded(model) => *model,
            Self::Unloaded => panic!("called `BelongsTo::unwrap()` on an `Unloaded` value"),
        }
    }
}

impl<E> BelongsTo<Option<E>>
where
    E: EntityTrait,
{
    /// Return true if this optional relation was loaded and no model was found.
    pub fn is_not_found(&self) -> bool {
        matches!(self, Self::Loaded(None))
    }

    /// Return true if this optional relation holds no model — i.e. it is either
    /// `Unloaded` or was loaded with no match (`is_not_found`).
    pub fn is_unloaded_or_not_found(&self) -> bool {
        matches!(self, Self::Unloaded | Self::Loaded(None))
    }

View on GitHub (pinned to e29bcd1b41)

Solutions

  1. Load the relation before unwrapping: `child::Entity::load().filter_by_id(id).with(parent::Entity).one(db)`.
  2. Match on `BelongsTo::Loaded(model)` / `Unloaded` and handle the unloaded case instead of panicking.
  3. Use a fallible accessor (e.g. `try_...` or `as_loaded()`) if available and return an error.
  4. Ensure the code path that loads parents isn't conditionally skipped.

Example fix

// before
let parent = post.author.unwrap(); // panics if unloaded
// after
let post = post::Entity::load().filter_by_id(id).with(user::Entity).one(db)?.unwrap();
let parent = post.author.unwrap();
Defensive patterns

Strategy: validation

Validate before calling

// Load the relation before unwrapping
let post = post::Entity::load().filter_by_id(id).with(user::Entity).one(db)?.unwrap();
assert!(matches!(post.author, BelongsTo::Loaded(_)), "author relation must be loaded");

Type guard

fn loaded_parent<E>(b: &BelongsTo<E>) -> Option<&E::ModelEx> {
    match b { BelongsTo::Loaded(m) => Some(m), BelongsTo::Unloaded => None }
}

Try / catch

// Avoid panic-catching; check state first
if let Some(parent) = loaded_parent(&post.author) { /* use parent */ } else { /* load it */ }

Prevention

When it happens

Trigger: Calling `child.parent.unwrap()` where `parent` is a `BelongsTo` field still in `Unloaded` state — the `with(parent::Entity)` load was skipped, or the model was built/deserialized without loading the relation.

Common situations: Forgetting `.with(...)` in a loader query then accessing the relation; models constructed in tests without loading relations; relation lost after mapping the model to another shape.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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