Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Design Constraints

Cross-StructError From Conversion: Orphan Rule Limitation

Problem

Cross-layer error conversion (StructError<ParseReason>StructError<OrderReason>) requires an explicit .conv_err() call. A blanket From to make ? work automatically is blocked by Rust’s orphan rule.

#![allow(unused)]
fn main() {
// Desired but impossible:
fn place_order() -> Result<OrderDraft, StructError<OrderReason>> {
    let draft = parse_order()?;  // expected auto From<ParseError> → OrderError
    Ok(draft)
}

// Actual:
fn place_order() -> Result<OrderDraft, StructError<OrderReason>> {
    let draft = parse_order().conv_err()?;  // explicit conversion
    Ok(draft)
}
}

Root Cause

Rust’s orphan rule prohibits implementing From<Foreign<Local>> for Foreign<Local2> from a downstream crate:

#![allow(unused)]
fn main() {
impl From<orion_error::StructError<UserLocalReason>>   // Foreign<Local>
    for orion_error::StructError<UserLocalReason2>      // Foreign<Local2>
}
  • From = std trait (foreign)
  • StructError = foreign type (from orion-error)
  • Even though LocalReason and LocalReason2 are local types

The orphan rule requires at least one local anchor in either the trait or the implementing type. Neither From nor StructError<_> satisfy this when the impl is written in a downstream crate.

Attempted Workarounds

ApproachResult
Direct impl From<StructError<A>> for StructError<B> in downstream❌ orphan rule
Derive attribute upcast_from(SubReason) on target type❌ orphan rule
Derive attribute upcast_to(MainReason) on source type❌ orphan rule
Make ? auto-convert across reasons❌ can’t use From
newtype struct AppError(StructError<T>)✅ works, but changes every return type

Conclusion

.conv_err()` is the recommended path. The newtype wrapper can technically bypass the orphan rule but the cost (wrapping every function return type) far outweighs the benefit of saving one explicit call. Rust’s orphan rule is a core guarantee for ecosystem compatibility and is unlikely to change for this use case in the foreseeable future.