Skip to content

refactor!: relax Exn root marker bounds - #60

Merged
tisonkun merged 3 commits into
mainfrom
codex/relax-exn-marker-sized
Aug 24, 2026
Merged

refactor!: relax Exn root marker bounds#60
tisonkun merged 3 commits into
mainfrom
codex/relax-exn-marker-sized

Conversation

@tisonkun

@tisonkun tisonkun commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • allow Exn<E> to use an unsized root error marker
  • propagate ?Sized only through APIs and implementations that carry E as a marker
  • retain Sized wherever an error, closure, iterator, or success value is accepted or stored by value
  • remove the unused ResultExt::Error associated type, which otherwise prevents the Result<T, Exn<E>> implementation from accepting an unsized E

Motivation

Exn<E> stores a Box<Frame> and uses PhantomData<E> only to describe the root error type. The root value itself is already stored behind Box<dyn Error + Send + Sync> inside the frame, so requiring the marker E to be sized is not a storage requirement.

This relaxation allows downstream APIs to describe an unsized marker without committing exn to a type-erasure API or name:

type LocalDynamicExn = exn::Exn<dyn std::error::Error + Send + Sync + 'static>;

fn inspect<E>(exn: &exn::Exn<E>)
where
    E: std::error::Error + Send + Sync + 'static + ?Sized,
{
    let _ = exn.frame();
}

It also makes a later type-erasure conversion additive: a follow-up can decide whether a standard alias and conversion API are useful without changing the structural bounds again.

Scope boundary

This PR deliberately does not provide a way to construct or convert into Exn<dyn Error>. A downstream crate can name a local alias after this change, but cannot safely convert Exn<ConcreteError> into it because Exn's fields are private and Rust does not perform that marker coercion automatically.

Type-erasure naming, conversion ergonomics, callback guidance, and a specialized Deref implementation belong in a separate follow-up if the feature proves worthwhile.

The generic Deref<Target = E> implementation also remains sized because Error::downcast_ref::<E>() requires a sized target.

Breaking change

ResultExt::Error is removed. The associated type was declared and assigned by both implementations, but no ResultExt method or implementation reads it. Downstream code that explicitly refers to <R as ResultExt>::Error must stop doing so.

Validation

  • cargo x lint
  • cargo x test --no-capture
  • tested on the workspace MSRV, Rust 1.85.0

Relates to #35.

@tisonkun tisonkun changed the title feat!: support type-erased exceptions refactor!: relax Exn root marker bounds Aug 24, 2026
@tisonkun
tisonkun requested review from andylokandy and a lite review from Copilot August 24, 2026 04:03
@tisonkun
tisonkun marked this pull request as ready for review August 24, 2026 04:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR relaxes the Exn<E> root marker bounds to allow E: ?Sized where E is only used as a type-level marker, while keeping Sized requirements for APIs that accept or store values by value. It also removes an unused associated type from ResultExt to unblock the Result<T, Exn<E>> implementation from accepting unsized markers.

Changes:

  • Allow Exn<E> and related marker-only APIs/impls to use E: ... + ?Sized.
  • Update formatting impls and conversions to work with unsized root markers (Display, Debug, From<Exn<E>> for Box<dyn Error...>).
  • Remove the unused ResultExt::Error associated type (breaking change) and document it in the changelog.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
exn/src/result.rs Removes ResultExt::Error and updates Result<T, Exn<E>> extension impl to accept E: ?Sized.
exn/src/iterator.rs Relaxes the child root marker bound C in IteratorExt::raise to ?Sized.
exn/src/impls.rs Makes Exn<E> accept E: ?Sized, moves marker-only APIs (raise, frame) into a ?Sized impl, and relaxes From<Exn<E>> conversions.
exn/src/ext.rs Allows exn::Ok helper to use an unsized marker type parameter.
exn/src/display.rs Implements Display for Exn<E> with E: ?Sized using the stored frame error.
exn/src/debug.rs Implements Debug for Exn<E> with E: ?Sized using the stored frame.
exn-anyhow/src/lib.rs Allows into_anyhow to accept Exn<E> with E: ?Sized.
CHANGELOG.md Notes the breaking removal of ResultExt::Error.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tisonkun
tisonkun merged commit 0e7fa33 into main Aug 24, 2026
13 checks passed
@tisonkun
tisonkun deleted the codex/relax-exn-marker-sized branch August 24, 2026 12:15
This was referenced Aug 24, 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.

3 participants