SeaQL/sea-orm · error

called `BelongsTo::unwrap()` on a `Loaded(None)` value

Error message

called `BelongsTo::unwrap()` on a `Loaded(None)` value

What it means

Same optional belongs-to `unwrap` as error 17, but this panic fires when the relation IS loaded yet holds `Loaded(None)` — the foreign key is NULL or no matching parent row exists, so unwrapping an optional parent that is absent fails.

Source

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

        }
    }

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

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

impl<E> BelongsTo<E>
where
    E: EntityTrait,
    E::ActiveModelEx: From<E::ModelEx>,
{
    pub fn into_active_model(self) -> ActiveBelongsTo<E> {
        match self {
            Self::Loaded(model) => ActiveBelongsTo::set(*model),
            Self::Unloaded => ActiveBelongsTo::NotSet,
        }
    }
}

impl<E> BelongsTo<Option<E>>

View on GitHub (pinned to e29bcd1b41)

Solutions

  1. Handle the None case: match `BelongsTo::Loaded(Some(p))` and treat absent parents explicitly.
  2. If the relation should always exist, make the foreign key NOT NULL at the schema level and fix orphaned data.
  3. Use `unwrap_or_default`/return an `Option` to the caller instead of panicking.
  4. Add a cascade delete or cleanup job to prevent orphan rows.

Example fix

// before
let parent = comment.post.unwrap(); // panics on Loaded(None)
// after
let parent = match comment.post {
    BelongsTo::Loaded(Some(p)) => Some(p),
    _ => None, // nullable FK: parent may legitimately be absent
};
Defensive patterns

Strategy: fallback

Validate before calling

// Treat Loaded(None) as a legitimate outcome for nullable FKs
let parent = match comment.post {
    BelongsTo::Loaded(Some(p)) => Some(p),
    _ => None,
};

Type guard

fn has_parent<E>(b: &BelongsTo<Option<E>>) -> bool {
    matches!(b, BelongsTo::Loaded(Some(_)))
}

Try / catch

// Use fallback semantics instead of unwrap
let parent = opt_parent(&comment.post).ok_or(MyError::ParentMissing)?;

Prevention

When it happens

Trigger: Calling `child.parent.unwrap()` where `parent` was fetched via `.with(parent::Entity)` but the row's foreign key is NULL or points to a missing parent, yielding `Loaded(None)`.

Common situations: Nullable foreign key columns treated as mandatory; orphaned rows after the parent was deleted without cascading; data imported with dangling references.

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/85a17961d70af8dd. Report an issue: GitHub.