{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreibleswaesxuwri7zhwcmt3cpww2lr2jmopv4sh4febyjacm2tlste",
"uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mnkqi27ztg42"
},
"path": "/t/named-impl-with-implementation-selection-variant/24374#post_1",
"publishedAt": "2026-06-05T17:17:42.000Z",
"site": "https://internals.rust-lang.org",
"tags": [
"negative-impls",
"[Pre-RFC] Selectable Trait Implementations",
"[Pre-RFC] Scoped `impl Trait for Type`",
"Looking for RFC coauthors on \"named impls\"",
"Named impls and impl generics"
],
"textContent": "# Rust named impl Draft\n\nThe purpose is clear: to bypass Rust's orphan rule.\nThe proposed syntax should be more fluent to write, more natural to read, and more complete than the referenced proposals.\n\n 1. Introduce named trait implementations (named-impl)\n\n\n\n\n pub impl<T> Trait1 for Struct1<T> use ST1 {\n fn trait1_fn(&self) {\n println!(\"named impl ST1\");\n }\n }\n\n\n 2. Introduce a new type definition syntax, the Implementation Selection Variant (Impl Variant for short)\n\n\n\n\n type Struct1Default = Struct1<i32> + _ use default;\n type Struct1WithST1 = Struct1<i32> + Trait1 use ST1;\n\n\n## Overview\n\n\n // Default impl for struct and trait, same as Rust 2024 edition\n pub struct Struct1<T>(T);\n pub trait Trait1 {\n fn trait1_fn(&self);\n }\n impl<T> Trait1 for Struct1<T> {\n fn trait1_fn(&self) {\n println!(\"default impl\");\n }\n }\n\n // New named-impl definition, can be defined in any crate\n // ST1 below lives in the Type Namespace\n pub impl<T> Trait1 for Struct1<T> use ST1 {\n fn trait1_fn(&self) {\n println!(\"named impl ST1\");\n }\n }\n\n // Forbidden: using `default` as a named-impl name\n pub impl Clone for i32 use default {} // ❌\n\n // Import with `use` just like structs and traits; the full path mod1::ST1 can also be used\n use mod1::{Struct1, Trait1, ST1};\n\n // Type definitions\n type Struct1Generic = Struct1<i32>;\n // A generic type, which by default resolves to the following line: Struct1<i32> + _ use default\n\n type Struct1Default = Struct1<i32> + _ use default;\n // A concrete type, behaving exactly like Struct1 in Rust 2024; no named-impl is used.\n // `default` is a weak keyword here.\n\n type Struct1WithST1 = Struct1<i32> + Trait1 use ST1;\n // A concrete type, same layout as Struct1, but uses ST1's implementation for Trait1.\n // The trailing `_ use default` is implicitly omitted.\n\n // Different impl => different types\n assert!(TypeId::of::<Struct1Default>() != TypeId::of::<Struct1WithST1>());\n\n // Complex definition 1\n type ComplexType1 = &(i32 + Trait1 use ST1) + Trait2 use ST2;\n\n // Complex definition 2: overlapping implementations and priority rules\n type ComplexType2 = Struct1<i64>\n + From<i32> use mod2::Struct1Fromi32\n + From<_: Add> use default\n + From<_> use StructFrom\n + Trait1 use ST1;\n // Multiple named-impl generic parameters; overlapping of traits is allowed.\n // When acting as From<i32>, Struct1Fromi32 is used; as From<&str>, StructFrom is used.\n // The first matching trait (before `use`) from left to right wins and its implementation is used.\n\n\n // Type conversions\n let s = Struct1(1); // defaults to Struct1<i32> + _ use default\n let mut s_ref_named: &(Struct1<i32> + Trait1 use ST1) = &s; // ✅ type conversion\n let s_ref_default = s_ref_named as &(Struct1<i32> + _ use default); // ✅ mutual conversion\n s_ref_named = s_ref_default; // ❌ implicit conversion without explicit type annotation is forbidden\n\n let s4 = Struct1(4); // defaults to Struct1<i32> + _ use default\n let s5: Struct1<i32> + Trait1 use ST1 = s4; // move plus type conversion\n\n let vs: Vec<Struct1<i32>> = vec![]; // defaults to Vec<Struct1<i32> + _ use default>\n let vs1: &Vec<Struct1<i32> + Trait1 use ST1> = &vs; // Struct1 with impl variant can appear anywhere in the type\n let vs2: &Vec<Struct1<i32> + _ use default> = vs1;\n\n\n // End usage\n // Any T: Trait1 can be instantiated with both Struct1<i32> + _ use default and Struct1<i32> + Trait1 use ST1\n\n // 1. Direct call\n s5.trait1_fn();\n\n // 2. UFCS\n <Struct1<i32> as Trait1 use ST1>::trait1_fn(&s5);\n\n // 3. Generic function\n fn use_trait1<T: Trait1>(a: &T) {\n a.trait1_fn();\n }\n\n use_trait1(s_ref_default); // default impl\n use_trait1(s_ref_named); // named impl ST1\n\n // 4. Generic type\n pub struct Struct2<'a, T: Trait1>(&'a T);\n impl Struct2<'a, T: Trait1> {\n pub fn use_inner(&self) {\n self.0.trait1_fn();\n }\n }\n\n let s21 = Struct2(s_ref_default);\n s21.use_inner(); // default impl\n\n let s22 = Struct2(s_ref_named);\n s22.use_inner(); // named impl ST1\n\n\n // trait object\n let mut dyn_trait1: &dyn Trait1;\n\n dyn_trait1 = s_ref_named;\n dyn_trait1.use_inner(); // named impl ST1\n\n dyn_trait1 = s_ref_default;\n dyn_trait1.use_inner(); // default impl\n\n\n // impl trait\n // 1. `impl trait` in argument position is merely syntactic sugar for generics\n fn use_trait1(a: &impl Trait1) {\n a.trait1_fn();\n }\n use_trait1(s_ref_default); // default impl\n use_trait1(s_ref_named); // named impl ST1\n\n // 2. `impl trait` in return position\n fn return_trait1() -> impl Trait1 {\n let s4 = Struct1(4); // defaults to Struct1<i32> + _ use default\n return s4 as (Struct1<i32> + Trait1 use ST1); // move plus type conversion\n }\n\n\n\n## Prohibition Rules\n\n 1. Named-impl must be forbidden for `Copy`, `Drop`, `Send`, `Sync`, `Unpin`, etc.\n\n\n\n\n #[disable_named_impl(all)]\n pub trait Copy {}\n\n\n`#[disable_named_impl(all)]` can be used by external code and placed on traits, structs, enums, etc.\n\n 2. Named-impl must be forbidden for `Hash`, `Ord`, `PartialOrd`, `Eq`, `PartialEq`, etc. at least when a default implementation exists.\n\n\n\n\n #[disable_named_impl(exist_default)]\n pub trait Hash {}\n\n\n`#[disable_named_impl(exist_default)]` can be used by external code and placed on traits, structs, enums, etc.\n\n## Extended Usages\n\n 1. Implementing external traits for external types, bypassing the orphan rule.\n\n 2. Compile-time proxy, or function decorator\n(akin to `java.lang.reflect.Proxy` or Python decorators)\n\n\n\n\n\n impl fmt::Display for i32 use ProxyDisplay {\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n write!(f, \"before \")?;\n (self as &(i32 + Display use default)).fmt(f)?;\n write!(f, \" after\")?;\n Ok(())\n }\n }\n\n // Generic proxy, but might be impossible to write due to recursion\n impl<T: Display> fmt::Display for T use ProxyDisplayDefault {\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n write!(f, \"before \")?;\n (self as &(T + Display use default)).fmt(f)?; // ❓\n write!(f, \" after\")?;\n Ok(())\n }\n }\n\n\n 3. Specialization, partial specialization\n\n\n\n\n // Overlapping implementations and priority rules\n type ComplexType2 = Struct1<i64>\n + From<i32> use mod2::Struct1Fromi32\n + From<_: Add> use default\n + From<_> use StructFrom\n + Trait1 use ST1;\n // Multiple named-impl generic parameters; overlapping of traits is allowed.\n // When acting as From<i32>, Struct1Fromi32 is used; as From<&str>, StructFrom is used.\n // The first matching trait (before `use`) from left to right wins.\n\n\n## Implicit Type Generics, Optional\n\n 1. Generic bounds can be structs or enums\n\n\n\n\n #[derive(Default)]\n struct Struct1;\n impl Trait1 for Struct1 {};\n impl Trait1 for Struct1 use TS1 {};\n fn use_struct1<T: Struct1>() {\n T::default().trait1_fn();\n }\n\n use_struct1<Struct1 + _ use default>();\n use_struct1<Struct1 + Trait1 use TS1>();\n\n\n 2. Implicit type generics: function parameters and return types become generic\n\n\n * Advantage: old-style functions can automatically work with named-impl types.\n * Disadvantage: unsound, breaks the function's original intent.\n\n\n\n\n fn use_struct1(s: &Struct1) {\n s.trait1_fn();\n }\n // Automatically translates to\n fn use_struct1<T: Struct1>(s: &T) {\n s.trait1_fn();\n }\n\n\n * Problem 1: Multiple `Struct1`s would be forced to the same type.\n * Problem 2: If a parameter is `i32` and becomes generic, it destroys logic inside the function that relies on that `i32` (e.g., `Add` or `PartialEq`).\n\n\n\n## Simplified Syntax\n\n 1. Named-impl selection parameter simplification\n\n\n\n\n type Struct1Default = Struct1<i32> + use default;\n type Struct1WithST1 = Struct1<i32> + use ST1;\n type ComplexType = Struct1<i64> + use Struct1Fromi32<i32> + use StructFrom<_> + use ST1;\n // The trait type can be inferred; generic parameters follow the impl name.\n\n\n## Alternative Syntax\n\n 1. Use `as` instead of `use`\n\n\n\n\n pub impl<T> Trait1 for Struct1<T> as ST1 {\n fn trait1_fn(&self) {\n println!(\"named impl ST1\");\n }\n }\n type Struct1Default = Struct1<i32> + _ as default;\n type Struct1WithST1 = Struct1<i32> + Trait1 as ST1;\n\n <Struct1<i32> as Trait1 as ST1>::trait1_fn(&s5);\n\n\n 2. Named-impl selection parameters inside `<>` after generic parameters\n\n\n\n\n type i32AlterDisplay = i32<i32, Display use AlterDisplay>;\n type Struct1Default = Struct1<i32, _ use default>;\n type Struct1WithST1 = Struct1<i32, Trait1 use ST1>;\n type ComplexType = Struct1<i64, From<i32> use Struct1Fromi32, From<_> use StructFrom, Trait1 use ST1>;\n\n //❓ How to express this with the alternative syntax?\n type ComplexType1 = &(i32 + Trait1 as ST1) + Trait2 as ST2;\n\n\n## Trait Inheritance Issues\n\n 1. Type bloat.\n 2. Whether a subtrait's named-impl can restrict which implementation of the supertrait is used.\n 3. More complex upcasting.\n\n\n\n\n trait T1 {}\n trait T2 : T1 {}\n\n struct S;\n impl T1 for S {}\n impl T2 for S {}\n\n impl T1 for S use T1S3 {}\n impl T2 for S use T2S3 where S: T1 use T1S3 {} //❓ Optional, forces T2S3 to only be used with T1S3\n\n // Bad news: type bloat\n // Good news: the four types are not automatically generated; they don't exist unless written (compile-time and runtime)\n let s1: S + _ use default = S;\n let s2: S + T1 use T1S3 = S;\n let s3: S + T2 use T2S3 = S; // If restricted, it is automatically inferred as the next\n let s4: S + T1 use T1S3 + T2 use T2S3 = S;\n\n type Invalid = S + T1 use default + T2 use T2S3; //❌ If the restrictive syntax above is present\n\n\n## Compatibility Breakage\n\n 1. FFI boundaries.\n 2. dylib exported functions.\n 3. All code that assumes trait object data pointers are the same to judge identity will break.\n\n\n\n\n fn is_same(a: &dyn T1, b: &dyn T1) -> bool {\n \tlet (ptr_a, vt_a) = unsafe { transmute::<_, (usize, usize)>(a) }\n \tlet (ptr_b, vt_b) = unsafe { transmute::<_, (usize, usize)>(b) }\n \treturn ptr_a == ptr_b;\n }\n\n\n## Negative Implementations (negative-impls)\n\n 1. negative-impls cannot be used for named-impl\n\n\n\n\n pub impl !Trait1 for i32 use NegTrait1 {} // ❌\n\n\n 2. Overlap checking between named-impl and negative-impls is deferred to type monomorphization time\n\n\n\n\n #![feature(negative_impls)]\n trait DerefMut { }\n impl<T: ?Sized> !DerefMut for &T { }\n\n // Overlap with other implementations (including !Trait) is NOT checked at the definition of named-impl\n impl DerefMut for &i32 use DerefMutForI32 {} // ✅\n\n // The overlap check between named-impl and negative-impls happens at type instantiation\n type X = &i32 + DerefMut use DerefMutForI32; // ❌\n\n\n## GAT\n\nOrthogonality with GAT\n\n\n type A1 = S + Iterator use SI64; // ✅\n type A2 = S + Iterator<Item=i64> use SI64; // ❌\n\n\n\n struct S;\n\n impl Iterator for S {\n type Item = i32;\n fn next(&mut self) -> Option<Self::Item> { todo!() }\n }\n\n impl Iterator for S use SI64{\n type Item = i64;\n fn next(&mut self) -> Option<Self::Item> { todo!() }\n }\n\n fn use_iter<T: Iterator>() -> T::Item { todo!() }\n fn use_iter_i32<T: Iterator<Item=i32>>() -> T::Item { todo!() }\n fn use_iter_i64<T: Iterator<Item=i64>>() -> T::Item { todo!() }\n\n use_iter::<S>(); // ✅\n use_iter_i32::<S>(); // ✅\n use_iter_i64::<S>(); // ❌\n\n use_iter::<S + Iterator use SI64>(); // ✅\n use_iter_i32::<S + Iterator use SI64>(); // ❌\n use_iter_i64::<S + Iterator use SI64>(); // ✅\n\n\n## References\n\n * [Pre-RFC] Selectable Trait Implementations\n * [Pre-RFC] Scoped `impl Trait for Type`\n * Looking for RFC coauthors on \"named impls\"\n * Named impls and impl generics\n\n",
"title": "Named impl with Implementation Selection Variant"
}