Yet another half-baked idea for working around the orphan rule
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:
- Strict Upcasting: If
T\f1is a descendant ofT\f2, thenNonNull<T\f1>can be cast toNonNull<T\f2>as a no-op. - Structural Invariance: By marking
NonNullwithstruct NonNull<#[structural()] T>, the author allows casting between any facets ofT. SinceNonNulldoes not rely on any specific trait ofTto 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\fooisn'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::Pathobjects directly torustix::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