External Publication
Visit Post

Yet another half-baked idea for working around the orphan rule

Rust Internals [Unofficial] March 31, 2026
Source

Hi everyone, and thank you for these insightful technical points.

dlight:

What really sucks is wrapping/unwrapping types when using the types.

This is precisely the friction point Facets aim to eliminate. Consider NonNull<T>. Today, if you have a newtype, you are forced to map/unwrap the container. With Facets, you can cast the nested type directly:

  1. Strict Upcasting: If T\f1 is a descendant of T\f2, then NonNull<T\f1> can be cast to NonNull<T\f2> as a no-op.
  2. Structural Invariance: By marking NonNull with struct NonNull<#[structural()] T>, the author allows casting between any facets of T. Since NonNull does not rely on any specific trait of T to maintain its "not null" invariant, this is perfectly safe and removes the need for any manual wrapping.

dlight:

Also, a small comment on syntax. I think that using punctuation in Type\foo isn't a good tradeoff.

I have no "religious" preference regarding the syntax. If the semantics of the design are deemed viable, we will have plenty of time to "bikeshed" the notation. The priority now is to determine if the underlying logic holds water.

SkiFire13:

This breaks down when the trait implementation names the implementing type somewhere else.

I apologize, as my initial presentation was indeed unclear on this point. In my model, facet inheritance involves a systematic substitution of Self.

Specifically, impl PartialEq<Self> for i32 on the base type is inherited as impl PartialEq<Self> for i32\foo on the facet. This ensures that Eq remains valid. If an implementation explicitly names the base type (e.g., i32) instead of Self, the facet inherits it as-is.

zackw:

What I want [...] is a way to pass unix_path::Path objects directly to rustix::fs::open [...] with no extra ceremony at each callsite.

Because a facet is a distinct nominal type, you "own" it and can implement the external trait for it:

// 1. Define a local facet for the external type
facet WithArg for unix_path::Path;

// 2. Implement the external trait for your local facet (Legal!)
impl rustix::path::Arg for Path\WithArg { /* ... */ }

fn main() {
    let path = get_path(); // Assume this is a unix_path::Path
    let path = path as unix_path::Path\WithArg; // no-op
    rustix::fs::open(path, O_RDONLY, M_IGNORED);
}

I suspect that requiring path as unix_path::Path\WithArg still constitutes "ceremony" from your perspective. While implicit coercion could be envisioned to bridge this gap, I believe it would introduce too much "magic" and obscure the transition between nominal identities. I opted for an explicit cast to maintain predictability and avoid "spooky action at a distance," which I consider a necessary trade-off for a system that strictly preserves global coherence.

Discussion in the ATmosphere

Loading comments...