Skip to content

Box ErrorKind inside Error - #31

Merged
emilk merged 4 commits into
release-0.6from
emilk/box-error-kind
Aug 27, 2026
Merged

Box ErrorKind inside Error#31
emilk merged 4 commits into
release-0.6from
emilk/box-error-kind

Conversation

@emilk

@emilk emilk commented Aug 27, 2026

Copy link
Copy Markdown
Member

Related

What

Error was 96 bytes. It rides in the Err arm of every Result in the crate, and embedders assert on the size of error enums that hold it. kind is now a Box<ErrorKind>, taking Error to 24 bytes. The allocation only happens on the cold error path.

The two three-field variants set the old size: WrongDatatype and WrongArrayType each hold column: String + expected: String + actual: DataType = 72 bytes, plus the discriminant. Arrow's DataType is only 24 bytes, so it was never the culprit.

  • Error::new(record_type, kind) boxes for you, so no construction site names Box.
  • ErrorKind and all its variants are unchanged.
  • A const _: () = assert!(size_of::<Error>() <= 24, …) keeps it from growing back.

Breaking: Error { kind } can no longer be pattern-matched through. Match *err.kind (or &*err.kind) instead, as the tests here now do.

Testing

Existing tests, updated for the new match shape. The compile-time assert is the regression test; I checked it fires by tightening it to 16.

Compatibility

Breaking for anyone destructuring Error, which is why it goes into 0.6.

🤖 Generated with Claude Code

emilk and others added 4 commits August 27, 2026 17:35
`Error` was 96 bytes, driven by the two three-field `ErrorKind` variants
(`WrongDatatype` and `WrongArrayType`, 72 bytes each). It rides in the
`Err` arm of every `Result` in the crate, so boxing the kind takes it to
24 bytes; the allocation only happens on the cold error path.

`Error::new` boxes for you, so no construction site has to name `Box`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `Box<ErrorKind>` cannot be pattern-matched through, so the assertions
that used to spell out the whole `Err(Error { .. })` pattern now check
`record_type` separately and match the dereferenced kind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@emilk emilk mentioned this pull request Aug 27, 2026
@emilk
emilk marked this pull request as ready for review August 27, 2026 15:44
@emilk
emilk merged commit 8fc48f6 into release-0.6 Aug 27, 2026
6 checks passed

@emilk emilk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge review, as part of reviewing the 0.6 stack for #30. Clippy and tests are clean at the tip of release-0.6. 96 → 24 bytes for a type that rides in every Err arm is worth the break, and the compile-time size assert is the right regression test.

Error::source() is still None, and the From<Error> for ArrowError doc claims otherwise. kind has no #[source], so the chain stops dead at Error. Verified at the tip of release-0.6 with a try_newtype_datatype! failure: err.source().is_none() is true, and the NotEven cause that ErrorKind::Conversion carefully stores as #[source] Box<dyn Error + Send + Sync> is unreachable programmatically — only its Display text survives, twice, through ArrowError::ExternalError. Meanwhile the doc says "The error is preserved (including its source chain)". #[source] pub kind: Box<ErrorKind> fixes the gap and the doc in one line (thiserror allows a field to be both interpolated and the source).

Box is now part of the public API. The break was unavoidable; the box being permanent wasn't. A private field with kind(&self) -> &ErrorKind / into_kind(self) gets the same 24 bytes, reads better at match sites, and lets the representation change again without a third breaking release. You were already spending the break. It would also collapse the tests, where matches!(*err.kind, …) now runs past 120 characters at ~15 call sites.

— Claude

@emilk emilk mentioned this pull request Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant