From a10729f094ac5bd160a69c07c36c8fe1bb26ab1f Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Mon, 13 Nov 2023 05:26:36 +0100 Subject: [PATCH 01/27] Added draft RFC: Scoped `imple Trait for Type` --- text/0000-scoped-impl-trait-for-type.md | 1869 +++++++++++++++++++++++ 1 file changed, 1869 insertions(+) create mode 100644 text/0000-scoped-impl-trait-for-type.md diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md new file mode 100644 index 00000000000..276eb3c8570 --- /dev/null +++ b/text/0000-scoped-impl-trait-for-type.md @@ -0,0 +1,1869 @@ +- Feature Name: `scoped_impl_trait_for_type` +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This RFC proposes adding scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also proposes extending the syntax of `use`-declarations to allow importing these scoped implementations into other scopes (including other crates), and to differentiate type identity of generics by which scoped trait implementations are available to each discretised generic type parameter. + +(This document uses "scoped implementation" and "scoped `impl Trait for Type`" interchangeably. As such, the former should always be interpreted to mean the latter below.) + +# Motivation +[motivation]: #motivation + +While orphan rules regarding trait implementations are necessary to allow crates to add features freely without fear of breaking dependent crates, they limit the composability of third party types and traits, especially in the context of derive macros. + +For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`¹, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between crates. + +Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. + +Another pain point are sometimes missing `Into<>`-conversions when propagating errors with `?`, even though one external residual (payload) type may (sometimes *contextually*) be cleanly convertible into another. As-is, this usually requires a custom intermediary type, or explicit conversion using `.map_err(|e| ...)` (or equivalent function/extension trait). If an appropriate `From<>`-conversion can be provided *in scope*, then just `?` can be used. + +This RFC aims to address these pain points by creating a new path of least resistance that is easy to use and very easy to teach, intuitive to existing Rust-developers, readable without prior specific knowledge, discoverable as needed, has opportunity for rich tooling support in e.g. rust-analyzer and helpful error messages, is quasi-perfectly composable including decent re-use of composition, improves maintainability and (slightly) robustness to minor-version dependency changes compared to newtype wrappers, and does not restrict crate API evolution, compromise existing coherence rules or interfere with future developments like specialisation. Additionally, it allows the implementation of more expressive (but no less explicit) extension APIs using syntax traits like in the `PartialEq<>`-example below, without complications should these traits be later implemented in the type-defining crate. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Scoped `impl Trait for Type` can be introduced in The Book alongside global trait implementations and mentioned in the standard library documentation examples. + +For example, the following changes could be made: + +## **10.2.** Traits: Defining Shared Behavior + +The following sections are added after [Implementing a Trait on a Type]: + +[Implementing a Trait on a Type]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type + +### Scoped Implementation of a Trait on a Type + +Independently of implementing a trait on a type or set of types *globally*, it's possible to do so only for the current scope, by adding the `use` keyword: + +```rust +use impl Trait for Type { + // ... +} +``` + +With the exception of very few traits related to language features, you can implement any visible trait on any visible type this way, even if both are defined in other crates. + +In other words: The *orphan rule* does not apply to scoped implementations. Instead, item shadowing is used to determine which implementation to use. + +*Scoped implementations are intended mainly as compatibility feature*, to let third party crates provide glue code for other crate combinations. To change the behaviour of an instance or a set of instances from their default, consider using [the newtype pattern] instead. + +[`Hash`]: https://doc.rust-lang.org/stable/std/hash/trait.Hash.html +[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html +[`Eq`]: https://doc.rust-lang.org/stable/std/cmp/trait.Eq.html +[`PartialOrd`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialOrd.html +[`Ord`]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html + +[`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html +[`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html + +[the newtype pattern]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types + +### Publishing and Importing Scoped Implementations + +You can also publish a scoped implementation further by adding a visibility before `use` ...: + +```rust +pub use impl Trait for Type { + // ... +} + +pub use unsafe impl UnsafeTrait for Type { + // ... +} +``` + +... and import it into other scopes: + +```rust +use other_module::{ + impl Trait for Type, + impl UnsafeTrait for Type, +}; +``` + +Note that the scoped implementation of `UnsafeTrait` is imported without the `unsafe` keyword. **It is the implementing crate's responsibility to ensure the exported `unsafe` implementation is sound everywhere it is visible!** + +Generic parameters, bounds and `where`-clauses can be used as normal in each of these locations, though you usually have to brace `impl Trait for Type where /*...*/` individually in `use`-declarations. + +You can import a subset of a blanket implementation, by narrowing bounds or replacing type parameters with concrete types in the `use`-declaration. + +### Scoped implementations and generics +[scoped-implementations-and-generics]: #scoped-implementations-and-generics + +Scoped implementations are resolved on generic type parameters where those are specified, and become part of the (now less generic) host type's identity: + +```rust +struct Type(T); + +trait Trait { + fn trait_fn(); +} + +impl Type { + fn type_fn() { + T::trait_fn(); + } +} + +mod nested { + use impl Trait for () { + fn trait_fn() { + println!("nested"); + } + } + + pub type Alias = Type<()>; +} +use nested::Alias; + +Alias::type_fn(); // "nested" + +// Type::<()>::type_fn(); +// ^^^^^^^ error[E0599]: the function or associated item `type_fn` exists for struct `Type<()>`, but its trait bounds were not satisfied + +// let t: Type<()> = Alias(()); +// ^^^^^^^^^ error[E0308]: mismatched types +``` + +This works equally not just for type aliases but also fields, `let`-bindings and also where generic type parameters are inferred automatically from expressions (for example to call a constructor). + +## **19.2.** Advanced Traits + +The section [Using the Newtype Pattern to Implement External Traits on External Types] is updated to mention scoped implementations, to make them more discoverable when someone arrives from an existing community platform answer regarding orphan rule workarounds. It should also mention that newtypes are preferred over scoped implementations when use of the type is semantically different, to let the type checker distinguish it from others. + +[Using the Newtype Pattern to Implement External Traits on External Types]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types + +A new section is added: + +### Using Scoped Implementations to Implement External Traits on External Types +[using-scoped-implementations-to-implement-external-traits-on-external-types]: #using-scoped-implementations-to-implement-external-traits-on-external-types + +Since scoped implementations allow crates to reusably implement external traits on external types, they can be used to provide API extensions that make use of syntactic sugar. For example: + +Filename: fruit-comparer/src/lib.rs + +```rust +#![no_std] + +use apples::Apple; +use oranges::Orange; + +pub use impl PartialEq for Apple { + fn eq(&self, other: &Orange) -> bool { + todo!("Figure out how to compare apples and oranges.") + } +} + +pub use impl PartialEq for Orange { + fn eq(&self, other: &Orange) -> bool { + todo!("Figure out how to compare oranges and apples.") + } +} +``` + +Filename: src/main.rs + +```rust +use apples::Apple; +use oranges::Orange; + +use fruit_comparer::{ + impl PartialEq for Apple, + impl PartialEq for Orange, +}; + +fn main() { + let apple = Apple::new(); + let orange = Orange::new(); + + // Compiles: + dbg!(apple == orange); + dbg!(orange == apple); +} +``` + +If the type whose API was extended this way later gains the same trait inherently, that is not a problem as the consuming code continues to use `fruit_comparer`'s scoped implementation. However, a warning ([global-trait-implementation-available]) is shown by default to alert the maintainers of each crate of the covering global implementation. + +Be careful about literal coercion when using generic traits this way! For example, if a scoped implementation of `Index` is used and a global `Index` implementation is added later on the same type, the compiler will *not* automatically decide which to use for integer literal indices between these two. + +## Rustdoc documentation changes + +### `use` and `impl` keywords + +The documentation pages [for the `use` keyword] and [for the `impl` keyword] are adjusted to (very) briefly demonstrate the respective scoped use of `impl Trait for Type`. + +[for the `use` keyword]: https://doc.rust-lang.org/stable/std/keyword.use.html +[for the `impl` keyword]: https://doc.rust-lang.org/stable/std/keyword.impl.html + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Grammar changes +[grammar-changes]: #grammar-changes + +The core Rust language grammar is extended as follows: + +- [*TraitImpl*]'s definition is prepended with (*Visibility*? `use`)? and refactored for partial reuse to arrive at + + > *TraitImpl*: + >   **(*Visibility*? `use`)?** `unsafe`? ***TraitCoverage*** + >   `{` + >    *InnerAttribute*\* + >    *AssociatedItem*\* + >   `}` + > + > **TraitCoverage**: + >   ***TraitCoverageNoWhereClause*** + >   *WhereClause*? + > + > **TraitCoverageNoWhereClause**: + >   `impl` *GenericParams*? `!`? *TypePath* `for` *Type* + + where a trait implementation with that `use`-prefix provides the implementation *only* as item in the containing item scope. + + (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) + + **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [resolution-at-type-instantiation-site]). + + [*TraitImpl*]: https://doc.rust-lang.org/reference/items/implementations.html?highlight=TraitImpl#implementations + +- [*UseTree*]'s definition is extended for importing scoped implementations by inserting the extracted *TraitCoverage* and *TraitCoverageNoWhereClause* rules as follows: + + > *UseTree*: + >   (*SimplePath*? `::`)? `*` + >  | (*SimplePath*? `::`)? `{` + >   ( + >    (**(**‍*UseTree* **| *TraitCoverageNoWhereClause*)** (`,` **(**‍*UseTree* **| *TraitCoverageNoWhereClause*)**)\* **(**`,` ***TraitCoverage*?)**?)? + >   **| *TraitCoverage*** + >   ) + >   `}` + >  | *SimplePath* (`as` (IDENTIFIER | `_`))? + + Allowing a trailing *TraitCoverage* with *WhereClause* in a braced list is intended for ergonomics, but rustfmt should brace it individually by default, then append a trailing comma where applicable as usual. A '`,`' in the *WhereClause* here is not truly ambiguous because *WhereClauseItem*s contain '`:`', but allowing that ahead of others would likely be visually confusing and tricky to implement (requiring an arbitrarily long look-ahead). Alternatively to allowing a trailing *TraitCoverage* in mixed lists, an error similar to [E0178] could be emitted. + + [E0178]: https://doc.rust-lang.org/error_codes/E0178.html + + (Allowing unbraced imports like `use some_crate::impl Trait for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy pretty badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced.) + + Here, *TraitCoverage* imports the specified scoped `impl Trait for Type` for binding and conflict checks as if defined in the scope containing the `use`-declaration. The resulting visibility is taken from *UseDeclaration*, like with *SimplePath*-imported items. + + *TraitCoverage* must be fully covered by the scoped implementation visible in the source module. Otherwise, a compile-error occurs explaining the uncovered case (similarly to the current error(s) for missing trait implementations). + + ***TraitCoverage* may subset the source module's implementation** by having more narrow bounds or using concrete types in place of one or more generic type parameters. This causes only the specified subset of the scoped implementation to be imported. + + Note that scoped implementations of `unsafe` traits are imported without `unsafe`. It is the exporting crate's responsibility to ensure a scoped implementation is sound everywhere it is visible. + + Other elements of the coverage must match the source module's implementation exactly, unless specified otherwise. + + [*UseTree*]: https://doc.rust-lang.org/reference/items/use-declarations.html?highlight=UseTree#use-declarations + +## No scoped `impl Trait for Type` of auto traits, `Copy` and `Drop` + +Implementations of auto traits state guarantees about private implementation details of the covered type(s), which an external implementation can almost never do soundly. + +`Copy` is not an auto trait, but implementing it on a smart pointer like `Box` would immediately be unsound. As such, this trait must be excluded from all external implementations. + +Shadowing `Drop` for types that are `!Unpin` is similarly unsound without cooperation of the original crate (in addition to likely causing memory leaks in this and more cases). + +## No scoped `impl !Trait for Type` + +Any negative scoped implementation like for example + +```rust +use impl !Sync for Type {} +``` + +is syntactically valid, but rejected by the compiler with a specific error. (See [negative-scoped-implementation].) + +This also applies to `impl Trait`s in `use`-declarations (even though the items they would import cannot be defined anyway. Having a specific error saying that this *isn't possible* would be much clearer than one saying that the imported item doesn't exist). + +## Resolution at type instantiation site +[resolution-at-type-instantiation-site]: #resolution-at-type-instantiation-site + +When a generic like the type of a generic function or a generic struct type is instantiated into a less generic type, trait implementations **on generic type parameters** are bound in the scope of the consumer performing the instantiation. This also applies to bounds on associated types of generic type parameters. See [trait-binding-site] for more details. + +As an exception to this, type parameter defaults bind traits where they are defined. These bindings are used whenever the type parameter default is used. + +If an additional trait must be resolved on a generic type parameter, for example due to a bound on an implementation, then an implementation is resolved at the place where implementations for inherent bounds on that type parameter (would) have been resolved. + +## Type identity + +Instances of generics with distinct implementations available to their generic type parameters are distinct: + +```rust +struct Type; +struct Generic(T); +trait Trait {} + +impl Generic { + fn identical(_: Self) {} + fn nested_convertible>(_: Generic) {} +} + +mod mod1 { + use crate::{Generic, Trait, Type}; + use impl Trait for Type {} // Private implementation, but indirectly published through `Alias1`. + pub type Alias1 = Generic; +} + +mod mod2 { + use crate::{Generic, Trait, Type}; + pub use impl Trait for Type {} // Public implementation. + pub type Alias2 = Generic; +} + +mod mod3 { + use crate::{Generic, Trait, Type}; + use crate::mod2::{impl Trait for Type}; // Reused implementation. + pub type Alias3 = Generic; +} + +mod mod4 { + use crate::{Generic, Trait, Type}; + use impl Trait for Generic {} // Irrelevant top-level implementation. + pub type Alias4 = Generic; +} + +mod mod5 { + use crate::{Generic, Type}; + // No implementation. + pub type Alias5 = Generic; +} + +use mod1::Alias1; +use mod2::Alias2; +use mod3::Alias3; +use mod4::Alias4; +use mod5::Alias5; + +fn main() { + use std::any::TypeId; + use tap::Conv; + + // Distinct implementations produce distinct types. + assert_ne!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::(), TypeId::of::()); + + // Types with the same implementations available are the same type. + assert_eq!(TypeId::of::(), TypeId::of::()); + + // Top-level implementations are not part of type identity. + assert_eq!(TypeId::of::(), TypeId::of::()); + + // If the type is distinct, then values aren't assignable. + // Alias1::identical(Alias2::default()); + // ^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types + + // Fulfilled using the global reflexive `impl Into for T` on `Type`, + // as from its perspective, the binding is stripped due to being top-level. + Alias1::nested_convertible(Alias2::default()); + + // The reflexive `impl Into for T` does not apply here, + // as the distinct binding is on a type parameter. + // (It's unfortunately not possible to blanket-implement this conversion without specialisation.) + // Alias1::default().conv::(); + // ^^^^ error[E0277]: the trait bound `[…]¹` is not satisfied + + // Identical types are interchangeable. + Alias2::identical(Alias3::default()); + Alias4::identical(Alias5::default()); +} +``` + +¹ With the current implementation, this would likely say `Generic<_>: From>>`, which isn't helpful. With [explicit-binding], it could say `Generic: From>>`. + +## No interception/no proxies + +That each scoped `impl Trait for Type { /*...*/ }` is in scope for itself makes use of the implementation it shadows in the consumer scope *inexpressible*. There can be no scoped implementation constrained to always shadow another. + +This is intentional, as it makes the following code trivial to reason about: + +```rust +{ + use a::{impl TheTrait for TheType}; // <-- Clearly unused, no hidden interdependencies. + { + use b::{impl TheTrait for TheType}; + // ... + } +} +``` + +(The main importance here is to not allow non-obvious dependencies of imports. Implementations can still access associated items of a *specific* other implementation by bringing it into a nested scope or binding to its associated items elsewhere. See also [independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations].) + +## Contextual monomorphisation of blanket implementations and generic functions +[contextual-monomorphisation-of-blanket-implementations-and-generic-functions]: #contextual-monomorphisation-of-blanket-implementations-and-generic-functions + +Traits of generic type parameters (and of their associated types, recursively) are resolved to implementations according to the binding site of the blanket-implemented trait or generic function, which is in the code consuming that implementation. + +This means blanket implementations are monomorphised in different ways for the same type(s) when there is a difference in relevant scoped implementations for these types. + +```rust +struct Type; +struct Type2(T); + +trait Trait1 { + fn trait1(); +} +impl Trait1 for Type { + fn trait1() { + print!("global"); + } +} + +trait Trait2 { + fn trait2(&self); +} +impl Trait2 for T { + fn trait2(&self) { + Self::trait1(); + } +} + +trait Trait3 { + fn trait3(&self); +} +impl Trait3 for Type2 +where + T: Trait2, +{ + fn trait3(&self) { + self.0.trait2(); + } +} + +Type2(Type).trait3(); // "global" + +{ + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped1"); + } + } + + Type2(Type).trait3(); // "scoped1" + + { + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped2"); + } + } + + Type2(Type).trait3(); // "scoped2" + } + + { + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped3"); + } + } + + Type2(Type).trait3(); // "scoped3" + } +} +``` + +## Implicit shadowing of subtrait implementations + +Take this code for example: + +```rust +use std::ops::{Deref, DerefMut}; + +struct Type1(Type2); +struct Type2; + +impl Deref for Type1 { + type Target = Type2; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Type1 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +fn function1(_x: impl Deref + DerefMut) {} +fn function2(x: impl DerefMut) { + x.deref(); +} + +{ + use impl Deref for Type1 { + type Target = (); + + fn deref(&self) -> &Self::Target { + &() + } + } + + function1(Type1(Type2)); // <-- Clearly impossible. + function2(Type1(Type2)); // <-- Unexpected behaviour if allowed. +} +``` + +Clearly, `function1` cannot be used here, as its generic bounds would have to bind to incompatible implementations. + +But what about `function2`? Here, the bound is implicit but `Deref::deref` can still be accessed. For type compatibility, this would have to be the shadowed global implementation, which is most likely unintended decoherence. + +As such, **shadowing a trait implementation also shadows all respective subtrait implementations**. Note that the subtrait *may* still be immediately available (again), if it is blanket-implemented and all bounds can be satisfied in the relevant scope: + +```rust +trait Trait1 { + fn trait1(&self); +} +trait Trait2: Trait1 { // <-- Subtrait of Trait1. + fn uses_trait1(&self) { + self.trait1(); + } +} +impl Trait2 for T {} // <-- Blanket implementation with bounds satisfiable in scope. + +struct Type; +impl Trait1 for Type { + fn trait1(&self) { + print!("global"); + } +} + +{ + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped"); + } + } + + Type.uses_trait1(); // Works, prints "scoped". +} +``` + +If a subtrait implementation is brought into scope, it must be either a blanket implementation, or an implementation on a discrete type making use of the identical supertrait implementations in that scope. (This rule is automatically fulfilled by scoped implementation definitions, so it's only relevant for which scoped implementations can be imported via `use`-declaration.) + +## Independent trait implementations on discrete types may still call shadowed implementations +[independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations]: #independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations + +Going back to the previous example, but now implementing `Trait2` independently without `Trait1` in its supertraits: + +```rust +trait Trait1 { + fn trait1(&self); +} +trait Trait2 { // <-- Not a subtrait of `Trait1`. + fn uses_trait1(&self); +} +impl Trait2 for Type { // <-- Implementation on discrete type. + fn uses_trait1(&self) { + self.trait1(); + } +} + +struct Type; +impl Trait1 for Type { + fn trait1(&self) { + print!("global"); + } +} + +{ + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped"); + } + } + + Type.uses_trait1(); // Works, prints "global". +} +``` + +In this case, the implementation of `Trait2` is *not* shadowed at all. Additionally, since `self.trait1();` here binds `Trait` on `Type` directly, rather than on a generic type parameter, it uses whichever `impl Trait1 for Type` is in scope *where it is written*. + +## Warnings + +### Unused scoped implementation +[unused-scoped-implementation]: #unused-scoped-implementation + +Scoped implementations and `use`-declarations of such receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. + +(rust-analyzer should suggest removing an unused `use`-declaration as fix in either case.) + +An important counter-example: + +Filename: library/src/lib.rs + +```rust +pub struct Type; +pub struct Generic; + +pub trait Trait {} +use impl Trait for Type {} + +pub type Alias = Generic; +``` + +Filename: main.rs +```rust +use std::any::TypeId; + +use library::{Alias, Generic, Type}; + +assert_ne!(TypeId::of::(), TypeId::of::>()); +``` + +Here, the scoped implementation `use impl Trait for Type {}` **is** used as it is captured into the type identity of `Alias`. + +Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. +If it can ensure that that is the case, a (different!) warning could in theory still be shown here. + +### Global trait implementation available +[global-trait-implementation-available]: #global-trait-implementation-available + +Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that at least partially (in either sense) shadow another scoped implementation. + +(Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) + +### Self-referential bound of scoped implementation + +```rust +trait Foo { } + +use impl Foo for T where T: Foo { } + --------- ^^^^^^ +``` + +A Rust developer may write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the same global implementation, cannot apply to any types at all. + +The warning should explain that and why the bound is impossible to satisfy. + +### Private supertrait implementation required by public implementation +[private-supertrait-implementation-required-by-public-implementation]: #private-supertrait-implementation-required-by-public-implementation + +Consider the following code: + +```rust +pub struct Type; + +use impl PartialEq for Type { + // ... +} + +pub use impl Eq for Type {} +``` + +Here, the public implementation relies strictly on the private implementation to also be available. This means it effectively cannot be imported in `use`-declarations outside this module. + +See also the error [incompatible-or-missing-supertrait-implementation]. + +### Public implementation of private trait/on private type + +The code + +```rust +struct Type; +trait Trait {} + +pub use impl Trait for Type {} + ^^^^^ ^^^^ +``` + +should produce two distinct warnings similarly to those for private items in public signatures, as the limited visibilities of `Type` and `Trait` independently prevent the implementation from being imported in modules for which it is declared as visible. + +## Errors + +### Global implementation of trait where global implementation of supertrait is shadowed + +A trait cannot be implemented globally for a discrete type in a scope where the global implementation of any of its supertraits is shadowed on that type. + +```rust +struct Type; + +trait Super {} +trait Sub: Super {} + +impl Super for Type {} + +{ + use impl Super for Type {} + ----------------------- // <-- Scoped implementation defined/imported here. + + impl Sub for Type {} + ^^^^^^^^^^^^^^^^^ //<-- error: global implementation of trait where global implementation of supertrait is shadowed +} +``` + +### Negative scoped implementation +[negative-scoped-implementation]: #negative-scoped-implementation + +This occurs on all negative scoped implementations. Negative scoped implementations can be parsed, but are rejected shortly after macros are applied. + +```rust +struct Type; +trait Trait {} + +impl Trait for Type {} + +{ + use impl !Trait for Type {} + ^^^^^^^^^^^^^^^^^^^^^^^^ error: negative scoped implementation +} +``` + +### Incompatible or missing supertrait implementation +[incompatible-or-missing-supertrait-implementation]: #incompatible-or-missing-supertrait-implementation + +Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those. + +```rust +struct Type; +trait Super {} +trait Sub: Super {} + +impl Super for Type {} + +mod nested { + pub use impl Super for Type {} + pub use impl Sub for Type {} +} + +use nested::{impl Sub for Type}; + ^^^^^^^^^^^^^^^^^ error: incompatible supertrait implementation +``` + +Rustc should suggest to import the required scoped implementation, if possible. + +See also the warning [private-supertrait-implementation-required-by-public-implementation]. See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] for a potential way to improve the ergonomics here. + +## Resolution on generic type parameters +[resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters + +Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `impl Trait for T`. + +However, a blanket implementation can only be bound on a generic type parameter iff its bounds are fully covered by the generic type parameter's bounds and other available trait implementations on the generic type parameter, in the same way as this applies for global implementations. + +## Method resolution to scoped implementation without trait in scope + +[Method calls] can bind to scoped implementations even when the declaring trait is not separately imported. For example: + +```rust +struct Type; +struct Type2; + +mod nested { + trait Trait { + fn method(&self) {} + } +} + +use impl nested::Trait for Type {} +impl nested::Trait for Type2 {} + +Type.method(); // Compiles. +Type2.method(); // error[E0599]: no method named `method` found for struct `Type2` in the current scope +``` + +This also equally (importantly) applies to scoped implementations imported from elsewhere. + +[Method calls]: https://doc.rust-lang.org/book/ch05-03-method-syntax.html#method-syntax + +## Scoped implementations do not implicitly bring the trait into scope + +This so that no method calls on other types become ambiguous: + +```rust +struct Type; +struct Type2; + +mod nested { + trait Trait { + fn method(&self) {} + } + + trait Trait2 { + fn method(&self) {} + } +} + +use nested::Trait2; +impl Trait2 for Type {} +impl Trait2 for Type2 {} + +use impl nested::Trait for Type {} +impl nested::Trait for Type2 {} + +Type.method(); // Compiles, binds to scoped implementation of `Trait`. +Type2.method(); // Compiles, binds to global implementation of `Trait2`. +``` + +(If `Trait` was not yet globally implemented for `Type2`, and `Trait` and `Type2` were defined in other crates, then bringing `Trait` into scope here could introduce instability towards that implementation later being added in one of those crates.) + +## Shadowing with different bounds + +Scoped implementations may have different bounds compared to an implementation they (partially) shadow. The compiler will attempt to satisfy those bounds, but if they are not satisfied, then the other implementation is not shadowed for that set of generic type parameters and no additional warning or error is raised. + +(Warnings for e.g. unused scoped implementations and scoped implementations that only shadow a covering global implementation are still applied as normal. It's just that partial shadowing with different bounds is likely a common use-case in macros.) + +```rust +struct Type1; +struct Type2; + +trait Trait1 { + fn trait1() { + println!("1"); + } +} +impl Trait1 for T {} // <-- + +trait Trait2 { + fn trait2() { + println!("2"); + } +} +impl Trait2 for Type2 {} // <-- + +trait Say { + fn say(); +} +impl Say for T +where + T: Trait1, // <-- +{ + fn say() { + T::trait1(); + } +} + +{ + use impl Say for T + where + T: Trait2 // <-- + { + fn say() { + T::trait2(); + } + } + + Type1::say(); // 1 + Type2::say(); // 2 +} +``` + +## No priority over type-associated methods + +Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. + +## Trait binding site +[trait-binding-site]: #trait-binding-site + +This is analogous to the site of resolution of not fully qualified type paths, and of function items and attached functions at function call sites, which depend on which items are in scope. + +Unlike bindings on discretised generic type parameters, which are reused from the original type annotation or inference site, binding of top-level implementations can happen in more places and is transient (unless captured into an inferred generic type parameter). + +The exact token/token-span from which to start the lookup does matter here, both for [`Span::def_site()`] and mitigations for [first-party-implementation-assumptions-in-macros]. + +Whenever possible, the explicitly stated [*Type*] is used, unless it is [*InferredType*]. Otherwise, the most narrow token or span *in that statement* that represents the value whose type is inferred is used. + +[`Span::def_site()`]: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.def_site +[*Type*]: https://doc.rust-lang.org/stable/reference/types.html?highlight=Type#type-expressions +[*InferredType*]: https://doc.rust-lang.org/stable/reference/types/inferred.html?highlight=InferredType#inferred-type + +An almost certainly incomplete list of examples: + +(*This section is pretty rough. I think I need more information here to form a clearer picture. See also the relevant remark in [unresolved-questions].*) + +### *Type* in *GenericParams* in *Struct* + +Implementations for type parameter defaults are captured from the top-level implementations on the defined default: + +```rust +pub struct MyHashSet { /*...*/ } + ----------- +``` + +This means that the type identity is never split by scoped implementations on the default type parameter, as every such use sees the same set here. To capture different scoped implementations, the type parameter has to be explicitly inferred or stated by the consumer. + +### *Type* in *StructField*, *TypeAlias* and in *LetBinding* + +```rust +struct MyStruct { + set: MyHashSet, + ----- ----------- + + // Binds on `usize` here, but uses trait bindings of/at default definition `RandomState` in `MyHashSet`'s definition. + set: MyHashSet, + ----- +} +``` + +```rust +let set: MyHashSet; + ----- ----------- + +// Does not bind implementations. +// The bindings are inferred along with the generic type parameters. +let inferred: MyHashSet<_, _>; + +// Fully written-out types bind/capture regardless of expression. +let set2: MyHashSet = any_expression; + ----- ----------- +``` + +### Qualified calls of type-associated functions + +```rust +// Binds at `usize` for the first generic type parameter, but infers the second entirely through `set`. +// The binds on the first parameter must match exactly. +MyHashSet::>::insert(&mut set, any_value) + ----- ~~~ + +// Binds implementations for both type parameters here. +// The type of `set` must be identical including those bindings! +MyHashSet::::insert(&mut set, any_value) + ----- ----------- +``` + +### Qualified calls of trait-associated functions + +```rust +// Captures implementations on `usize` here. `set`'s type must match that exactly. +// The binding of `Debug` on the `MyHashSet` type is transient and *doesn't have* to match! + as Debug>::fmt(&set, &mut formatter) + ----- ~~~ + --------------------- ----- + +// Infers all type parameters through `set`. +// As above, the top-level binding is transient. +::fmt(&set, &mut formatter) + ~~~ + --------- ----- +``` + +### Coercion to trait objects + +```rust +// Binds the trait `Debug` at the expression `1`, as the type is not explicitly stated. +// Bindings on `i32` here are erased from the type identity by the trait object coercion. +// However: Any scoped implementations available on the trait object `dyn Debug` are captured into the type identity of `&dyn Debug` here and may distinguish it! +let d: &dyn Debug = &1; + ^ + --------- + +// Binds the trait `Debug` at the expression `1`, as the type is not explicitly stated. +// Bindings on `()` here are erased from the type identity by the trait object coercion. +// However: Any scoped implementations available on the trait object `dyn Debug` are captured into the type identity of `Box` here and may distinguish it! +let b: Box = Box::<()>::new(()); + ^^ +``` + +Due to the coercion into a trait object in the following code, the scoped implementation becomes attached to the value through the pointer meta data. This means it can then be called from other scopes: + +```rust +use std::fmt::{self, Display, Formatter}; + +fn function() -> &'static dyn Display { + use impl Display for () { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "scoped") + } + } + + &() +} + +println!("{}", function()); // "scoped" +``` + +## Interaction with return-position `impl Trait` + +Consider the following functions: + +```rust +trait Trait {} + +fn function() -> impl Trait { + use impl Trait for () {} + + () // Binds on trailing `()`-expression. +} + +fn function2() -> impl Trait { + use impl Trait for () {} + + {} // Binds on trailing `{}`-block used as expression. +} +``` + +In this case, the returned opaque types use the respective inner scoped implementation, as it binds on the `()` expression. + +These functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: + +```rust +trait Trait {} + +fn function() -> impl Trait { + ^^^^^^^^^^ + use impl Trait for () {} + --------------------- + + // Cannot bind on implicit `()` returned by function body without trailing *Expression*. +} + +fn function2() -> impl Trait { + ^^^^^^^^^^ + use impl Trait for () {} + --------------------- + + return; // Cannot bind on `return` without expression. + ------- +} +``` + +(The errors should ideally also point at the scoped implementations here with a secondary highlight, and suggest stating the return value explicitly.) + +The binding must be consistent: + +```rust +trait Trait {} + +fn function() -> impl Trait { + // error: Inconsistent implementation of opaque return type. + if true { + use impl Trait for () {} + return (); + ---------- + } else { + use impl Trait for () {} + return (); + ^^^^^^^^^^ + } +} +``` + +This function *does* compile, as the outer scoped `impl Trait for ()` is bound on the `if`-`else`-expression as a whole. + +```rust +trait Trait {} + +fn function() -> impl Trait { + use impl Trait for () {} + + if true { + use impl Trait for () {} // warning: unused scoped implementation + () + } else { + use impl Trait for () {} // warning: unused scoped implementation + () + } +} +``` + +This compiles because the end of the function is not reachable: + +```rust +trait Trait {} + +fn function() -> impl Trait { + { + use impl Trait for () {} + return (); // Explicit `return` is required to bind in the inner scope. + } +} +``` + +## Static interception of dynamic calls + +As a consequence of binding outside of generic contexts, it *is* possible to statically wrap *specific* trait implementations on *concrete* types. This includes the inherent implementations on trait objects: + +```rust +use std::fmt::{self, Display, Formatter}; + +fn outside_scope(d: &dyn Display, f: &mut Formatter<'_>) -> fmt::Result { + d.fmt(f) // Binds to global/inherent implementation. +} + +{ + use impl Display for dyn Display { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Hello! ")?; + outside_scope(self, f)?; + write!(f, " See you!") + } + } + + let question = "What's up?"; // &str + println!("{question}"); // "What's up?" + + let question: &dyn Display = &question; + println!("{question}"); // Binds to the scoped implementation; "Hello! What's up? See you!" +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +## First-party implementation assumptions in macros +[first-party-implementation-assumptions-in-macros]: #first-party-implementation-assumptions-in-macros + +If a macro outputs a call of the form `<$crate::Type as $crate::Trait>::method()`, it can currently make safety-critical assumptions about implementation details of the `method` that is called iff implemented in the same crate. + +(This should also be considered relevant for library/proc-macro crate pairs where the macro crate is considered an implementation detail of the library even where the macro doesn't require an `unsafe` token in its input, even though "crate privacy" currently isn't formally representable towards Cargo.) + +As such, **newly allowing the global trait implementation to be shadowed here can introduce soundness holes** iff `Trait` is not `unsafe` or exempt from scoped implementations. + +(I couldn't come up with a good example for this. There might be a slim chance that it's not actually a practical issue in the ecosystem. Unfortunately, this seems to be very difficult to lint for.) + +There are a few ways to mitigate this, but they all have significant drawbacks: + +- Opt-in scoped-`impl Trait` transparency for macros + + This would make scoped `impl Trait for Type`s much less useful, as they couldn't be used with for example most derive macros by default. It would also be necessary to teach the opt-in along with macros, which may not be realistic considering existing community-made macro primers. + + Implementation is likely complicated because many procedural macros emit tokens only with `Span::call_site()` hygiene, so information on the distinct binding site origin may not be readily available. + + This could be limited to existing kinds of macro definitions, so that future revised macro systems can be opted in by default. Future macros could use an `unsafe` trait instead to assume an implementation, or make use of scoped `impl Trait for Type` to enforce a specific implementation in their output. + + Drawback: Whether globally implemented behaviour can be changed by the consumer would depend on the macro. It would be good to surface a transparency opt-in in the documentation here. + +- Opt-in scoped-`impl Trait` *priority* for macros + + This would preserve practical usefulness of the proposed feature in most cases. + + This would add significant complexity to the feature, as resolution of scoped implementations wouldn't be exactly the same as for other items. (We should otherwise warn if a scoped `impl Trait for Type` outside a macro shadows binding a global implementation inside of it though, so at least the feature implementation complexity may be net zero in this regard.) + + This could be limited to existing kinds of macro definitions, with the same implications as for opt-in transparency above. + + Drawback: Whether globally implemented behaviour can be changed by the consumer would depend on the macro. It would be good to surface a priority opt-in in the documentation here. + +- Forbid scoped `impl Trait for Type` if `Trait` and `Type` are from the same crate + + This would at best be a partial fix and would block some interesting uses of [using-scoped-implementations-to-implement-external-traits-on-external-types]. + +## More `use`-declaration clutter, potential inconsistencies between files + +If many scoped implementations need to be imported, this could cause the list of `use`-declarations to become less readable. If there are multiple alternatives available, inconsistencies could sneak in between modules (especially if scoped `impl Trait for Type` is used in combination with [specialisation](https://rust-lang.github.io/rfcs/1210-impl-specialization.html)). + +This can largely be mitigated by centralising a crate's scoped trait imports and implementations in one module, then wildcard-importing its items: + +```rust +// lib.rs +mod scoped_impls; +use scoped_impls::*; +``` + +```rust +// scoped_impls.rs +use std::fmt::Debug; + +use a::{TypeA, TraitA}; +use b::{TypeB, TraitB}; + +pub use a_b_glue::{impl TraitA for TypeB, impl TraitB for TypeA}; +// ... + +pub use impl Debug for TypeA { + // ... +} +pub use impl Debug for TypeB { + // ... +} + +// ... +``` + +```rust +// other .rs files +use crate::scoped_impls::*; +``` + +## Type inference has to consider both scoped and global implementations + +Complexity aside, this could cause compiler performance issues since caching would be less helpful. + +Fortunately, at least checking whether scoped implementations exist at all for a given trait and item scope should be reasonably inexpensive, so this hopefully won't noticeably slow down compilation of existing code. + +That implementation binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. + +## Cost of additional blanket implementation instances + +The [contextual-monomorphisation-of-blanket-implementations-and-generic-functions] and the resulting additional instantiations of these implementations could have a detrimental effect on compile times and .text size (depending on optimisations). + +This isn't unusual for anything involving *GenericParams*, but use of this feature could act as a multiplier to some extent. It's likely a good idea to evaluate relatively fine-grained caching in this regard, if that isn't in place already. + +## Split type identity may be unexpected +[split-type-identity-may-be-unexpected]: #split-type-identity-may-be-unexpected + +Consider crates like `inventory` or Bevy's systems and queries. + +There may be tricky to debug issues for their consumers if a `TypeId` doesn't match between uses of generics with superficially the same type parameters, especially without prior knowledge of distinction by captured available scoped implementations. + +A partial mitigation would be to have rustc include such captures on generic type parameters when printing types, but that wouldn't solve the issue entirely. + +Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct captured implementations directly, as long as only these top-level implementations differ and no further nested ones. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- As outlined in the [motivation], there is considerable friction in the current form of Rust when combining traits and types from unrelated crates. Implementing newtypes for each combination is cumbersome and adds noise to consuming code, and these newtypes are often not well-reusable and usually don't compose well. + +- With scoped `impl Trait for Type` as in this RFC, scoped implementations must be implemented or imported explicitly in the same file as they are bound, which stops this feature from introducing hard-to-follow non-local side-effects. Consumption of discretised generics also does not affect their identity, as the set of available - rather than actually used - scoped implementations on their type parameters is evaluated. + +- Usage at the binding site is concise: An existing scoped implementation can always be imported in a single `use`-declaration as long as it is visible. A new scoped implementation can be created directly via `use impl Trait for Type { /*...*/ }`. Nothing else is needed compared to when a global implementation is available. + + - This is also true for blanket imports, which remain easy to parse visually due to the surrounding braces: + + ```rust + // This does NOT reliably redact information, + // as the scoped implementation is NOT bound on non-generic calls! + // Please use privacy wrappers like `redact::Secret` instead. + use omit_debug::{impl Debug for T}; + + Err(()).unwrap(); // Does not show the usual `()`. + ``` + + ```rust + use debug_by_display::{impl Debug for T}; + + print!("{:?}", "Hello!"); // Prints `Hello!` *without* surrounding double-quotes. + ``` + + ```rust + use std::fmt; + + // `Debug` and `Display` all `Pointer`-likes as addresses. + // The `Display` import is different only to show the long form + // with `where`. It could be written like the `Debug` import. + use cross_formatting::by_pointer::{ + impl Debug for T, + {impl Display for T where T: fmt::Pointer}, + }; + + println!("{}", &()); // For example: 0x7ff75584c360 + ``` + + - The newtype pattern requires an additional wrapper type and, usually, `.into()`-conversions or other explicit function calls where the inner type is received or returned. If the newtype (for coherence-proofing) doesn't implement `Deref`, explicit field access is also necessary in some places. + +- This RFC introduces few new grammar elements and concepts. Rather, it mainly allows existing concepts to be reused elsewhere: + + - *UseDeclaration* and *TraitImpl* are changed in a way that's quick to summarise and remains easy to parse. (See [grammar-changes]) + + - Shadowing of scoped implementations exactly matches that of other items. + + - Coherence rules for scoped implementations defined or imported in the same scope exactly match those for global implementations, minus orphan rules. + + (Notably, this means that two potentially-conflicting blanket implementations still are not allowed. If *really* necessary, precedence can be disambiguated by nesting scopes, but narrowing the implementations or narrowly importing from a wide implementation in an inline submodule should be preferred where at all feasible!) + + - This contrasts with Genus's proper-named models, which allow mixed use in the same scope. + + - This contrasts with 𝒢's overload mixing throughout the scope hierarchy. + + - If a crate wishes to provide alternative implementations (for example to blanket-provide a scoped `Display` implementation for all types that are `LowerHex` or `UpperHex`), it can still easily export them from distinct modules. + +- Scoped `impl Trait for Type`s compose very well: + + ```rust + use serde::{Deserialize, Serialize}; + use bevy_reflect::{FromReflect, Reflect}; + + use serde_compatible::A; + use bevy_compatible::B; + use neither_compatible::C; + + // This module actually contains the blanket implementations + // `pub use impl Serialize for T { /*...*/ }` and + // `pub use impl Deserialize<'_> for T { /*...*/ }`, + // so the import narrows it down. + use serde_by_bevy_reflect::{ + impl Serialize for B, + impl Deserialize<'_> for B, + }; + + use bevy_reflect_for_b::{ + impl FromReflect for B, + impl Reflect for B, + }; + + // You can concisely import all implementations from a module along with other items: + mod traits_for_c; + use traits_for_c::*; + // However, avoid this for external modules except `prelude`. + // Expanding the coverage of a scoped implementation visible in a `prelude` onto a previously existing type should always be considered a breaking change, so please be careful with blanket implementations there! + + #[derive(Debug, Clone, Reflect, Deserialize, Serialize)] + pub Type { + a: A, + b: B, + c: C, + } + ``` + + - Unlike with external newtypes, there are no potential conflicts beyond overlapping imports and definitions in the same scope. + These conflicts can *always* be resolved both without editing code elsewhere and without adding an additional implementation (either by narrowing local blanket implementations, possibly moving it into a submodule and importing for discrete types, or by narrowing a blanket implementation import to a subset of the external implementation). + +- Since the trait implementation binding isn't stated explicitly at the binding site, this feature integrates well with implicit uses of traits, like in `?`-chains. + + This would make it easier for middleware and application frameworks to slot into each other concisely, as glue crates could provide seamless conversions. The configuration of this kind of glue would be easily reusable: + + ```rust + // glue.rs + use database_middleware::ConnectionError; + use authentication_middleware::ActAsError; + use storage_middleware::StoreError; + use application_framework::IntentError; + + pub use database_application_glue::{impl From for IntentError}; + pub use authentication_application_glue::{impl From for IntentError}; + + pub use impl From for IntentError { + // ... + } + ``` + + ```rust + use application_framework::IntentError; + use crate::glue::*; + + async fn submit_data(user: User, data: Data) -> Result { + Ok(SERVER_CONNECTIONS.lock_one()?.act_as(user)?.store_data(data)?) + } + ``` + +- Public scoped implementations in other modules/crates are discoverable. + + - Unlike newtypes, rustc and rust-analyzer can index these and suggest fitting (non-blanket) `use`-declarations for a trait-type-pair, just as they suggest trait imports. + + - For scoped implementations in the current crate, it may be better to suggest a full blanket import of the implementation where applicable (e.g. `use crate::some_module::{impl Trait for T};`, where that coverage is copied *exactly* from the `use impl ...` item). That also nicely teaches that that's possible. + + - Generic type parameters that are *not* the target should be suggested by default too, e.g. `use other_crate::{impl Trait for Container};` should be suggested instead of `use other_crate::{impl Trait for Container};`, even if the external implementation is the broader `pub use impl Trait for T { /*...*/ }`. A narrower import can still be suggested if the broad import would conflict. + + - rust-analyzer can suggest and create a new `use impl Trait for Type { /*...*/ }` item where no fitting implementation is visible. This is likely to be seen by someone about to use the newtype pattern, which makes the feature itself more discoverable too. + + However, for example `Debug`-implementations are often feature-gated, so it would be good if rust-analyzer had a way to suggest activating the feature more prominently than suggesting a local implementation. + +- Scoped `impl Trait for Type`s lead to better maintenance lints: + + - If a covering global implementation later becomes available through a dependency, a warning can be shown on the local trait implementation for review. (See [global-trait-implementation-available]) + + It would make sense to let the definitions and also alternatively specific global implementations of traits like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. + +- Like abandoning orphan rules, scoped `impl Trait for Type` allows third-party crates to provide compatibility implementations for arbitrary combinations of other crates. + + Unlike abandoning orphan rules, using one of these implementations is an explicit consumer-choice (or directly inferred from one) and overlapping scoped implementations can exist without issue in the dependency graph or even in the same crate in distinct scopes, even if they overlap for an actually-used type for which that trait is actually bound. + +- Like with newtype wrappers, use of the custom implementation is explicit, unambiguous, and robust/stable against all later-added or expanded trait implementations elsewhere, whether they are scoped or global. + + - This sets the feature apart from weakening coherence rules, which could introduce additional conflicts through feature additions. + + - Scoped implementations are actually *more* robust than newtypes: + + A newtype that implements multiple traits could later gain a global blanket implementation of one of its traits for types that implement another of its trait, causing a conflict. In the presence of a scoped `impl Trait for Type`, the new blanket implementation would be unambiguously shadowed for existing code. + + (Side-note: This also means it's not a great idea to put `Deref` on a newtype, but it's tempting to do so to avoid explicit field access everywhere. That seems like a bit of a footgun in the status quo.) + +- Additionally splitting type identity (including `TypeId`) only by bindings on generic type parameters already satisfies consistency requirements for each of instantiated, static and dynamically managed collection use cases. + + - Not splitting type identity by top-level bindings avoids a large amount of unnecessary friction when passing values between modules. Types can rely on consistent implementations of traits on their generic type parameters, but they *already* cannot rely on outside use of their API aside from `unsafe` features. + +# Prior art +[prior-art]: #prior-art + +## Lightweight, Flexible Object-Oriented Generics + +Yizhou Zhang, Matthew Loring, Guido Salvaneschi, Barbara Liskov and Andrew C. Myers, May 2015 + + + +There are some parallels between Genus's models and the scoped `impl Trait for Type`s proposed in this RFC, but for the most part they are quite distinct due to Rust's existing features: + +| Genus | scoped `impl Trait for Type` | reasoning | +|---|---|---| +| Proper-named models | Anonymous scoped implementations | Use of existing coherence constraints for validation. Forced subsetting in `use`-declarations improves stability. The `impl Trait for Type` syntax stands out in `use`-declarations and is intuitively readable. | +| Explicit bindings of non-default models | Only implicit bindings | Focus on simplicity. Mixed bindings for definitions with the same scope/type/trait triple are rare and can be emulated with newtypes where needed. More natural use with specialisation. | +| Comparing containers inherently constrain type parameters in their type definition. | Available scoped implementations for discretised type parameters become part of the type identity. |

This is a tradeoff towards integration with Rust's ecosystem, as generics are generally not inherently bounded on collection types in Rust.

There is likely some friction here with APIs that make use of runtime type identity. See [split-type-identity-may-be-unexpected].

| + +Some features are largely equivalent: + +| Genus | Rust | notes / scoped `impl Trait for Type` | +|---|---|---| +| Implicit default models | Explicit global trait implementations | Implicit implementation of unknown external interfaces is unnecessary if third party crates' are as conveniently usable in scope as if global, as with scoped `impl Trait for Type`. | +| Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects.
This does not allow for invisible runtime specialisation in all cases. | +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available scoped implementations on discretised type parameters are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of blanket implementations in Rust can simply be monomorphised accordingly, and existing reflexive conversions and comparisons can bind between similar type parameters.

| + +## A Language for Generic Programming in the Large + +Jeremy G. Siek, Andrew Lumsdaine, 2007 + + + +𝒢 and scoped `impl Trait for Type` are conceptually very similar, though this RFC additionally solves logical consistency issues that arise from having multiple alternative ways to fulfill a constraint and develops some ideas further than the paper. Other differences are largely due to 𝒢 being more C++-like while scoped `impl Trait for Type` attempts smooth integration with all relevant Rust language features. + +A few notable similarities, in the paper's words: + +- equivalent retroactive modeling (where existing Rust's is limited by orphan rules), +- (retained) separate compilation (though *some* information can flow between items in this RFC, but only where such information flows exist in Rust already), +- lexically scoped models, +- seemingly the same binding rules on generic type parameters within constrained models/generic implementations, + +and key differences: + +| 𝒢 | Rust / scoped `impl Trait for Type` | notes | +|---|---|---| +| Only discrete model imports | Includes blanket imports and re-exports | This is pointed out as '[left] for future work' in the paper. Here, it follows directly from the syntax combination of Rust's `use` and `impl Trait for Type` items. | +| - | (Rust) Global implementations | The automatic availability of global implementations between separately imported traits and types offers more convenience especially when working with common traits, like those backing operators in Rust. | +| Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | +| - | (Rust) Trait objects | 𝒢 does not appear to support runtime polymorphism beyond function pointers. Scoped `impl Trait for Type` is seamlessly compatible with `dyn Trait` coercions (iff `Trait` is object-safe). | +| (unclear?) | Available implementations on discretised type parameters become part of the type identity. |

This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour.

The tradeoff is that it may be difficult to explicitly annotate types in cases of mixed bindings with this RFC. As newtypes and named configuration token types are still preferred for changed behaviour, such cases will hopefully be limited. Otherwise, see [explicit-binding] for bikeshedded syntax.

| + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" in better? + +- In macros, which function-call token should provide the resolution context from where to look for scoped `impl Trait for Type`s (in all possible cases)? + + This doesn't matter for `Span::call_site()` vs. `Span::mixed_site()` since scoped implementations would resolve transparently through both, but it does matter for `Span::def_site()` which should exclude them. + + It very much does matter if one of the opt-in mitigations for [first-party-implementation-assumptions-in-macros] is implemented. + + The [trait-binding-site] section above has a range of examples, but these likely don't cover everything. + +- I used some example crates above that don't exist. If that makes it into the documentation, I think those should be created with the described features. + +- Should outer generic type parameters be visible on/in scoped `impl Trait for Type`, including `use`-declarations? + + That would enable the following pattern: + + ```rust + use some_crate::Trait; + + fn function(value: T) -> impl Trait { + use impl Trait for T { + // ... + } + + #[derive(Trait)] // Based on fields' `: Trait`. + struct Returned { + field: T, + } + + Returned { field: value } + } + ``` + + However, if [explicit-binding] is added then that is unnecessary, as the following would work: + + ```rust + use some_crate::Trait; + + fn function(value: T) -> impl Trait { + mod scoped { + use impl some_crate::Trait for T { + // ... + } + } + + #[derive(Trait)] // Based on fields' `: Trait`. + struct Returned { + field: T, + } + + Returned:: { field: value } + } + ``` + +- How important is explicit binding? If it should be included here, which syntax should it use? + + See [explicit-binding] in *Future possibilities* below for one possibility. + +- How to bring a global implementation into a scope as if scoped? + + This may be necessary to fix some [incompatible-or-missing-supertrait-implementation] errors without larger code reorganisation, and would also give macro authors a better way to force use of the global implementation. + + The syntax should translate well to [explicit-binding] and also support re-exporting the implementation. + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Exporting a scoped implementation as global, `extern impl Trait` + +***This should never be used for IO/serialisation traits.*** + +Application crates may want to provide a specific implementation globally, disregarding orphan rules since there are no downstream crates that could be impacted by future incompatibilities (and crate-local issues are largely mitigated by *Cargo.lock*). + +This could later be allowed using a construct like + +```rust +// Use an external implementation as global: +#[core::unstable_use_as_global] +use impl_crate::{impl Trait for Type}; + +// Provide a local implementation globally: +#[core::unstable_use_as_global] +use impl Trait for Type { /*...*/ } +``` + +To use a global implementation not available through one of its dependencies, a library crate would have to declare it: + +```rust +extern impl Trait for Type; +``` + +This would result in a compile error if the declaration is not fully covered by a global trait implementation. + +If the trait implementation is later made available plainly (that is: without `use`, subject to orphan rules) by a dependency, a warning should appear on the `extern impl` declaration, along with the suggestion to remove the `extern impl` item. + +(However, I assume binding to implementations not-from dependencies or the same crate in this way has a lot of implications for code generation.) + +## Scoped `impl Trait for Type` of auto traits, `Drop` and/or `Copy` with orphan rules + +The crate in which a type is defined could in theory safely provide scoped implementations for it also for these traits. + +- This is likely more complicated to implement than the scoped `impl Trait for Type`s proposed in this RFC, as these traits interact with more distinct systems. + +- What would be the binding site of `Drop` in `let`-statements? + +- This could interact with linear types, were those to be added later on. + + For example, database transactions could be opt-out linear by being `!Drop` globally but also having their crate provide a scoped `Drop` implementation that can be imported optionally to remove this restriction in a particular consumer scope. + +## Scoped proxy implementations + +In theory it *might* be possible to later add syntax to create an exported implementation that's *not in scope for itself*. + +I'm **very** hesitant about this since doing so would allow transparent overrides of traits (i.e. proxying), which could be abused for JavaScript-style layered overrides through copy-pasting source code together to some extent. + +## Analogous scoped `impl Type` + +This could be considered as more-robust alternative to non-object-safe extension traits defined in third party crates. + +A good example of this use case could be the [tap] crate, which provides generic extension methods applicable to *all* types, but where its use is *theoretically* vulnerable to instability regarding the addition of type-associated methods of the same name(s). + +If instead of (or in addition to!) ...: + +```rust +// pipe.rs + +pub trait Pipe { + #[inline(always)] + fn pipe(self, func: impl FnOnce(Self) -> R) -> R + where + Self: Sized, + R: Sized, + { + func(self) + } + + // ... +} + +impl Pipe for T where T: ?Sized {} +``` + +...the extension could be defined as ...: + +```rust +pub use impl T where T: ?Sized { + #[inline(always)] + fn pipe(self, func: impl FnOnce(Self) -> R) -> R + where + Self: Sized, + R: Sized, + { + func(self) + } + + // ... +} +``` + +...then: + +- The consumer crate could choose which types to import the extension for, weighing + + ```rust + use tap::pipe::{impl Type1, impl Type2}; + ``` + + against + + ```rust + use tap::pipe::{impl T where T: ?Sized}; + ``` + +- These *scoped extensions would shadow inherent type-associated items of the same name*, guaranteeing stability towards those being added. + + (This should come with some warning labels in the documentation for this feature, since *adding items to an existing public scoped extension* could be considered an easily-breaking change here.) + +This has fewer benefits compared to scoped `impl Trait for Type`, but would still allow the use of such third-party extension APIs in library crates with very high stability requirements. + +An open question here is whether (and how) to allow partially overlapping `use impl Type` in the same scope, in order to not shadow inherent associated items with ones that cannot be implemented for the given type. + +- That could in theory be more convenient to use, but + +- calls could be *subtly* inconsistent at the consumer side, i.e. accidentally calling an inherent method if a scoped extension method was expected and + +- widening a public implementation to overlap more of another exported in the same module could break dependent crates if a wide blanket import applied to narrower extensions. + +As such, *if* this feature was proposed and accepted at some point in the future, it would likely be a good idea to only allow non-overlapping implementations to be exported. + +[tap]: https://crates.io/crates/tap + +## Interaction with specialisation +[interaction-with-specialisation]: #interaction-with-specialisation + +- Scoped `impl Trait for Type` can be used for consumer-side specialisation of traits for binding sites that are in item scope, by partially shadowing an outer scope's implementation. + + Note that this would **not** work on generic type parameters, as the selected implementation is controlled strictly by their bounds (See [resolution-on-generic-type-parameters].), but it would work in macros for the most part. + + This does not interact with [specialisation proper](https://rust-lang.github.io/rfcs/1210-impl-specialization.html), but rather is a distinct, less powerful mechanism. As such, it would not supersede specialisation. + +- Scoped `impl Trait for Type` does not significantly interact with specialisation of global implementations. + + Any global specialisation would only be resolved once it's clear no scoped implementation applies. + +- Specialisation could disambiguate scoped implementations which are provided (implemented or imported) in the same scope. For example, + + ```rust + use dummy_debug::{impl Debug for T}; + use debug_by_display::{impl Debug for T}; + use impl Debug for str { + // ... + } + ``` + + would then compile, in scope resolving `` to the local implementation and otherwise binding `Debug` depending on whether `Display` is available at the binding site for each given type `T`. + + Local implementations do not necessarily have to be more specific compared to imported ones - in keeping with "this is the same as for global implementations", the way in which the scoped implementation is introduced to the scope should not matter to specialisation. + + **When importing scoped implementations from a module, specialisation should apply hierarchically.** First, the specificity of implementations is determined only by `use impl` implementations and `use`-declarations in the importing scope. If the trait bound binds to a `use`-declaration, then the actual implementation is chosen by specificity among those visible in the module they are imported from. If the chosen implementation there is an import, the process repeats for the next module. This ensures stability and coherence when published implementations are specialised in other modules. + + - I'm not sure how well this can be cached in the compiler for binding-sites in distinct scopes, unfortunately. Fortunately, specialisation of scoped `impl Trait for Type` does not seem like a blocker for specialisation of global trait implementations. + + - Should specialisation of scoped implementations require equal visibility? I think so, but this question also seems considerably out of scope for scoped `impl Trait as Type` as a feature itself. + +## Scoped `impl Trait for Type` as associated item + +Scoped `impl Trait for Type` could be allowed and used as associated non-object-safe item as follows: + +```rust +trait OuterTrait { + use impl Trait for Type; +} + +fn function() { + use T::{impl Trait for Type}; + // ...configured code... +} +``` +```rust +impl OuterTrait for OtherType { + // Or via `use`-declaration of scoped implementation(s) defined elsewhere! + // Or specify that the global implementation is used (somehow)! + use impl Trait for Type { + // ... + } +} + +function::(); +``` + +This would exactly supersede the following more verbose pattern enabled by this RFC: + +```rust +trait OuterTrait { + type Impl: ImplTraitFor; +} + +trait ImplTraitFor { + // Copy of trait's associated items, but using `T` instead of the `Self` type and + // e.g. a parameter named `this` in place of `self`-parameters. +} + +fn function() { + use impl Trait for Type { + // Implement using `T::Impl`, associated item by associated item. + } + + // ...configured code... +} +``` + +```rust +struct ImplTraitForType; +impl ImplTraitFor for ImplTraitForType { + // Implement item-by-item, as existing scoped `impl Trait for Type` cannot be used here. +} + +impl OuterTrait for OtherType { + type Impl: ImplTraitFor = ImplTraitForType; +} + +function::(); +``` + +- *In theory* this could be made object-safe if the associated implementation belongs to an object-safe trait, but this would introduce much-more-implicit call indirection into Rust. + +## Scoped fallback implementations +[scoped-fallback-implementations]: #scoped-fallback-implementations + +A scoped fallback implementation could be allowed, for example by negatively bounding it *on the same trait* in the definition or import: + +```rust +#[derive(Debug)] +struct Type1; + +struct Type2; + +{ + use debug_fallback::{impl Debug for T where T: !Debug}; + + dbg!(Type1); // Compiles, uses global implementation. + dbg!(Type2); // Compiles, uses fallback implementation. +} +``` + +This would be a considerably less messy alternative to [autoref-] or [autoderef-specialisation] for macro authors. + +Note that ideally, these fallback implementations would still be required to not potentially overlap with any other (plain or fallback) scoped implementation brought into that same scope. + +[autoref-]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md +[autoderef-specialisation]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html + +## Negative scoped implementations + +It's technically possible to allow negative scoped implementations that only shadow the respective implementation from an outer scope. For example: + +```rust +// signed-indexing/src/arrays/prelude.rs +use core::ops::Index; + +pub use impl !Index for [T; N] {} +pub use impl Index for [T; N] { + type Output = T; + + #[inline] + #[track_caller] + fn index(&self, index: isize) -> &T { + match index { + 0.. => self[index as usize], + ..=-1 => if let Some(index) = self.len().checked_add_signed(index) { + self[index] + } else { + #[inline(never)] + #[track_caller] + fn out_of_bounds(len: usize, index: isize) -> ! { + panic!("Tried to index slice of length {len} with index {index}, which is too negative to index backwards here."); + } + + out_of_bounds(self.len(), index); + }, + } + } +} +``` + +```rust +use signed_indexing::arrays::prelude::*; + +let array = [1, 2, 3]; + +// Unambiguous: +let first = array[0]; +let last = array[-1]; +``` + +This is likely a rather niche use-case. + +It could also be useful in the context of [scoped-fallback-implementations]. + +## Explicit binding +[explicit-binding]: #explicit-binding + +It could be possible to explicitly state bindings. Here is an example: + +```rust +use std::collections::BinaryHeap; + +// Contains discrete implementations of `PartialOrd` and `Ord` that invert the comparison. +mod reverse; + +// Uses whichever implementation is in scope. +let max_heap: BinaryHeap = [1, 3, 2, 4].into(); + +// Explicit binding. Requirements are like for a discrete import. +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); + +while let Some(max) in max_heap.pop() { + println!("{max}"); // 4, 3, 2, 1 +} + +while let Some(min) in min_heap.pop() { + println!("{min}"); // 1, 2, 3, 4 +} + +// Uses whichever implementation is in scope. +dbg!(::cmp(&1, &2)); // […] = Less + +// Explicit binding. Requirements are like for a discrete import. +// (How to specify a scoped implementation with supertraits?) +dbg!(::cmp(&1, &2)); // […] = Greater + +{ + let mut a = max_heap; + let mut b = min_heap; + + // a.append(&mut b); + // ^^^^^^ error[E0308]: mismatched types +} +``` + +This is of course syntax bikeshedding. + +Binding only `PartialEq` would still shadow the discrete global `Ord` implementation, so binding both is required. + +As the scoped implementation of `Ord` in `reverse` is on a discrete type, it requires the specific supertrait implementation that is in scope for its definition. This should make it possible to infer the module here. (See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] below.) + +For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit bindings should be allowed where no matching bound is present, but should produce an "unused" warning iff neither published nor used in the same crate (including for type identity distinction). + +## Implicit import of supertrait implementations of scoped implementations defined on discrete types +[implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types]: #implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types + +As subtype implementations defined on discrete types always require specific supertrait implementations, the import of these supertrait implementations could be made implicit. + +This would also affect [explicit-binding], changing + +```rust +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +``` + +to + +```rust +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +``` + +and + +```rust +// (How to specify a scoped implementation with supertraits?) +dbg!(::cmp(&1, &2)); // […] = Greater +``` + +to + +```rust +dbg!(::cmp(&1, &2)); // […] = Greater +``` + +The downside is that `use`-declarations would become less obvious. Implied supertrait implementation imports could be enabled only for [explicit-binding] to avoid this. + +If this is added later than scoped `impl Trait for Type`, then private scoped implementations **must not** be implicitly exported through this mechanism. (It's likely a good idea to not allow that anyway, as it would be surprising.) Making previously crate-private implementations available that way could lead to unsoundness. From dfa5a2e835c92e598e922c03b0ee97835a4848a5 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 26 Nov 2023 06:14:02 +0100 Subject: [PATCH 02/27] Added section Layout-compatibility --- text/0000-scoped-impl-trait-for-type.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 276eb3c8570..7d38175adee 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -377,6 +377,10 @@ fn main() { ¹ With the current implementation, this would likely say `Generic<_>: From>>`, which isn't helpful. With [explicit-binding], it could say `Generic: From>>`. +## Layout-compatibility + +Types whose identities are only distinct because of a difference in captured scoped implementations remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. + ## No interception/no proxies That each scoped `impl Trait for Type { /*...*/ }` is in scope for itself makes use of the implementation it shadows in the consumer scope *inexpressible*. There can be no scoped implementation constrained to always shadow another. From 7cd3473c43cc85912adbae4453d4b9b33417207f Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 3 Dec 2023 01:25:14 +0100 Subject: [PATCH 03/27] Pre-RFC v2 - Defined rules for the observed `TypeId` of generic type parameters' opaque types, rewrote sections on type identity and layout-compatibility - Added section on sealed traits - Corrected the use of 'blanket implementation' vs. 'generic implementation' vs. 'implementation on a generic type' - Sketched two more warnings and one more error - Added a section Behaviour change/Warning: `TypeId` of generic discretised using generic type parameters - Removed the Trait binding site section but kept Coercion to trait objects and slightly expanded it - Added section Unexpected behaviour of `TypeId::of::()` in implementations on generics in the consumer-side presence of scoped implementations and `transmute` - Near-completely rewrote the Rationale and alternatives section with subheadings and before/after-style examples, added more positive effects of the feature - Rewrote Alternatives section - Removed some Unresolved questions that are now tentatively resolved - Added top-level syntax and a field example to Explicit binding, elaborated a bit more - Added Future possibilities: - Conversions where a generic only cares about specific bounds' consistency - Scoped bounds as contextual alternative to sealed traits - Glue crate suggestions - Various small fixes, adjustments and clarifications --- text/0000-scoped-impl-trait-for-type.md | 1284 ++++++++++++++++++----- 1 file changed, 1005 insertions(+), 279 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 7d38175adee..3df7e0f71dc 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -19,9 +19,11 @@ For example, while many crates support `serde::{Deserialize, Serialize}` directl Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. -Another pain point are sometimes missing `Into<>`-conversions when propagating errors with `?`, even though one external residual (payload) type may (sometimes *contextually*) be cleanly convertible into another. As-is, this usually requires a custom intermediary type, or explicit conversion using `.map_err(|e| ...)` (or equivalent function/extension trait). If an appropriate `From<>`-conversion can be provided *in scope*, then just `?` can be used. +Another pain point are sometimes missing `Into<>`-conversions when propagating errors with `?`, even though one external residual (payload) type may (sometimes *contextually*) be cleanly convertible into another. As-is, this usually requires a custom intermediary type, or explicit conversion using `.map_err(|e| …)` (or an equivalent function/extension trait). If an appropriate `From<>`-conversion can be provided *in scope*, then just `?` can be used. -This RFC aims to address these pain points by creating a new path of least resistance that is easy to use and very easy to teach, intuitive to existing Rust-developers, readable without prior specific knowledge, discoverable as needed, has opportunity for rich tooling support in e.g. rust-analyzer and helpful error messages, is quasi-perfectly composable including decent re-use of composition, improves maintainability and (slightly) robustness to minor-version dependency changes compared to newtype wrappers, and does not restrict crate API evolution, compromise existing coherence rules or interfere with future developments like specialisation. Additionally, it allows the implementation of more expressive (but no less explicit) extension APIs using syntax traits like in the `PartialEq<>`-example below, without complications should these traits be later implemented in the type-defining crate. +This RFC aims to address these pain points by creating a new path of least resistance that is easy to use and very easy to teach, intuitive to existing Rust-developers, readable without prior specific knowledge, discoverable as needed, has opportunity for rich tooling support in e.g. rust-analyzer and helpful error messages, is quasi-perfectly composable including decent re-use of composition, improves maintainability and (slightly) robustness to major-version dependency changes compared to newtype wrappers, and does not restrict crate API evolution, compromise existing coherence rules or interfere with future developments like specialisation. Additionally, it allows the implementation of more expressive (but no less explicit) extension APIs using syntax traits like in the `PartialEq<>`-example below, without complications should these traits be later implemented in the type-defining crate. + +For realistic examples of the difference this makes, please check the [rationale-and-alternatives] section. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -90,7 +92,13 @@ Note that the scoped implementation of `UnsafeTrait` is imported without the `un Generic parameters, bounds and `where`-clauses can be used as normal in each of these locations, though you usually have to brace `impl Trait for Type where /*...*/` individually in `use`-declarations. -You can import a subset of a blanket implementation, by narrowing bounds or replacing type parameters with concrete types in the `use`-declaration. +You can import a subset of a generic implementation, by narrowing bounds or replacing type parameters with concrete types in the `use`-declaration. + +Global implementations can be imported from the root namespace, for example to shadow a scoped implementation: + +```rust +use ::{impl Trait for Type}; +``` ### Scoped implementations and generics [scoped-implementations-and-generics]: #scoped-implementations-and-generics @@ -228,7 +236,7 @@ The core Rust language grammar is extended as follows: (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) - **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [resolution-at-type-instantiation-site]). + **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [generic-type-parameters-capture-scoped-implementations]). [*TraitImpl*]: https://doc.rust-lang.org/reference/items/implementations.html?highlight=TraitImpl#implementations @@ -248,13 +256,13 @@ The core Rust language grammar is extended as follows: [E0178]: https://doc.rust-lang.org/error_codes/E0178.html - (Allowing unbraced imports like `use some_crate::impl Trait
for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy pretty badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced.) + (Allowing unbraced imports like `use some_crate::impl Trait for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy quite badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added for convenience, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced.) Here, *TraitCoverage* imports the specified scoped `impl Trait for Type` for binding and conflict checks as if defined in the scope containing the `use`-declaration. The resulting visibility is taken from *UseDeclaration*, like with *SimplePath*-imported items. *TraitCoverage* must be fully covered by the scoped implementation visible in the source module. Otherwise, a compile-error occurs explaining the uncovered case (similarly to the current error(s) for missing trait implementations). - ***TraitCoverage* may subset the source module's implementation** by having more narrow bounds or using concrete types in place of one or more generic type parameters. This causes only the specified subset of the scoped implementation to be imported. + ***TraitCoverage* may subset the source module's implementation** by having narrower bounds or using concrete types in place of one or more generic type parameters. This causes only the specified subset of the scoped implementation to be imported. Note that scoped implementations of `unsafe` traits are imported without `unsafe`. It is the exporting crate's responsibility to ensure a scoped implementation is sound everywhere it is visible. @@ -282,21 +290,64 @@ is syntactically valid, but rejected by the compiler with a specific error. (See This also applies to `impl Trait`s in `use`-declarations (even though the items they would import cannot be defined anyway. Having a specific error saying that this *isn't possible* would be much clearer than one saying that the imported item doesn't exist). -## Resolution at type instantiation site -[resolution-at-type-instantiation-site]: #resolution-at-type-instantiation-site +## No external scoped implementations of sealed traits +[no-external-scoped-implementations-of-sealed-traits]: #no-external-scoped-implementations-of-sealed-traits + +Consider this library crate: + +```rust +pub struct Generic(T); + +mod private { + // Implemented only on traits that are also `Sealed`. + pub trait Sealing {} +} +use private::Sealing; + +pub trait Sealed: Sealing { + fn assumed { + // (2) + } +} + +impl Generic { + fn assuming { + // (1) + } +} +``` + +In this crate, any code at (1) is currently allowed to make safety-critical assumptions about code at (2) and other implementations of `assumed`. + +To ensure this stays sound, scoped `impl Trait for Type` where `Trait` is external requires that all supertraits of `Trait` are visible to the crate defining the scoped implementation or are defined not in `Trait`'s definition crate (meaning they must still be exported from a crate somewhere in the dependency tree). + +See also [scoped-implementation-of-external-sealed-trait]. -When a generic like the type of a generic function or a generic struct type is instantiated into a less generic type, trait implementations **on generic type parameters** are bound in the scope of the consumer performing the instantiation. This also applies to bounds on associated types of generic type parameters. See [trait-binding-site] for more details. +## Generic type parameters capture scoped implementations +[generic-type-parameters-capture-scoped-implementations]: #generic-type-parameters-capture-scoped-implementations -As an exception to this, type parameter defaults bind traits where they are defined. These bindings are used whenever the type parameter default is used. +When a type parameter is specified, either explicitly or inferred from an expression, it captures *all* scoped implementations from its scope that are applicable to its type. -If an additional trait must be resolved on a generic type parameter, for example due to a bound on an implementation, then an implementation is resolved at the place where implementations for inherent bounds on that type parameter (would) have been resolved. +When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type parameters are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type parameter is specified. -## Type identity +When a generic type parameter is used to discretise another generic, the view captured in the latter is based on the view captured in the former and modified by scoped implementations and shadowing applicable to that generic type parameter's opaque type. -Instances of generics with distinct implementations available to their generic type parameters are distinct: +Note that type parameter defaults too capture implementations where they are specified, so at the initial definition site of the generic. This capture is used whenever the type parameter default is used. + +## Type identity of discrete types +[type-identity-of-discrete-types]: #type-identity-of-discrete-types + +The type identity and `TypeId::of::<…>()` of discrete types, including discretised generics, are not affected by scoped implementations *on* them. + +## Type identity of generic types +[type-identity-of-generic-types]: #type-identity-of-generic-types + +The type identity of generic types is derived from the types specified for their type parameters as well as the *full* captured view of available implementations for each of their type parameters: ```rust +#[derive(Default)] struct Type; +#[derive(Default)] struct Generic(T); trait Trait {} @@ -343,13 +394,14 @@ use mod5::Alias5; fn main() { use std::any::TypeId; + use tap::Conv; // Distinct implementations produce distinct types. assert_ne!(TypeId::of::(), TypeId::of::()); assert_ne!(TypeId::of::(), TypeId::of::()); - // Types with the same implementations available are the same type. + // Types with the same captured implementations are still the same type. assert_eq!(TypeId::of::(), TypeId::of::()); // Top-level implementations are not part of type identity. @@ -363,9 +415,9 @@ fn main() { // as from its perspective, the binding is stripped due to being top-level. Alias1::nested_convertible(Alias2::default()); - // The reflexive `impl Into for T` does not apply here, - // as the distinct binding is on a type parameter. - // (It's unfortunately not possible to blanket-implement this conversion without specialisation.) + // The reflexive `impl Into for T` does not to the generic here, + // as the distinct capture in the type parameter affects its inherent identity. + // (It's unfortunately not possible to generically implement this conversion without specialisation.) // Alias1::default().conv::(); // ^^^^ error[E0277]: the trait bound `[…]¹` is not satisfied @@ -375,15 +427,168 @@ fn main() { } ``` +As mentioned in [type-identity-of-discrete-types], implementations on the generic type *itself* do *not* affect its type identity, as can be seen with `Alias4` above. + +The `TypeId` of discretised generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] instead. + ¹ With the current implementation, this would likely say `Generic<_>: From>>`, which isn't helpful. With [explicit-binding], it could say `Generic: From>>`. +## `TypeId` of generic type parameters' opaque types +[typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types + +In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to captured view of *only implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: + +```rust +use std::any::TypeId; + +#[derive(Default)] +struct Type; +trait Trait {} +impl Trait for Type {} + +mod nested { + use super::{Trait, Type}; + use impl Trait for Type {}; + pub type Distinct = (Type,); +} +use nested::Distinct; + +fn no_bound(_: (T,), _: (U,)) { + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::<(T,)>(), TypeId::of::<(U,)>()); + + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_eq!(TypeId::of::(), TypeId::of::()); +} + +fn yes_bound(_: (T,), _: (U,)) { + assert_ne!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::<(T,)>(), TypeId::of::<(U,)>()); + + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::(), TypeId::of::()); +} + +fn main() { + no_bound((Type,), Distinct::default()); + yes_bound((Type,), Distinct::default()); +} + +``` + +In particular: + +- If no bound-relevant scoped implementations are captured in a type parameter, then the `TypeId` of the opaque type of that type parameter is identical to that of the discrete type specified for that type parameter. +- Distinct sets of bound-relevant captured scoped implementations lead to distinct `TypeId`s of the opaque type of a type parameter. +- If the set of bound-relevant captured scoped implementations in two generic type parameters is the same, and the captured discrete type is identical, then the `TypeId` of the opaque types of these generic type parameters is identical. +- If a generic type parameter is distinguishable this way, it remains distinguishable in called implementations even if those have fewer bounds - the relevant distinction is 'baked' into the generic type parameter's opaque type. + +These rules (and the transmutation permission in [layout-compatibility]) allow the following collection to remain sound with minimal perhaps unexpected behaviour: + +```rust +use std::{ + any::TypeId, + collections::{ + hash_map::{HashMap, RandomState}, + HashSet, + }, + hash::{BuildHasher, Hash}, + mem::drop, +}; + +use ondrop::OnDrop; + +#[derive(Default)] +pub struct ErasedHashSet<'a, S: 'a + BuildHasher + Clone = RandomState> { + storage: HashMap, + droppers: Vec>>, +} + +impl ErasedHashSet<'_, RandomState> { + pub fn new() -> Self { + Self::default() + } +} + +impl<'a, S: BuildHasher + Clone> ErasedHashSet<'a, S> { + pub fn with_hasher(hasher: S) -> Self { + Self { + storage: HashMap::with_hasher(hasher), + droppers: vec![], + } + } + + // This is the important part. + pub fn insert(&mut self, value: T) -> bool + where + T: Hash + Eq + 'static, // <-- Bounds. + { + let type_id = TypeId::of::(); // <-- `TypeId` depends on implementations of bounds. + let storage: *mut () = if let Some(storage) = self.storage.get_mut(&type_id) { + *storage + } else { + let pointer = Box::into_raw(Box::new(HashSet::::with_hasher( + self.storage.hasher().clone(), + ))); + self.droppers.push(OnDrop::new(Box::new(move || unsafe { + // SAFETY: Only called once when the `ErasedHashSet` is dropped. + // The type is still correct since the pointer wasn't `.cast()` yet and + // both `S` and `T` are bounded on `'a`, so they are still alive at this point. + drop(Box::from_raw(pointer)); + }))); + self.storage + .insert(type_id, pointer.cast::<()>()) + .expect("always succeeds") + }; + + let storage: &mut HashSet = unsafe { + // SAFETY: Created with (close to) identical type above. + // Different `Hash` and `Eq` implementations are baked into `T`'s identity because of the bounds, so they result in distinct `TypeId`s above. + // It's allowed to transmute between types that differ in identity only by bound-irrelevant captured implementations. + // The borrowed reference isn't returned. + &mut *(storage.cast::>()) + }; + storage.insert(value) + } + + // ... +} +``` + +In particular, this code will ignore any scoped implementations on `T` that are not `Hash`, `Eq` or (implicitly) `PartialEq`, while any distinct set of discrete type, `Hash`, `Eq` and `PartialEq` is cleanly separated. + +See also [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for how to lint for an implementation of this collection that uses `TypeId::of::()` as key, which *also* remains sound and deterministic but distinguishes too aggressively by irrelevant scoped implementations in consumer code, leading to unexpected behaviour. + ## Layout-compatibility +[layout-compatibility]: #layout-compatibility + +Types whose identities are only distinct because of a difference in captured scoped implementations on their generic type parameters remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. + +It is sound to transmute an instance between these types **if** no inconsistency is observed on that instance by the bounds of any external-to-the-`transmute` implementation or combination of implementations, including scoped implementations and implementations on discrete variants of the generic. As a consequence, the `Self`-observed `TypeId` of instances of generic types **may** change in some cases. -Types whose identities are only distinct because of a difference in captured scoped implementations remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. +For example, given a library + +```rust +#[derive(Debug)] +pub struct Type(T); + +impl Type { + pub fn method(&self) {} +} +``` + +then in another crate + +- if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again then and +- if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured implementations, because `method` has observed the *exact* type parameter through its constraints. + +(In short: Don't use external-to-your-code implementations with the instance in any combination that couldn't have been done without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) + +See [typeid-of-generic-type-parameters-opaque-types] for details on what this partial transmutation permission is for, and [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for a future incompatibility lint that could be used to warn implementations where this is relevant. ## No interception/no proxies -That each scoped `impl Trait for Type { /*...*/ }` is in scope for itself makes use of the implementation it shadows in the consumer scope *inexpressible*. There can be no scoped implementation constrained to always shadow another. +That each scoped `impl Trait for Type { /*...*/ }` is in scope for itself makes the use of the implementation it shadows in the consumer scope *inexpressible*. There can be no scoped implementation constrained to always shadow another. This is intentional, as it makes the following code trivial to reason about: @@ -399,12 +604,12 @@ This is intentional, as it makes the following code trivial to reason about: (The main importance here is to not allow non-obvious dependencies of imports. Implementations can still access associated items of a *specific* other implementation by bringing it into a nested scope or binding to its associated items elsewhere. See also [independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations].) -## Contextual monomorphisation of blanket implementations and generic functions -[contextual-monomorphisation-of-blanket-implementations-and-generic-functions]: #contextual-monomorphisation-of-blanket-implementations-and-generic-functions +## Contextual monomorphisation of generic implementations and generic functions +[contextual-monomorphisation-of-generic-implementations-and-generic-functions]: #contextual-monomorphisation-of-generic-implementations-and-generic-functions -Traits of generic type parameters (and of their associated types, recursively) are resolved to implementations according to the binding site of the blanket-implemented trait or generic function, which is in the code consuming that implementation. +Traits of generic type parameters (and of their associated types, recursively) are resolved to implementations according to the binding site of the generically implemented trait or generic function, which is in the code consuming that implementation. -This means blanket implementations are monomorphised in different ways for the same type(s) when there is a difference in relevant scoped implementations for these types. +This means generic implementations are monomorphised in different ways for the same type(s) when there is a difference in relevant scoped implementations for these types. ```rust struct Type; @@ -429,7 +634,7 @@ impl Trait2 for T { } trait Trait3 { - fn trait3(&self); + fn trait3(&self); } impl Trait3 for Type2 where @@ -452,23 +657,23 @@ Type2(Type).trait3(); // "global" Type2(Type).trait3(); // "scoped1" { - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped2"); - } - } + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped2"); + } + } - Type2(Type).trait3(); // "scoped2" + Type2(Type).trait3(); // "scoped2" } { - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped3"); - } - } + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped3"); + } + } - Type2(Type).trait3(); // "scoped3" + Type2(Type).trait3(); // "scoped3" } } ``` @@ -520,7 +725,7 @@ Clearly, `function1` cannot be used here, as its generic bounds would have to bi But what about `function2`? Here, the bound is implicit but `Deref::deref` can still be accessed. For type compatibility, this would have to be the shadowed global implementation, which is most likely unintended decoherence. -As such, **shadowing a trait implementation also shadows all respective subtrait implementations**. Note that the subtrait *may* still be immediately available (again), if it is blanket-implemented and all bounds can be satisfied in the relevant scope: +As such, **shadowing a trait implementation also shadows all respective subtrait implementations**. Note that the subtrait *may* still be immediately available (again), if it is implemented with a generic target and all bounds can be satisfied in the relevant scope: ```rust trait Trait1 { @@ -551,7 +756,7 @@ impl Trait1 for Type { } ``` -If a subtrait implementation is brought into scope, it must be either a blanket implementation, or an implementation on a discrete type making use of the identical supertrait implementations in that scope. (This rule is automatically fulfilled by scoped implementation definitions, so it's only relevant for which scoped implementations can be imported via `use`-declaration.) +If a subtrait implementation is brought into scope, it must be either an implementation with a generic target, or an implementation on a discrete type making use of the identical supertrait implementations in that scope. (This rule is automatically fulfilled by scoped implementation definitions, so it's only relevant for which scoped implementations can be imported via `use`-declaration.) ## Independent trait implementations on discrete types may still call shadowed implementations [independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations]: #independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations @@ -625,8 +830,7 @@ assert_ne!(TypeId::of::(), TypeId::of::>()); Here, the scoped implementation `use impl Trait for Type {}` **is** used as it is captured into the type identity of `Alias`. -Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. -If it can ensure that that is the case, a (different!) warning could in theory still be shown here. +Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. If it can ensure that that is the case, a (different!) warning could in theory still be shown here. ### Global trait implementation available [global-trait-implementation-available]: #global-trait-implementation-available @@ -635,6 +839,8 @@ Scoped implementations and `use`-declarations of such receive a specific warning (Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) +It would make sense to let the definitions and also alternatively specific global implementations of traits with high implementation stability requirements like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. + ### Self-referential bound of scoped implementation ```rust @@ -644,7 +850,7 @@ use impl Foo for T where T: Foo { } --------- ^^^^^^ ``` -A Rust developer may write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the same global implementation, cannot apply to any types at all. +A Rust developer may write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. The warning should explain that and why the bound is impossible to satisfy. @@ -681,6 +887,80 @@ pub use impl Trait for Type {} should produce two distinct warnings similarly to those for private items in public signatures, as the limited visibilities of `Type` and `Trait` independently prevent the implementation from being imported in modules for which it is declared as visible. +### Scoped implementation is less visible than item/field it is captured in +[scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in + +The code + +```rust +pub struct Type; +pub struct Generic(U, V); + +trait Trait {} // <-- Visibility of the trait doesn't matter for *this* warning. + +use impl Trait for Type {} +----------------------- + +pub type Alias = Generic; + ^^^^ ^^^^ + +pub fn function(value: Generic) -> Generic { + ^^^^ ^^^^ ^^^^ ^^^^ + value +} + +pub struct Struct { + private: Generic, // This is fine. + pub public: Generic, + ^^^^ ^^^^ +} +``` + +should produce eight warnings (or four/three warnings with multiple primary spans each, if possible). The warning should explain that the type can't be referred to by fully specified name outside the crate/module and that the implementation may be callable from code outside the crate/module. + +(If [explicit-binding] is added to the RFC and used in such a way, then the warning should show up on the `Trait in module` span instead.) + +Note that as with other private-in-public warnings, replacing + +```rust +use impl Trait for Type {} +``` + +with + +```rust +mod nested { + use super::{Trait, Type}; + pub use impl Trait for Type {} +} +use nested::{impl Trait for Type}; +``` + +in the code sample above should silence the warning. + +### Imported implementation is less visible than item/field it is captured in +[imported-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #imported-implementation-is-less-visible-than-itemfield-it-is-captured-in + +This occurs under the same circumstances as above, except that + +```rust +trait Trait {} +use impl Trait for Type {} +``` + +is replaced with + +```rust +use a_crate::{ + Trait, + impl Trait for Type, +}; +``` + +(where here the implementation import is subsetting a blanket import, but that technicality isn't relevant. What matters is that the implementation is from another crate). + +If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in a public signature or item?). + ## Errors ### Global implementation of trait where global implementation of supertrait is shadowed @@ -746,6 +1026,51 @@ Rustc should suggest to import the required scoped implementation, if possible. See also the warning [private-supertrait-implementation-required-by-public-implementation]. See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] for a potential way to improve the ergonomics here. +### Scoped implementation of external sealed trait +[scoped-implementation-of-external-sealed-trait]: #scoped-implementation-of-external-sealed-trait + +Given crate `a`: + +```rust +mod private { + pub trait Sealing {} +} +use private::Sealing; + +pub trait Sealed: Sealing {} + +pub use impl Sealed for T {} // Ok. +``` + +And crate `b`: + +```rust +use a::{ + Sealed, + impl Sealed for usize, // Ok. +}; + +use impl Sealed for () {} // Error. + ^^^^^^ +``` + +Crate `b` cannot define scoped implementations of the external sealed trait `Sealed`, but can still import them. + +See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. + +## Behaviour change/Warning: `TypeId` of generic discretised using generic type parameters +[behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters + +As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for generics they discretise using type parameters may be called on the same value instance. + +Notably, this affects `TypeId::of::()` in implementations with generic targets, but not in unspecific blanket implementations on the type parameter itself. + +This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. + +In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show the expected `TypeId` distinction. + +Instead of `TypeId::of::<(U, V, W)>()`, `(TypeId::of::(), TypeId::of::(), TypeId::of::())` can usually be used. + ## Resolution on generic type parameters [resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters @@ -864,108 +1189,7 @@ where Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. -## Trait binding site -[trait-binding-site]: #trait-binding-site - -This is analogous to the site of resolution of not fully qualified type paths, and of function items and attached functions at function call sites, which depend on which items are in scope. - -Unlike bindings on discretised generic type parameters, which are reused from the original type annotation or inference site, binding of top-level implementations can happen in more places and is transient (unless captured into an inferred generic type parameter). - -The exact token/token-span from which to start the lookup does matter here, both for [`Span::def_site()`] and mitigations for [first-party-implementation-assumptions-in-macros]. - -Whenever possible, the explicitly stated [*Type*] is used, unless it is [*InferredType*]. Otherwise, the most narrow token or span *in that statement* that represents the value whose type is inferred is used. - -[`Span::def_site()`]: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.def_site -[*Type*]: https://doc.rust-lang.org/stable/reference/types.html?highlight=Type#type-expressions -[*InferredType*]: https://doc.rust-lang.org/stable/reference/types/inferred.html?highlight=InferredType#inferred-type - -An almost certainly incomplete list of examples: - -(*This section is pretty rough. I think I need more information here to form a clearer picture. See also the relevant remark in [unresolved-questions].*) - -### *Type* in *GenericParams* in *Struct* - -Implementations for type parameter defaults are captured from the top-level implementations on the defined default: - -```rust -pub struct MyHashSet { /*...*/ } - ----------- -``` - -This means that the type identity is never split by scoped implementations on the default type parameter, as every such use sees the same set here. To capture different scoped implementations, the type parameter has to be explicitly inferred or stated by the consumer. - -### *Type* in *StructField*, *TypeAlias* and in *LetBinding* - -```rust -struct MyStruct { - set: MyHashSet, - ----- ----------- - - // Binds on `usize` here, but uses trait bindings of/at default definition `RandomState` in `MyHashSet`'s definition. - set: MyHashSet, - ----- -} -``` - -```rust -let set: MyHashSet; - ----- ----------- - -// Does not bind implementations. -// The bindings are inferred along with the generic type parameters. -let inferred: MyHashSet<_, _>; - -// Fully written-out types bind/capture regardless of expression. -let set2: MyHashSet = any_expression; - ----- ----------- -``` - -### Qualified calls of type-associated functions - -```rust -// Binds at `usize` for the first generic type parameter, but infers the second entirely through `set`. -// The binds on the first parameter must match exactly. -MyHashSet::>::insert(&mut set, any_value) - ----- ~~~ - -// Binds implementations for both type parameters here. -// The type of `set` must be identical including those bindings! -MyHashSet::::insert(&mut set, any_value) - ----- ----------- -``` - -### Qualified calls of trait-associated functions - -```rust -// Captures implementations on `usize` here. `set`'s type must match that exactly. -// The binding of `Debug` on the `MyHashSet` type is transient and *doesn't have* to match! - as Debug>::fmt(&set, &mut formatter) - ----- ~~~ - --------------------- ----- - -// Infers all type parameters through `set`. -// As above, the top-level binding is transient. -::fmt(&set, &mut formatter) - ~~~ - --------- ----- -``` - -### Coercion to trait objects - -```rust -// Binds the trait `Debug` at the expression `1`, as the type is not explicitly stated. -// Bindings on `i32` here are erased from the type identity by the trait object coercion. -// However: Any scoped implementations available on the trait object `dyn Debug` are captured into the type identity of `&dyn Debug` here and may distinguish it! -let d: &dyn Debug = &1; - ^ - --------- - -// Binds the trait `Debug` at the expression `1`, as the type is not explicitly stated. -// Bindings on `()` here are erased from the type identity by the trait object coercion. -// However: Any scoped implementations available on the trait object `dyn Debug` are captured into the type identity of `Box` here and may distinguish it! -let b: Box = Box::<()>::new(()); - ^^ -``` +## Coercion to trait objects Due to the coercion into a trait object in the following code, the scoped implementation becomes attached to the value through the pointer meta data. This means it can then be called from other scopes: @@ -985,6 +1209,12 @@ fn function() -> &'static dyn Display { println!("{}", function()); // "scoped" ``` +This behaves exactly as a global implementation would. + +Note that the [`DynMetadata`]s of the reference returned above and one that uses the global implementation would compare as distinct even if both are "`&()`". + +[`DynMetadata`]: https://doc.rust-lang.org/stable/core/ptr/struct.DynMetadata.html + ## Interaction with return-position `impl Trait` Consider the following functions: @@ -1130,7 +1360,7 @@ There are a few ways to mitigate this, but they all have significant drawbacks: - Opt-in scoped-`impl Trait` transparency for macros - This would make scoped `impl Trait for Type`s much less useful, as they couldn't be used with for example most derive macros by default. It would also be necessary to teach the opt-in along with macros, which may not be realistic considering existing community-made macro primers. + This would make scoped `impl Trait for Type`s much less useful, as they couldn't be used with for example some derive macros by default. It would also be necessary to teach the opt-in along with macros, which may not be realistic considering existing community-made macro primers. Implementation is likely complicated because many procedural macros emit tokens only with `Span::call_site()` hygiene, so information on the distinct binding site origin may not be readily available. @@ -1152,6 +1382,12 @@ There are a few ways to mitigate this, but they all have significant drawbacks: This would at best be a partial fix and would block some interesting uses of [using-scoped-implementations-to-implement-external-traits-on-external-types]. +## Unexpected behaviour of `TypeId::of::()` in implementations on generics in the consumer-side presence of scoped implementations and `transmute` + +As explained in [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s in e.g. type-erased value-keyed collection like the `ErasedHashSet` example in the latter section. + +This use case appears to be niche enough in Rust to not have an obvious example on crates.io, but see [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for a lint that aims to mitigate issues in this regard and could be used to survey potential issues. + ## More `use`-declaration clutter, potential inconsistencies between files If many scoped implementations need to be imported, this could cause the list of `use`-declarations to become less readable. If there are multiple alternatives available, inconsistencies could sneak in between modules (especially if scoped `impl Trait for Type` is used in combination with [specialisation](https://rust-lang.github.io/rfcs/1210-impl-specialization.html)). @@ -1197,9 +1433,9 @@ Fortunately, at least checking whether scoped implementations exist at all for a That implementation binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. -## Cost of additional blanket implementation instances +## Cost of additional monomorphised implementation instances -The [contextual-monomorphisation-of-blanket-implementations-and-generic-functions] and the resulting additional instantiations of these implementations could have a detrimental effect on compile times and .text size (depending on optimisations). +The [contextual-monomorphisation-of-generic-implementations-and-generic-functions] and the resulting additional instantiations of these implementations could have a detrimental effect on compile times and .text size (depending on optimisations). This isn't unusual for anything involving *GenericParams*, but use of this feature could act as a multiplier to some extent. It's likely a good idea to evaluate relatively fine-grained caching in this regard, if that isn't in place already. @@ -1212,175 +1448,566 @@ There may be tricky to debug issues for their consumers if a `TypeId` doesn't ma A partial mitigation would be to have rustc include such captures on generic type parameters when printing types, but that wouldn't solve the issue entirely. -Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct captured implementations directly, as long as only these top-level implementations differ and no further nested ones. +Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct but bound-irrelevant captured implementations directly, as long as only these top-level implementations differ and no further nested ones. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- As outlined in the [motivation], there is considerable friction in the current form of Rust when combining traits and types from unrelated crates. Implementing newtypes for each combination is cumbersome and adds noise to consuming code, and these newtypes are often not well-reusable and usually don't compose well. +## Avoid newtypes' pain points -- With scoped `impl Trait for Type` as in this RFC, scoped implementations must be implemented or imported explicitly in the same file as they are bound, which stops this feature from introducing hard-to-follow non-local side-effects. Consumption of discretised generics also does not affect their identity, as the set of available - rather than actually used - scoped implementations on their type parameters is evaluated. +Alternative keywords: ergonomics and compatibility. -- Usage at the binding site is concise: An existing scoped implementation can always be imported in a single `use`-declaration as long as it is visible. A new scoped implementation can be created directly via `use impl Trait for Type { /*...*/ }`. Nothing else is needed compared to when a global implementation is available. +### Recursively dependent `#[derive(…)]` - - This is also true for blanket imports, which remain easy to parse visually due to the surrounding braces: +Many derives, like `Clone`, `Debug`, partial comparisons, `serde::{Deserialize, Serialize}` and `bevy_reflect::{FromReflect, Reflect}` require the trait to be implemented for each field type. Even with the more common third-party traits like Serde's, there are many crates with useful data structures that do not implement these traits directly. - ```rust - // This does NOT reliably redact information, - // as the scoped implementation is NOT bound on non-generic calls! - // Please use privacy wrappers like `redact::Secret` instead. - use omit_debug::{impl Debug for T}; +As such, glue code is necessary. - Err(()).unwrap(); // Does not show the usual `()`. - ``` +#### Current pattern - ```rust - use debug_by_display::{impl Debug for T}; +Some crates go out of their way to provide a compatibility mechanism for their derives, but this is neither the default nor has it (if available) any sort of consistency between crates, which means finding and interacting with these mechanisms requires studying the crate's documentation in detail. - print!("{:?}", "Hello!"); // Prints `Hello!` *without* surrounding double-quotes. - ``` +For derives that do not provide such a mechanism, often only newtypes like `NewSerdeCompatible` and `NewNeitherCompatible` below can be used. However, these do not automatically forward all traits (and forwarding implementations may be considerably more painful than the `derive`s), so additional glue code between glue crates may be necessary. - ```rust - use std::fmt; +```rust +use bevy_reflect::Reflect; +use serde::{Deserialize, Serialize}; + +use bevy_compatible::BevyCompatible; +use neither_compatible::NeitherCompatible; +use serde_compatible::SerdeCompatible; + +// I could not actually find much information on how to implement the Bevy-glue. +// I assume it's possible to provide at least this API by creating a newtype and implementing the traits manually. +use bevy_compatible_serde_glue::BevyCompatibleDef; +use neither_compatible_bevy_glue::NewNeitherCompatible; // Assumed to have `From`, `Into` conversions. +use neither_compatible_serde_glue::NeitherCompatibleDef; +use serde_compatible_bevy_glue::NewSerdeCompatible; // Assumed to have `From`, `Into` conversions. + +/// A typical data transfer object as it may appear in a service API. +#[derive(Deserialize, Serialize, Reflect)] +#[non_exhaustive] // Just a reminder, since the fields aren't public anyway. +pub struct DataBundle { + // Serde provides a way to use external implementations on fields (but it has to be specified for each field separately). + // Bevy does not have such a mechanism so far, so newtypes are required. + // The newtypes should be an implementation detail, so the fields are (for consistency all) private. + #[serde(with = "NewSerdeCompatibleDef")] + serde: NewSerdeCompatible, + #[serde(with = "BevyCompatibleDef")] + bevy: BevyCompatible, + #[serde(with = "NewNeitherCompatibleDef")] + neither: NewNeitherCompatible, +} + +// Some of the newtypes don't implement `Default` (maybe it was added to the underlying types later and the glue crate doesn't want to bump the dependency), +// so this has to be implemented semi-manually instead of using the `derive`-macro. +impl Default for DataBundle { + fn default() -> Self { + DataBundleParts::default().into() + } +} - // `Debug` and `Display` all `Pointer`-likes as addresses. - // The `Display` import is different only to show the long form - // with `where`. It could be written like the `Debug` import. - use cross_formatting::by_pointer::{ - impl Debug for T, - {impl Display for T where T: fmt::Pointer}, - }; +// If the Bevy glue doesn't forward the Serde implementations, this is necessary. +#[derive(Deserialize, Serialize)] +#[serde(remote = "NewSerdeCompatible")] +#[serde(transparent)] +struct NewSerdeCompatibleDef(SerdeCompatible); - println!("{}", &()); // For example: 0x7ff75584c360 - ``` +// Same as above, but here the implementation is redirected to another glue crate. +#[derive(Deserialize, Serialize)] +#[serde(remote = "NewNeitherCompatible")] +#[serde(transparent)] +struct NewNeitherCompatibleDef(#[serde(with = "NeitherCompatibleDef")] NeitherCompatible); - - The newtype pattern requires an additional wrapper type and, usually, `.into()`-conversions or other explicit function calls where the inner type is received or returned. If the newtype (for coherence-proofing) doesn't implement `Deref`, explicit field access is also necessary in some places. +impl DataBundle { + // These conversions are methods for discoverability. + pub fn from_parts(parts: DataBundleParts) -> Self { + parts.into() + } + pub fn into_parts(self) -> DataBundleParts { + self.into() + } -- This RFC introduces few new grammar elements and concepts. Rather, it mainly allows existing concepts to be reused elsewhere: + // Necessary to mutate multiple fields at once. + pub fn parts_mut(&mut self) -> DataBundlePartsMut<'_> { + DataBundlePartsMut { + serde: &mut self.serde.0, + bevy: &mut self.bevy, + neither: &mut self.neither.0, + } + } - - *UseDeclaration* and *TraitImpl* are changed in a way that's quick to summarise and remains easy to parse. (See [grammar-changes]) + // Accessors to the actual instances with the public types. + pub fn serde(&self) -> &SerdeCompatible { + &self.serde.0 + } + pub fn serde_mut(&mut self) -> &mut SerdeCompatible { + &mut self.serde.0 + } - - Shadowing of scoped implementations exactly matches that of other items. + // This also uses an accessor just for consistency. + pub fn bevy(&self) -> &BevyCompatible { + &self.bevy + } + pub fn bevy_mut(&mut self) -> &mut BevyCompatible { + &mut self.bevy + } - - Coherence rules for scoped implementations defined or imported in the same scope exactly match those for global implementations, minus orphan rules. + // More accessors. + pub fn neither(&self) -> &NeitherCompatible { + &self.neither.0 + } + pub fn neither_mut(&mut self) -> &mut NeitherCompatible { + &mut self.neither.0 + } +} - (Notably, this means that two potentially-conflicting blanket implementations still are not allowed. If *really* necessary, precedence can be disambiguated by nesting scopes, but narrowing the implementations or narrowly importing from a wide implementation in an inline submodule should be preferred where at all feasible!) +// Conversions for convenience +impl From for DataBundle { + fn from(value: DataBundleParts) -> Self { + Self { + serde: value.serde.into(), + bevy: value.bevy.into(), + neither: value.neither.into(), + } + } +} - - This contrasts with Genus's proper-named models, which allow mixed use in the same scope. +impl From for DataBundleParts { + fn from(value: DataBundle) -> Self { + Self { + serde: value.serde.into(), + bevy: value.bevy, + neither: value.neither.into(), + } + } +} - - This contrasts with 𝒢's overload mixing throughout the scope hierarchy. +/// Used to construct and destructure [`DataBundle`]. +#[derive(Default)] // Assume that all the actual field types have useful defaults. +#[non_exhaustive] +pub struct DataBundleParts { + pub serde: SerdeCompatible, + pub bevy: BevyCompatible, + pub neither: NeitherCompatible, +} - - If a crate wishes to provide alternative implementations (for example to blanket-provide a scoped `Display` implementation for all types that are `LowerHex` or `UpperHex`), it can still easily export them from distinct modules. +/// Return type of [`DataBundle::parts_mut`]. +#[non_exhaustive] +pub struct DataBundlePartsMut<'a> { + pub serde: &'a mut SerdeCompatible, + pub bevy: &'a mut BevyCompatible, + pub neither: &'a mut NeitherCompatible, +} +``` -- Scoped `impl Trait for Type`s compose very well: +If two traits that require newtype wrappers need to be added for the same type, the process can be even more painful than what's shown above, involving `unsafe` reinterpret casts to borrow a wrapped value correctly as each newtype and forwarding-implementing each trait manually if no transparent derive is available. - ```rust - use serde::{Deserialize, Serialize}; - use bevy_reflect::{FromReflect, Reflect}; - - use serde_compatible::A; - use bevy_compatible::B; - use neither_compatible::C; - - // This module actually contains the blanket implementations - // `pub use impl Serialize for T { /*...*/ }` and - // `pub use impl Deserialize<'_> for T { /*...*/ }`, - // so the import narrows it down. - use serde_by_bevy_reflect::{ - impl Serialize for B, - impl Deserialize<'_> for B, - }; - - use bevy_reflect_for_b::{ - impl FromReflect for B, - impl Reflect for B, - }; - - // You can concisely import all implementations from a module along with other items: - mod traits_for_c; - use traits_for_c::*; - // However, avoid this for external modules except `prelude`. - // Expanding the coverage of a scoped implementation visible in a `prelude` onto a previously existing type should always be considered a breaking change, so please be careful with blanket implementations there! - - #[derive(Debug, Clone, Reflect, Deserialize, Serialize)] - pub Type { - a: A, - b: B, - c: C, - } - ``` +#### With scoped `impl Trait for Type` - - Unlike with external newtypes, there are no potential conflicts beyond overlapping imports and definitions in the same scope. - These conflicts can *always* be resolved both without editing code elsewhere and without adding an additional implementation (either by narrowing local blanket implementations, possibly moving it into a submodule and importing for discrete types, or by narrowing a blanket implementation import to a subset of the external implementation). +Scoped `impl Trait for Type` eliminates these issues, in a standardised way that doesn't require any special consideration from the trait or derive crates: -- Since the trait implementation binding isn't stated explicitly at the binding site, this feature integrates well with implicit uses of traits, like in `?`-chains. +```rust +use bevy_reflect::Reflect; +use serde::{Deserialize, Serialize}; + +use bevy_compatible::BevyCompatible; +use neither_compatible::NeitherCompatible; +use serde_compatible::SerdeCompatible; + +// I could not actually find much information on how to implement Bevy-glue. +// It's about the same as manually implementing the traits for newtypes, though. +// Since many traits are required for `bevy_reflect`'s derives, those glue crates use the prelude pattern and provide one for each target type. +use bevy_compatible_serde_glue::{ + impl Deserialize<'_> for BevyCompatible, + impl Serialize for BevyCompatible, +}; +use neither_compatible_bevy_glue::preludes::neither_compatible::*; +use neither_compatible_serde_glue::{ + impl Deserialize<'_> for NeitherCompatible, + impl Serialize for NeitherCompatible, +}; +use serde_compatible_bevy_glue::preludes::serde_compatible::*; + +/// A typical data transfer object as it may appear in a service API. +#[derive(Default, Deserialize, Serialize, Reflect)] +#[non_exhaustive] +pub struct DataBundle { + // Everything just works. + pub serde: SerdeCompatible, + pub bevy: BevyCompatible, + pub neither: NeitherCompatible, +} + +// `Default` was derived normally. +// No glue for the glue is necessary. +// No conversions are needed to construct or destructure. +// `&mut`-splitting is provided seamlessly by Rust. +// No accessors are needed since the fields are public. +``` - This would make it easier for middleware and application frameworks to slot into each other concisely, as glue crates could provide seamless conversions. The configuration of this kind of glue would be easily reusable: +Even in cases where the glue API cannot be removed, it's still possible to switch to this simplified, easier to consume implementation and deprecate the original indirect API. - ```rust - // glue.rs - use database_middleware::ConnectionError; - use authentication_middleware::ActAsError; - use storage_middleware::StoreError; - use application_framework::IntentError; +Note that the imported scoped implementations are *not* visible in the public API here, since they do not appear on generic type parameters in public items. There may still be situations in which defining a type alias is necessary to keep some scoped implementations away from generic type parameters. For a possible future way to eliminate that remaining friction, see [explicit-binding] in the [future-possibilities] section below. - pub use database_application_glue::{impl From for IntentError}; - pub use authentication_application_glue::{impl From for IntentError}; +Unlike with external newtypes, there are no potential conflicts beyond overlapping imports and definitions in the same scope. These conflicts can *always* be resolved both without editing code elsewhere and without adding an additional implementation: - pub use impl From for IntentError { - // ... - } - ``` +- either by narrowing a local blanket implementation, +- by narrowing a blanket implementation import to a subset of the external implementation, +- or at worst by moving a generic implementation into a submodule and importing it for discrete types. + +### Error handling and conversions + +When implementing services, it's a common pattern to combine a framework that dictates function signatures with one or more unrelated middlewares that have their own return and error types. The example below is a very abridged example of this. + +Note that in either version, the glue code may be project-specific. Glue code is *very slightly* more concise when implemented with scoped `impl Trait for Type`, as intermediary `struct` definitions and the resulting field access can be avoided. + +#### Current pattern + +```rust +// crate `service` + +use framework::{Error, Returned}; +use middleware_a::{fallible_a, Error as ErrorA}; +use middleware_b::{fallible_b, Error as ErrorB}; + +use framework_middleware_a_glue::{IntoReturnedExt as _, NewErrorA}; +use framework_middleware_b_glue::{IntoReturnedExt as _, NewErrorB}; + +pub fn a() -> Result { + // A `try` block should work eventually, but it may be not much less verbose. + Ok((|| -> Result<_, NewErrorA> { + fallible_a()?; + Ok(fallible_a()?) + })()? + .into_returned()) +} + +pub fn b() -> Result { + // The same as above. + Ok((|| -> Result<_, NewErrorB> { + fallible_b()?; + Ok(fallible_b()?) + })()? + .into_returned()) +} + +pub fn mixed(condition: bool) -> Result { + // Neither 'NewError' type provided by third-party crates can be used directly here. + Ok((move || -> Result<_, NewError> { + Ok(if condition { + fallible_b()?; + fallible_a()?.into_returned() + } else { + fallible_a()?; + fallible_b()?.into_returned() + }) + })()?) +} + +// Custom glue to connect all three errors: +struct NewError(Error); +impl From for Error { + fn from(value: NewError) -> Self { + value.0 + } +} +impl From for NewError { + fn from(value: ErrorA) -> Self { + let intermediate: NewErrorA = value.into(); + Self(intermediate.into()) + } +} +impl From for NewError { + fn from(value: ErrorB) -> Self { + let intermediate: NewErrorB = value.into(); + Self(intermediate.into()) + } +} +``` + +```rust +use service::{a, b, mixed}; + +fn main() { + framework::setup() + .add_route("a", a) + .add_route("b", b) + .add_route("mixed", mixed) + .build() + .run(); +} +``` + +#### With scoped `impl Trait for Type` + +```rust +// crate `service` + +// More concise, since middleware errors are used only once in imports. +use framework::{Error, Returned}; +use middleware_a::fallible_a; +use middleware_b::fallible_b; + +// Note: It is often better to import `impl Into` here over `impl From`, +// since middleware types often don't appear in public signatures. +// +// If the target type of the import must appear as type parameter in a public signature, +// a module that is wildcard-imported into each function body can be used instead, +// which would amount to 6 additional and 2 modified lines here. +// +// This RFC includes a warning for unintentionally exposed scoped implementations. +use framework_middleware_a_glue::{ + impl Into for middleware_a::Returned, + impl Into for middleware_a::Error, +}; +use framework_middleware_b_glue::{ + impl Into for middleware_b::Returned, + impl Into for middleware_b::Error, +}; + +pub fn a() -> Result { + // It just works. + fallible_a()?; + Ok(fallible_a()?.into()) +} + +pub fn b() -> Result { + // Here too. + fallible_b()?; + Ok(fallible_b()?.into()) +} + +pub fn mixed(condition: bool) -> Result { + // This too just works, as conversions bind separately. + Ok(if condition { + fallible_b()?; + fallible_a()?.into() + } else { + fallible_a()?; + fallible_b()?.into() + }) +} + +// No custom glue is necessary at all. +``` + +```rust +// Unchanged. No change in the API of `service`, either. + +use service::{a, b, mixed}; + +fn main() { + framework::setup() + .add_route("a", a) + .add_route("b", b) + .add_route("mixed", mixed) + .build() + .run(); +} +``` + +Note that to export *discrete* scoped `impl Into` in addition to their scoped `impl From`, the glue crates can use the following pattern, which discretises the global implementation and as such binds each scoped `impl From`: + +```rust +pub use ::{ + impl Into for middleware_a::Returned, + impl Into for middleware_a::Error, +}; +``` + +## Preserve coherence + +### Cross-crate stability + +With this RFC, scopes are a 'mini version' of the environment that global implementations exist in. As this environment is sealed within one scope, and not composed from multiple crates that may update independently, the *orphan rule* is not necessary. + +*All other* coherence rules and (for exported implementations) rules for what is and is not a breaking change apply *within each scope exactly like for global implementations*. In particular: + +- Blanket implementations like ```rust - use application_framework::IntentError; - use crate::glue::*; + // (Does not compile!) - async fn submit_data(user: User, data: Data) -> Result { - Ok(SERVER_CONNECTIONS.lock_one()?.act_as(user)?.store_data(data)?) + use std::fmt::{Debug, LowerHex, Pointer}; + mod debug_by_lower_hex; + + use debug_by_lower_hex::{impl Debug for T}; // <-- + + use impl Debug for T { // <-- + // ... } ``` -- Public scoped implementations in other modules/crates are discoverable. + still conflict regardless of actual implementations of `LowerHex` and `Pointer` because they may overlap later and + +- because scoped implementation are *explicitly subset* where they are imported, *it is not a breaking change to widen an exported scoped implementation*. + + (This is part of the reason why scoped `impl Trait for Type`s are anonymous; names would make these imports more verbose rather than shorter, since the subsetting still needs to happen in every case.) + +### Logical consistency - - Unlike newtypes, rustc and rust-analyzer can index these and suggest fitting (non-blanket) `use`-declarations for a trait-type-pair, just as they suggest trait imports. +Binding external top-level implementations to types is equivalent to using their public API in different ways, so no instance-associated consistency is expected here. Rather, values that are used in the same scope behave consistently with regard to that scope's visible implementations. - - For scoped implementations in the current crate, it may be better to suggest a full blanket import of the implementation where applicable (e.g. `use crate::some_module::{impl Trait for T};`, where that coverage is copied *exactly* from the `use impl ...` item). That also nicely teaches that that's possible. +Generics are trickier, as their instances often do expect trait implementations on generic type parameters that are consistent between uses but not necessarily declared as bounded on the struct definition itself. - - Generic type parameters that are *not* the target should be suggested by default too, e.g. `use other_crate::{impl Trait for Container};` should be suggested instead of `use other_crate::{impl Trait for Container};`, even if the external implementation is the broader `pub use impl Trait for T { /*...*/ }`. A narrower import can still be suggested if the broad import would conflict. +This problem is solved by making the `impl`s available to each type parameter part of the the type identity of the discretised host generic, including a difference in `TypeId` as with existing monomorphisation. That said, implementations, especially generic functions where the distinction is not statically caught by a host instance's consistency, are not expected to care about implementations unrelated to their bounds. It is for this reason that the `TypeId` of generic type parameters disregards irrelevant implementations somewhat. - - rust-analyzer can suggest and create a new `use impl Trait for Type { /*...*/ }` item where no fitting implementation is visible. This is likely to be seen by someone about to use the newtype pattern, which makes the feature itself more discoverable too. +(See [generic-type-parameters-capture-scoped-implementations], [type-identity-of-generic-types] and [typeid-of-generic-type-parameters-opaque-types] in the [reference-level-explanation] above for more detailed information.) - However, for example `Debug`-implementations are often feature-gated, so it would be good if rust-analyzer had a way to suggest activating the feature more prominently than suggesting a local implementation. +### Logical stability -- Scoped `impl Trait for Type`s lead to better maintenance lints: +- Non-breaking changes to external crates cannot change the meaning of the program. +- Breaking changes should result in compile-time errors rather than a behaviour change. - - If a covering global implementation later becomes available through a dependency, a warning can be shown on the local trait implementation for review. (See [global-trait-implementation-available]) +This is another consequence of subsetting rather than named-model imports, as narrowing a scoped implementation can only make the `use`-declaration fail to compile, rather than changing which implementations are shadowed. - It would make sense to let the definitions and also alternatively specific global implementations of traits like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. +Similarly, types of generics with different captured implementations are strictly distinct from each other, so that assigning them inconsistently does not compile. This is weighed somewhat against ease of refactoring, so in cases where a type parameter is inferred and the host is used in isolation, which are assumed to not care about implementation details, the code will continue to align with the definition instead of breaking. -- Like abandoning orphan rules, scoped `impl Trait for Type` allows third-party crates to provide compatibility implementations for arbitrary combinations of other crates. +## Encourage readable code - Unlike abandoning orphan rules, using one of these implementations is an explicit consumer-choice (or directly inferred from one) and overlapping scoped implementations can exist without issue in the dependency graph or even in the same crate in distinct scopes, even if they overlap for an actually-used type for which that trait is actually bound. +This RFC aims to further decreases the mental workload required for code review, by standardising glue code APIs to some degree and by clarifying their use in other modules. + +It also aims to create an import grammar that can be understood more intuitively than external newtypes when first encountering it, which should improve the accessibility of Rust code somewhat. + +### Clear imports + +As scoped implementations bind implicitly like global ones, two aspects must be immediately clear at a glace: + +- *Which trait* is implemented? +- *Which type* is targeted? + +Restating this information in the `use`-declaration means that it is available without leaving the current file, in plaintext without any tooling assists. This is another improvement compared to newtypes or external definitions, where the relationship may not be immediately clear depending on their names. + +Spelling scoped implementation imports out with keywords rather than just symbols makes their purpose easy to guess for someone unfamiliar with the scoped `impl Trait for Type` feature, possibly even for most English-speaking developers unfamiliar with Rust. + +This is also true for blanket imports with `where`, which remain easy to parse visually due to the surrounding braces: + +```rust +use std::fmt::{Debug, Display, Pointer}; + +// `Debug` and `Display` all `Pointer`-likes as addresses. +// The `Display` import is different only to show the long form +// with `where`. It could be written like the `Debug` import. +use cross_formatting::by_pointer::{ + impl Debug for T, + {impl Display for T where T: Pointer}, +}; + +println!("{:?}", &()); // For example: 0x7ff75584c360 +println!("{}", &()); // For example: 0x7ff75584c360 +``` -- Like with newtype wrappers, use of the custom implementation is explicit, unambiguous, and robust/stable against all later-added or expanded trait implementations elsewhere, whether they are scoped or global. +### Familiar grammar - - This sets the feature apart from weakening coherence rules, which could introduce additional conflicts through feature additions. +The grammar for scoped implementations differs from that for global implementations by only a prefixed `use` and an optional visibility. As such, it should be easy to parse for developers not yet familiar with scoped implementations specifically. - - Scoped implementations are actually *more* robust than newtypes: +The clear prefix (starting with at least two keywords instead of one) should still be enough to distinguish scoped implementations at a glance from global ones. - A newtype that implements multiple traits could later gain a global blanket implementation of one of its traits for types that implement another of its trait, causing a conflict. In the presence of a scoped `impl Trait for Type`, the new blanket implementation would be unambiguously shadowed for existing code. +The header (the part before the `{}` block) of global implementations is reused unchanged for scoped implementation imports, including all bounds specifications, so there is very little grammar to remember additionally in order to `use` scoped `impl Trait for Type`s. - (Side-note: This also means it's not a great idea to put `Deref` on a newtype, but it's tempting to do so to avoid explicit field access everywhere. That seems like a bit of a footgun in the status quo.) +In each case, the meaning of identical grammar elements lines up exactly - only their context and perspective vary due to immediately surrounding tokens. -- Additionally splitting type identity (including `TypeId`) only by bindings on generic type parameters already satisfies consistency requirements for each of instantiated, static and dynamically managed collection use cases. +(See [grammar-changes] for details.) - - Not splitting type identity by top-level bindings avoids a large amount of unnecessary friction when passing values between modules. Types can rely on consistent implementations of traits on their generic type parameters, but they *already* cannot rely on outside use of their API aside from `unsafe` features. +### Stop tokens for humans + +When looking for the scoped implementation affecting a certain type, strict shadowing ensures that it is always the closest matching one that is effective. + +As such, readers can stop scanning once they encounter a match, instead of checking the entire file's length for another implementation that may be present in the outermost scope. + +Outside of implementations captured *inside* generics, scoped implementations cannot influence the behaviour of another file without being mentioned explicitly. + +## Unblock ecosystem evolution +[unblock-ecosystem-evolution]: #unblock-ecosystem-evolution + +As any number of scoped glue implementations can be applied directly to application code without additional compatibility shims, it becomes far easier to upgrade individual dependencies to their next major version. Compatibility with multiple versions of crates like Serde and `bevy_reflect` can be provided in parallel through officially supported glue crates. + +Additionally, scoped implementations are actually *more* robust than newtypes regarding certain breaking changes: + +A newtype that implements multiple traits could eventually gain a global blanket implementation of one of its traits for types that implement another of its traits, causing a conflict during the upgrade. + +In the presence of an overlapping scoped `impl Trait for Type`, the new blanket implementation is just unambiguously shadowed where it would conflict, which means no change is necessary to preserve the code's behaviour. A [global-trait-implementation-available] warning is still shown where applicable to alert maintainers of new options they have. + +(See also [glue-crate-suggestions] for possible future tooling related to this pattern.) + +### Side-effect: Parallelise build plans (somewhat) more + +Serde often takes a long time to build even without its macros. If another complex crate depends on it just to support its traits, this can significantly stretch the overall build time. + +If glue code for 'overlay' features like Serde traits is provided in a separate crate, that incidentally helps to reduce that effect somewhat: + +Since the glue forms a second dependency chain that normally only rejoins in application code, the often heavier core functionality of libraries can build in parallel to Serde and/or earlier glue. Since the glue chain is likely to be less code, it matters less for overall build time whether it has to wait for one or two large crates first. + +## Provide opportunities for rich tooling + +### Discovery of implementations + +As scoped implementations clearly declare the link between the trait and type(s) they connect, tools like rust-analyzer are able to index them and suggest imports where needed, just like for global traits. + +(At least when importing from another crate, the suggested import should be for a specific type or generic, even if the export in question is a blanket implementation. Other generics of the export can usually be preserved, though.) + +### Discovery of the feature itself + +In some cases (where a trait implementations cannot be found at all), tools can suggest creating a scoped implementation, unless adding it in that place would capture it in a type parameter of an item visible outside the current crate. + +That said, it would be great if rust-analyzer could detect and suggest/enable feature-gated global implementations to some extent, with higher priority than creating a new scoped implementation. + +### Rich and familiar warnings and error messages + +Since scoped implementations work much like global ones, many of the existing errors and warnings can be reused with at most small changes. This means that, as developers become more familiar with either category of trait-related issues, they learn how to fix them for global and scoped implementations at the same time. + +The implementation of the errors and warnings in the compiler can also benefit from the existing work done for global implementations, or in some cases outright apply the same warning to both scoped and global implementations. + +Since available-but-not-imported scoped implementations are easily discoverable by the compiler, they can be used to improve existing errors like *error[E0277]: the trait bound `[…]` is not satisfied* and *error[E0599]: no method named `[…]` found for struct `[…]` in the current scope* with quick-fix suggestions in at least some cases. + +### Maintenance warnings for ecosystem evolution + +Scoped `impl Trait for Type`s lead to better maintenance lints: + +If a covering global implementation later becomes available through a dependency, a warning can be shown on the local trait implementation for review. (See [global-trait-implementation-available].) + +In the long run, this can lead to less near-duplicated functionality in the dependency graph, which can lead to smaller executable sizes. + +### Automatic documentation + +Scoped implementations can be documented and appear as separate item category in rustdoc-generated pages. + +Rustdoc should be able to detect and annotate captured implementations in public signatures automatically. This, in addition to warnings, should be another tool to help avoid accidental exposure of scoped implementations. + +Implementation origin and documentation could be surfaced by rust-analyzer in relevant places. + +## Alternatives + +### Named implementations + +Named implementations/models could be used to more-easily use potentially conflicting implementations in the same scope, but in exchange they would have to always be bound explicitly, which would likely hinder use outside of `derive`s and generics to an inconvenient level. + +Additionally, the use of named implementations is not as obvious as stating the origin-trait-type triple in close proximity. + +Scoped `impl Trait for Type` would not require proper-named models for later [explicit-binding], as the module already uniquely identifies an implementation for each type-trait combination. + +(See also [prior-art]: [lightweight-flexible-object-oriented-generics].) + +### Weakening coherence rules + +There is likely still some leeway here before the Rust ecosystem becomes brittle, but at least the orphan rule specifically is essential for ensuring that global trait implementations do not lead to hard ecosystem splits due to strictly incompatible framework crates. + +If *other* coherence rules are relaxed, scoped `impl Trait for Type` also benefits immediately since it is subject to all of them. + +### Crate-private implementations as distinct feature + +There is a previous [RFC: Hidden trait implementations] from 2018-2021 where the result was general acceptance, but postponement for logistical reasons. + +Scoped `impl Trait for Type` together with its warnings [scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in] and [imported-implementation-is-less-visible-than-itemfield-it-is-captured-in] can mostly cover this use-case, though with slightly more boilerplate (`use`-declarations) and not as-strict a limitation. + +[RFC: Hidden trait implementations]: https://github.com/rust-lang/rfcs/pull/2529 # Prior art [prior-art]: #prior-art ## Lightweight, Flexible Object-Oriented Generics +[lightweight-flexible-object-oriented-generics]: #lightweight-flexible-object-oriented-generics Yizhou Zhang, Matthew Loring, Guido Salvaneschi, Barbara Liskov and Andrew C. Myers, May 2015 @@ -1389,18 +2016,18 @@ Yizhou Zhang, Matthew Loring, Guido Salvaneschi, Barbara Liskov and Andrew C. My There are some parallels between Genus's models and the scoped `impl Trait for Type`s proposed in this RFC, but for the most part they are quite distinct due to Rust's existing features: | Genus | scoped `impl Trait for Type` | reasoning | -|---|---|---| +|---|---|---| | Proper-named models | Anonymous scoped implementations | Use of existing coherence constraints for validation. Forced subsetting in `use`-declarations improves stability. The `impl Trait for Type` syntax stands out in `use`-declarations and is intuitively readable. | -| Explicit bindings of non-default models | Only implicit bindings | Focus on simplicity. Mixed bindings for definitions with the same scope/type/trait triple are rare and can be emulated with newtypes where needed. More natural use with specialisation. | +| Explicit bindings of non-default models | Only implicit bindings | Focus on simplicity. Mixed bindings for definitions with the same scope/type/trait triple are rare and can be emulated with newtypes where needed. More natural use with future specialisation. | | Comparing containers inherently constrain type parameters in their type definition. | Available scoped implementations for discretised type parameters become part of the type identity. |

This is a tradeoff towards integration with Rust's ecosystem, as generics are generally not inherently bounded on collection types in Rust.

There is likely some friction here with APIs that make use of runtime type identity. See [split-type-identity-may-be-unexpected].

| Some features are largely equivalent: | Genus | Rust | notes / scoped `impl Trait for Type` | -|---|---|---| -| Implicit default models | Explicit global trait implementations | Implicit implementation of unknown external interfaces is unnecessary if third party crates' are as conveniently usable in scope as if global, as with scoped `impl Trait for Type`. | -| Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects.
This does not allow for invisible runtime specialisation in all cases. | -| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available scoped implementations on discretised type parameters are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of blanket implementations in Rust can simply be monomorphised accordingly, and existing reflexive conversions and comparisons can bind between similar type parameters.

| +|---|---|---| +| Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | +| Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available scoped implementations on discretised type parameters are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive conversions and comparisons can bind between similar type parameters.

| ## A Language for Generic Programming in the Large @@ -1413,7 +2040,7 @@ Jeremy G. Siek, Andrew Lumsdaine, 2007 A few notable similarities, in the paper's words: - equivalent retroactive modeling (where existing Rust's is limited by orphan rules), -- (retained) separate compilation (though *some* information can flow between items in this RFC, but only where such information flows exist in Rust already), +- (retained) separate compilation (though *some* information can flow between items in this RFC, but only where such information flows already exist in Rust currently), - lexically scoped models, - seemingly the same binding rules on generic type parameters within constrained models/generic implementations, @@ -1421,7 +2048,7 @@ and key differences: | 𝒢 | Rust / scoped `impl Trait for Type` | notes | |---|---|---| -| Only discrete model imports | Includes blanket imports and re-exports | This is pointed out as '[left] for future work' in the paper. Here, it follows directly from the syntax combination of Rust's `use` and `impl Trait for Type` items. | +| Only discrete model imports | Includes generic imports and re-exports | This is pointed out as '[left] for future work' in the paper. Here, it follows directly from the syntax combination of Rust's `use` and `impl Trait for Type` items. | | - | (Rust) Global implementations | The automatic availability of global implementations between separately imported traits and types offers more convenience especially when working with common traits, like those backing operators in Rust. | | Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | | - | (Rust) Trait objects | 𝒢 does not appear to support runtime polymorphism beyond function pointers. Scoped `impl Trait for Type` is seamlessly compatible with `dyn Trait` coercions (iff `Trait` is object-safe). | @@ -1438,10 +2065,6 @@ and key differences: It very much does matter if one of the opt-in mitigations for [first-party-implementation-assumptions-in-macros] is implemented. - The [trait-binding-site] section above has a range of examples, but these likely don't cover everything. - -- I used some example crates above that don't exist. If that makes it into the documentation, I think those should be created with the described features. - - Should outer generic type parameters be visible on/in scoped `impl Trait for Type`, including `use`-declarations? That would enable the following pattern: @@ -1488,12 +2111,6 @@ and key differences: See [explicit-binding] in *Future possibilities* below for one possibility. -- How to bring a global implementation into a scope as if scoped? - - This may be necessary to fix some [incompatible-or-missing-supertrait-implementation] errors without larger code reorganisation, and would also give macro authors a better way to force use of the global implementation. - - The syntax should translate well to [explicit-binding] and also support re-exporting the implementation. - # Future possibilities [future-possibilities]: #future-possibilities @@ -1527,6 +2144,9 @@ If the trait implementation is later made available plainly (that is: without `u (However, I assume binding to implementations not-from dependencies or the same crate in this way has a lot of implications for code generation.) +There is previous discussion regarding a similar suggestion in a slightly different context: [[Pre-RFC] Forward impls](https://internals.rust-lang.org/t/pre-rfc-forward-impls/4628) +Perhaps the downsides here could be mitigated by allowing `#[unstable_use_as_global]` very strictly only in application crates compiled with the `cargo --locked` flag. + ## Scoped `impl Trait for Type` of auto traits, `Drop` and/or `Copy` with orphan rules The crate in which a type is defined could in theory safely provide scoped implementations for it also for these traits. @@ -1551,7 +2171,7 @@ This could be considered as more-robust alternative to non-object-safe extension A good example of this use case could be the [tap] crate, which provides generic extension methods applicable to *all* types, but where its use is *theoretically* vulnerable to instability regarding the addition of type-associated methods of the same name(s). -If instead of (or in addition to!) ...: +If instead of (or in addition to!) …: ```rust // pipe.rs @@ -1572,7 +2192,7 @@ pub trait Pipe { impl Pipe for T where T: ?Sized {} ``` -...the extension could be defined as ...: +…the extension could be defined as …: ```rust pub use impl T where T: ?Sized { @@ -1589,7 +2209,7 @@ pub use impl T where T: ?Sized { } ``` -...then: +…then: - The consumer crate could choose which types to import the extension for, weighing @@ -1818,9 +2438,14 @@ while let Some(min) in min_heap.pop() { dbg!(::cmp(&1, &2)); // […] = Less // Explicit binding. Requirements are like for a discrete import. -// (How to specify a scoped implementation with supertraits?) dbg!(::cmp(&1, &2)); // […] = Greater +// The previous example is syntactic sugar for general top-level binding: +dbg!(<(u32: PartialOrd in reverse) as PartialOrd>::cmp(&1, &2)); // […] = Greater + +// The forms can be mixed to bind supertraits: +dbg!(<(u32: PartialOrd in _) as Ord in reverse>::cmp(&1, &2)); // […] = Greater + { let mut a = max_heap; let mut b = min_heap; @@ -1830,13 +2455,36 @@ dbg!(::cmp(&1, &2)); // […] = Greater } ``` +```rust +mod custom_defaults { + use impl Default for &'static str { + // ... + } +} + +#[derive(Default)] +pub struct Struct<'a> { + pub a: (&'a str: Default in custom_defaults), + + // The custom `Default` is not captured here, + // since it's not actually in scope. + pub b: Vec<&'a str>, +} +``` + This is of course syntax bikeshedding. +Specifying implementations on fields manually is a way to provide them only to `derive` and other attribute macros, as these top-level implementations do *not* bind to the type and as such are not used by code that doesn't restate the type explicitly. (The built-in macros should be defined as doing so from the get-go. Unfortunately, for other macros this is likely an optional implementation detail.) + +Since the specified top-level implementation doesn't bind persistently inside `Struct`, the exported signature is just `struct Struct<'a> {pub a: &'a str, pub b: Vec<&'a str>}`. + Binding only `PartialEq` would still shadow the discrete global `Ord` implementation, so binding both is required. As the scoped implementation of `Ord` in `reverse` is on a discrete type, it requires the specific supertrait implementation that is in scope for its definition. This should make it possible to infer the module here. (See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] below.) -For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit bindings should be allowed where no matching bound is present, but should produce an "unused" warning iff neither published nor used in the same crate (including for type identity distinction). +Top-level bindings require parentheses. To explicitly bind a global implementation, `::` can be used in place of the module path. + +For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit bindings should be allowed where no matching bound is present, but should produce an 'unused' warning iff neither published nor used in the same crate (including for type identity distinction). ## Implicit import of supertrait implementations of scoped implementations defined on discrete types [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types]: #implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types @@ -1871,3 +2519,81 @@ dbg!(::cmp(&1, &2)); // […] = Greater The downside is that `use`-declarations would become less obvious. Implied supertrait implementation imports could be enabled only for [explicit-binding] to avoid this. If this is added later than scoped `impl Trait for Type`, then private scoped implementations **must not** be implicitly exported through this mechanism. (It's likely a good idea to not allow that anyway, as it would be surprising.) Making previously crate-private implementations available that way could lead to unsoundness. + +## Conversions where a generic only cares about specific bounds' consistency + +With specialisation and more expressive bounds, an identity conversion like the following could be implemented: + +```rust +// In the standard library. + +use std::mem; + +impl From> for HashSet +where + T: ?Hash + ?Eq, // Observe implementations without requiring them. + U: ?Hash + ?Eq, + T == U, // Comparison in terms of innate type identity and observed implementations. +{ + fn from(value: HashSet) -> Self { + unsafe { + // SAFETY: This type requires only the `Hash` and `Eq` implementations to + // be consistent for correct function. All other implementations on + // generic type parameters may be exchanged freely. + // For the nested types this is an identity-transform, as guaranteed + // by `T == U` and the shared `S` which means the container is also + // guaranteed to be layout compatible. + mem::transmute(value) + } + } +} +``` + +## Scoped bounds as contextual alternative to sealed traits + +This is probably pretty strange, and may not be useful at all, but it likely doesn't hurt to mention this. + +Consider [explicit-binding] in bounds like here: + +```rust +use another_crate::{Trait, Type1, Type2}; + +pub fn function() {} + +pub use impl Trait for Type1 {} +pub use impl Trait for Type2 {} +``` + +With this construct, `function` could privately rely on implementation details of `Trait` on `Type1` and `Type2` without defining a new sealed wrapper trait. + +If the caller is allowed to use this function without restating the binding, then removing the scope would be a breaking change (as it is already with bindings captured on type parameters in public signatures, so that would be consistent for this syntactical shape). + +## Glue crate suggestions +[glue-crate-suggestions]: #glue-crate-suggestions + +If crates move some of their overlay features into glue crates, as explained in [unblock-ecosystem-evolution], it would be nice if they could suggest them if both they and e.g. Serde were `cargo add`ed to the project dependencies. + +An example of what this could look like: + +```toml +[package] +name = "my-crate" +version = "0.1.2" +edition = "2021" + +[dependencies] +# none + +[suggest-with.serde."1"] +my-crate_serde_glue = "0.1.0" + +[suggest-with.bevy_reflect."0.11"] +my-crate_bevy_reflect_glue = "0.1.2" + +[suggest-with.bevy_reflect."0.12"] +my-crate_bevy_reflect_glue = "0.2.1" +``` + +(This sketch doesn't take additional registries into account.) + +Ideally, crates.io should only accept existing crates here (but with non-existing version numbers) and Cargo should by default validate compatibility where possible during `cargo publish`. From 1c4a5bf17caa90d5f0960714cfaa301f6932892a Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 3 Dec 2023 03:12:17 +0100 Subject: [PATCH 04/27] Small wording fix --- text/0000-scoped-impl-trait-for-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 3df7e0f71dc..28c375aeb74 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -1520,7 +1520,7 @@ struct NewSerdeCompatibleDef(SerdeCompatible); struct NewNeitherCompatibleDef(#[serde(with = "NeitherCompatibleDef")] NeitherCompatible); impl DataBundle { - // These conversions are methods for discoverability. + // These conversions are associated functions for discoverability. pub fn from_parts(parts: DataBundleParts) -> Self { parts.into() } From 7aa89668e827136c14c019d998a2a72efae56b4b Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Tue, 5 Dec 2023 01:05:24 +0100 Subject: [PATCH 05/27] Pre-RFC v3 - Added a list of bullet points to the Summary and revised it slightly - Coined the term *implementation environment* to refer to the set of all implementations applicable (to a given type) in a given place - Near-completely rewrote the Logical consistency subsection to add subheadings and examples - Small fixes and adjustments --- text/0000-scoped-impl-trait-for-type.md | 364 ++++++++++++++++++++++-- 1 file changed, 335 insertions(+), 29 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 28c375aeb74..960682e2214 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -6,16 +6,25 @@ # Summary [summary]: #summary -This RFC proposes adding scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also proposes extending the syntax of `use`-declarations to allow importing these scoped implementations into other scopes (including other crates), and to differentiate type identity of generics by which scoped trait implementations are available to each discretised generic type parameter. +This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other scopes (including other crates), and to differentiate type identity of generics by which scoped trait implementations are available to each discretised generic type parameter. -(This document uses "scoped implementation" and "scoped `impl Trait for Type`" interchangeably. As such, the former should always be interpreted to mean the latter below.) +This (along with some details specified below) enables any crate to + +- locally, in item scopes, implement nearly any trait for any expressible type, +- publish these trivially composable implementations to other crates, +- import and use such implementations safely and seamlessly and +- completely ignore this feature when it's not needed\*. + +\* aside from one hopefully very obscure `TypeId` edge case that's easy to accurately lint for. + +This document uses "scoped implementation" and "scoped `impl Trait for Type`" interchangeably. As such, the former should always be interpreted to mean the latter below. # Motivation [motivation]: #motivation While orphan rules regarding trait implementations are necessary to allow crates to add features freely without fear of breaking dependent crates, they limit the composability of third party types and traits, especially in the context of derive macros. -For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`¹, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between crates. +For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between crates. Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. @@ -236,7 +245,7 @@ The core Rust language grammar is extended as follows: (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) - **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [generic-type-parameters-capture-scoped-implementations]). + **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [type-parameters-capture-their-implementation-environment]). [*TraitImpl*]: https://doc.rust-lang.org/reference/items/implementations.html?highlight=TraitImpl#implementations @@ -323,16 +332,16 @@ To ensure this stays sound, scoped `impl Trait for Type` where `Trait` is extern See also [scoped-implementation-of-external-sealed-trait]. -## Generic type parameters capture scoped implementations -[generic-type-parameters-capture-scoped-implementations]: #generic-type-parameters-capture-scoped-implementations +## Type parameters capture their *implementation environment* +[type-parameters-capture-their-implementation-environment]: #type-parameters-capture-their-implementation-environment -When a type parameter is specified, either explicitly or inferred from an expression, it captures *all* scoped implementations from its scope that are applicable to its type. +When a type parameter is specified, either explicitly or inferred from an expression, it captures a view of *all* implementations that are applicable to its type there. This is called the type parameter's *implementation environment*. When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type parameters are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type parameter is specified. -When a generic type parameter is used to discretise another generic, the view captured in the latter is based on the view captured in the former and modified by scoped implementations and shadowing applicable to that generic type parameter's opaque type. +When a generic type parameter is used to discretise another generic, the captured environment is the one captured in the former but overlaid with modifications applicable to that generic type parameter's opaque type. -Note that type parameter defaults too capture implementations where they are specified, so at the initial definition site of the generic. This capture is used whenever the type parameter default is used. +Note that type parameter defaults too capture their *implementation environment* where they are specified, so at the initial definition site of the generic. This environment is used whenever the type parameter default is used. ## Type identity of discrete types [type-identity-of-discrete-types]: #type-identity-of-discrete-types @@ -342,7 +351,7 @@ The type identity and `TypeId::of::<…>()` of discrete types, including discret ## Type identity of generic types [type-identity-of-generic-types]: #type-identity-of-generic-types -The type identity of generic types is derived from the types specified for their type parameters as well as the *full* captured view of available implementations for each of their type parameters: +The type identity of generic types is derived from the types specified for their type parameters as well as the *full* *implementation environment* of each of their type parameters: ```rust #[derive(Default)] @@ -401,7 +410,7 @@ fn main() { assert_ne!(TypeId::of::(), TypeId::of::()); assert_ne!(TypeId::of::(), TypeId::of::()); - // Types with the same captured implementations are still the same type. + // Types with identical captured implementation environments are still the same type. assert_eq!(TypeId::of::(), TypeId::of::()); // Top-level implementations are not part of type identity. @@ -433,10 +442,12 @@ The `TypeId` of discretised generics varies alongside their identity. Note that ¹ With the current implementation, this would likely say `Generic<_>: From>>`, which isn't helpful. With [explicit-binding], it could say `Generic: From>>`. +(For a practical example, see [logical-consistency] [of-generic-collections].) + ## `TypeId` of generic type parameters' opaque types [typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types -In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to captured view of *only implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: +In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to the captured *implementation environment*, but *only according implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: ```rust use std::any::TypeId; @@ -555,14 +566,16 @@ impl<'a, S: BuildHasher + Clone> ErasedHashSet<'a, S> { } ``` -In particular, this code will ignore any scoped implementations on `T` that are not `Hash`, `Eq` or (implicitly) `PartialEq`, while any distinct set of discrete type, `Hash`, `Eq` and `PartialEq` is cleanly separated. +In particular, this code will ignore any scoped implementations on `T` that are not `Hash`, `Eq` or (implicitly) `PartialEq`, while any combination of distinct discrete type and *implementation environments* with distinct `Hash`, `Eq` or `PartialEq` implementations is cleanly separated. See also [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for how to lint for an implementation of this collection that uses `TypeId::of::()` as key, which *also* remains sound and deterministic but distinguishes too aggressively by irrelevant scoped implementations in consumer code, leading to unexpected behaviour. +(For an example of `TypeId` behaviour, see [logical-consistency] [of-type-erased-collections].) + ## Layout-compatibility [layout-compatibility]: #layout-compatibility -Types whose identities are only distinct because of a difference in captured scoped implementations on their generic type parameters remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. +Types whose identities are only distinct because of a difference in *implementation environments* remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. It is sound to transmute an instance between these types **if** no inconsistency is observed on that instance by the bounds of any external-to-the-`transmute` implementation or combination of implementations, including scoped implementations and implementations on discrete variants of the generic. As a consequence, the `Self`-observed `TypeId` of instances of generic types **may** change in some cases. @@ -580,7 +593,7 @@ impl Type { then in another crate - if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again then and -- if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured implementations, because `method` has observed the *exact* type parameter through its constraints. +- if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured *implementation environment*, because `method` has observed the *exact* type parameter through its constraints. (In short: Don't use external-to-your-code implementations with the instance in any combination that couldn't have been done without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) @@ -828,7 +841,7 @@ use library::{Alias, Generic, Type}; assert_ne!(TypeId::of::(), TypeId::of::>()); ``` -Here, the scoped implementation `use impl Trait for Type {}` **is** used as it is captured into the type identity of `Alias`. +Here, the scoped implementation `use impl Trait for Type {}` **is** accounted for as it is captured into the type identity of `Alias`. Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. If it can ensure that that is the case, a (different!) warning could in theory still be shown here. @@ -959,7 +972,7 @@ use a_crate::{ (where here the implementation import is subsetting a blanket import, but that technicality isn't relevant. What matters is that the implementation is from another crate). -If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in a public signature or item?). +If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in that public signature or item?). ## Errors @@ -1444,11 +1457,11 @@ This isn't unusual for anything involving *GenericParams*, but use of this featu Consider crates like `inventory` or Bevy's systems and queries. -There may be tricky to debug issues for their consumers if a `TypeId` doesn't match between uses of generics with superficially the same type parameters, especially without prior knowledge of distinction by captured available scoped implementations. +There may be tricky to debug issues for their consumers if a `TypeId` doesn't match between uses of generics with superficially the same type parameters, especially without prior knowledge of distinction by captured *implementation environments*. -A partial mitigation would be to have rustc include such captures on generic type parameters when printing types, but that wouldn't solve the issue entirely. +A partial mitigation would be to have rustc include captured scoped implementations on generic type parameters when printing types, but that wouldn't solve the issue entirely. -Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct but bound-irrelevant captured implementations directly, as long as only these top-level implementations differ and no further nested ones. +Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct but bound-irrelevant captured implementations directly, as long as only these top-level implementations differ and no nested captured *implementation environments* do. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -1846,14 +1859,307 @@ With this RFC, scopes are a 'mini version' of the environment that global implem (This is part of the reason why scoped `impl Trait for Type`s are anonymous; names would make these imports more verbose rather than shorter, since the subsetting still needs to happen in every case.) ### Logical consistency +[logical-consistency]: #logical-consistency Binding external top-level implementations to types is equivalent to using their public API in different ways, so no instance-associated consistency is expected here. Rather, values that are used in the same scope behave consistently with regard to that scope's visible implementations. +#### of generic collections +[of-generic-collections]: #of-generic-collections + Generics are trickier, as their instances often do expect trait implementations on generic type parameters that are consistent between uses but not necessarily declared as bounded on the struct definition itself. -This problem is solved by making the `impl`s available to each type parameter part of the the type identity of the discretised host generic, including a difference in `TypeId` as with existing monomorphisation. That said, implementations, especially generic functions where the distinction is not statically caught by a host instance's consistency, are not expected to care about implementations unrelated to their bounds. It is for this reason that the `TypeId` of generic type parameters disregards irrelevant implementations somewhat. +This problem is solved by making the `impl`s available to each type parameter part of the the type identity of the discretised host generic, including a difference in `TypeId` there as with existing monomorphisation. + +(See [type-parameters-capture-their-implementation-environment] and [type-identity-of-generic-types] in the [reference-level-explanation] above for more detailed information.) + +Here is an example of how captured *implementation environments* safely flow across module boundaries, often seamlessly due to type inference: + +```rust +pub mod a { + // ⓐ == ◯ + + use std::collections::HashSet; + + #[derive(PartialEq, Eq)] + pub struct A; + + pub type HashSetA = HashSet
; + pub fn aliased(_: HashSetA) {} + pub fn discrete(_: HashSet) {} + pub fn generic(_: HashSet) {} +} + +pub mod b { + // ⓑ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + }; + + #[derive(PartialEq, Eq)] + pub struct B; + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + + pub type HashSetB = HashSet; // ⚠ + pub fn aliased(_: HashSetB) {} + pub fn discrete(_: HashSet) {} // ⚠ + pub fn generic(_: HashSet) {} +} + +pub mod c { + // ⓒ == ◯ + + use std::collections::HashSet; + + #[derive(PartialEq, Eq, Hash)] + pub struct C; + + pub type HashSetC = HashSet; + pub fn aliased(_: HashSetC) {} + pub fn discrete(_: HashSet) {} + pub fn generic(_: HashSet) {} +} + +pub mod d { + // ⓓ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + iter::once, + }; + + use super::{ + a::{self, A}, + b::{self, B}, + c::{self, C}, + }; + + use impl Hash for A { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for C { + fn hash(&self, _state: &mut H) {} + } + + fn call_functions() { + a::aliased(HashSet::new()); // ⓐ == ◯ + a::discrete(HashSet::new()); // ⓐ == ◯ + a::generic(HashSet::from_iter(once(A))); // ⊙ == ⓓ + + b::aliased(HashSet::from_iter(once(B))); // ⓑ + b::discrete(HashSet::from_iter(once(B))); // ⓑ + b::generic(HashSet::from_iter(once(B))); // ⊙ == ⓓ + + c::aliased(HashSet::from_iter(once(C))); // ⓒ == ◯ + c::discrete(HashSet::from_iter(once(C))); // ⓒ == ◯ + c::generic(HashSet::from_iter(once(C))); // ⊙ == ⓓ + } +} + +``` + +Note that the lines annotated with `// ⚠` produce a warning due to the lower visibility of the scoped implementation in `b`. + +Circles denote *implementation environment*s: + +| | | +|-|-| +| `◯` | indistinct from global | +| `ⓐ`, `ⓑ`, `ⓒ`, `ⓓ` | respectively as in module `a`, `b`, `c`, `d` | +| `⊙` | caller-side | + +The calls infer discrete `HashSet`s with different `Hash` implementations as follows: + +| call in `call_functions` | `impl Hash` in | captured in/at | notes | +|-|-|-|-| +| `a::aliased` | - | `type` alias | The implementation cannot be 'inserted' into an already-specified type parameter, even if it is missing. | +| `a::discrete` | - | `fn` signature | See `a::aliased`. | +| `a::generic` | `d` | `once` call | | +| `b::aliased` | `b` | `type` alias | | +| `b::discrete` | `b` | `fn` signature | | +| `b::generic` | `d` | `once` call | `b`'s narrow implementation cannot bind to the opaque `T`. | +| `c::aliased` | `::` | `type` alias | Since the global implementation is visible in `c`. | +| `c::discrete` | `::` | `fn` signature | See `c::aliased`. +| `c::generic` | `d` | `once` call | The narrow global implementation cannot bind to the opaque `T`. | + +#### of type-erased collections +[of-type-erased-collections]: #of-type-erased-collections + +Type-erased collections such as the `ErasedHashSet` shown in [typeid-of-generic-type-parameters-opaque-types] require slightly looser behaviour, as they are expected to mix instances between environments where only irrelevant implementations differ (since they don't prevent this mixing statically like `std::collections::HashSet`, as their generic type parameters are transient on their methods). + +It is for this reason that the `TypeId` of generic type parameters disregards bounds-irrelevant implementations. + +The example is similar to the previous one, but `aliased` has been removed since it continues to behave the same as `discrete`. A new set of functions `bounded` is added: + +```rust +#![allow(unused_must_use)] // For the `TypeId::…` lines. + +trait Trait {} + +pub mod a { + // ⓐ == ◯ -(See [generic-type-parameters-capture-scoped-implementations], [type-identity-of-generic-types] and [typeid-of-generic-type-parameters-opaque-types] in the [reference-level-explanation] above for more detailed information.) + use std::{collections::HashSet, hash::Hash}; + + #[derive(PartialEq, Eq)] + pub struct A; + + pub fn discrete(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn generic(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn bounded(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } +} + +pub mod b { + // ⓑ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + }; + + use super::Trait; + + #[derive(PartialEq, Eq)] + pub struct B; + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + use impl Trait for B {} + + pub fn discrete(_: HashSet) { // ⚠⚠ + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn generic(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn bounded(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } +} + +pub mod c { + // ⓒ == ◯ + + use std::{collections::HashSet, hash::Hash}; + + use super::Trait; + + #[derive(PartialEq, Eq, Hash)] + pub struct C; + impl Trait for C {} + + pub fn discrete(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn generic(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn bounded(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } +} + +pub mod d { + // ⓓ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + iter::once, + }; + + use super::{ + a::{self, A}, + b::{self, B}, + c::{self, C}, + Trait, + }; + + use impl Hash for A { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for C { + fn hash(&self, _state: &mut H) {} + } + + use impl Trait for A {} + use impl Trait for B {} + use impl Trait for C {} + + fn call_functions() { + a::discrete(HashSet::new()); // ⓐ == ◯ + a::generic(HashSet::from_iter(once(A))); // ⊙ == ⓓ + a::bounded(HashSet::from_iter(once(A))); // ⊙ == ⓓ + + b::discrete(HashSet::from_iter(once(B))); // ⓑ + b::generic(HashSet::from_iter(once(B))); // ⊙ == ⓓ + b::bounded(HashSet::from_iter(once(B))); // ⊙ == ⓓ + + c::discrete(HashSet::from_iter(once(C))); // ⓒ == ◯ + c::generic(HashSet::from_iter(once(C))); // ⊙ == ⓓ + c::bounded(HashSet::from_iter(once(C))); // ⊙ == ⓓ + } +} + +``` + +`// ⚠` and non-digit circles have the same meanings as above. + +The following table describes how the types are observed at runtime in the lines marked with ❶ and ❷. It borrows some syntax from [explicit-binding] to express this clearly, but **denotes types as if seen from the global *implementation environment***. + +| within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | +|-|-|-| +| `a::discrete` | `HashSet
` | `A` | +| `a::generic` | `HashSet` | `A` | +| `a::bounded` | `HashSet` | `A` ∘ `Hash in d` | +| `b::discrete` | `HashSet` | `B` | +| `b::generic` | `HashSet` | `B` | +| `b::bounded` | `HashSet` | `B` ∘ `Hash in d` | +| `c::discrete` | `HashSet` | `C` | +| `c::generic` | `HashSet` | `C` | +| `c::bounded` | `HashSet` | `C` ∘ `Hash in d` | + +The combination ∘ is not directly expressible in `TypeId::of::<>` calls (as even a direct top-level annotation would be ignored without bounds). Rather, it represents an observation like this: + +```rust +{ + use std::{any::TypeId, hash::Hash}; + + use a::A; + use d::{impl Hash for A}; + + fn observe() { + TypeId::of::; // '`A` ∘ `Hash in d`' + } + + observe::(); +} +``` ### Logical stability @@ -1862,13 +2168,13 @@ This problem is solved by making the `impl`s available to each type parameter pa This is another consequence of subsetting rather than named-model imports, as narrowing a scoped implementation can only make the `use`-declaration fail to compile, rather than changing which implementations are shadowed. -Similarly, types of generics with different captured implementations are strictly distinct from each other, so that assigning them inconsistently does not compile. This is weighed somewhat against ease of refactoring, so in cases where a type parameter is inferred and the host is used in isolation, which are assumed to not care about implementation details, the code will continue to align with the definition instead of breaking. +Similarly, types of generics with different captured *implementation environments* are strictly distinct from each other, so that assigning them inconsistently does not compile. This is weighed somewhat against ease of refactoring, so in cases where a type parameter is inferred and the host is used in isolation, which are assumed to not care about implementation details like that, the code will continue to align with the definition instead of breaking. ## Encourage readable code This RFC aims to further decreases the mental workload required for code review, by standardising glue code APIs to some degree and by clarifying their use in other modules. -It also aims to create an import grammar that can be understood more intuitively than external newtypes when first encountering it, which should improve the accessibility of Rust code somewhat. +It also aims to create an import grammar that can be understood more intuitively than external newtypes when first encountered, which should improve the accessibility of Rust code somewhat. ### Clear imports @@ -1916,7 +2222,7 @@ When looking for the scoped implementation affecting a certain type, strict shad As such, readers can stop scanning once they encounter a match, instead of checking the entire file's length for another implementation that may be present in the outermost scope. -Outside of implementations captured *inside* generics, scoped implementations cannot influence the behaviour of another file without being mentioned explicitly. +Aside from *implementation environments* captured *inside* generics, scoped implementations cannot influence the behaviour of another file without being mentioned explicitly. ## Unblock ecosystem evolution [unblock-ecosystem-evolution]: #unblock-ecosystem-evolution @@ -1949,7 +2255,7 @@ As scoped implementations clearly declare the link between the trait and type(s) ### Discovery of the feature itself -In some cases (where a trait implementations cannot be found at all), tools can suggest creating a scoped implementation, unless adding it in that place would capture it in a type parameter of an item visible outside the current crate. +In some cases (where a trait implementations cannot be found at all), tools can suggest creating a scoped implementation, unless adding it in that place would capture it as part of the *implementation environment* of a type parameter specified in an item definition visible outside the current crate. That said, it would be great if rust-analyzer could detect and suggest/enable feature-gated global implementations to some extent, with higher priority than creating a new scoped implementation. @@ -1973,7 +2279,7 @@ In the long run, this can lead to less near-duplicated functionality in the depe Scoped implementations can be documented and appear as separate item category in rustdoc-generated pages. -Rustdoc should be able to detect and annotate captured implementations in public signatures automatically. This, in addition to warnings, should be another tool to help avoid accidental exposure of scoped implementations. +Rustdoc should be able to detect and annotate captured scoped implementations in public signatures automatically. This, in addition to warnings, should be another tool to help avoid accidental exposure of scoped implementations. Implementation origin and documentation could be surfaced by rust-analyzer in relevant places. @@ -2027,7 +2333,7 @@ Some features are largely equivalent: |---|---|---| | Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | | Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | -| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available scoped implementations on discretised type parameters are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive conversions and comparisons can bind between similar type parameters.

| +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available scoped implementations on discretised type parameters are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of a type parameter's captured *implementation environment*.

| ## A Language for Generic Programming in the Large @@ -2474,7 +2780,7 @@ pub struct Struct<'a> { This is of course syntax bikeshedding. -Specifying implementations on fields manually is a way to provide them only to `derive` and other attribute macros, as these top-level implementations do *not* bind to the type and as such are not used by code that doesn't restate the type explicitly. (The built-in macros should be defined as doing so from the get-go. Unfortunately, for other macros this is likely an optional implementation detail.) +Specifying implementations on fields manually is a way to provide them only to `derive` and other attribute macros, as these top-level implementations do *not* bind to the type and as such are *not* used by code that doesn't restate the type explicitly. (The built-in macros should be defined as doing so from the get-go. Unfortunately, for other macros this is likely an optional implementation detail.) Since the specified top-level implementation doesn't bind persistently inside `Struct`, the exported signature is just `struct Struct<'a> {pub a: &'a str, pub b: Vec<&'a str>}`. From d21a6bb2c340c4581dc98bb9863b984530b6b8e3 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Tue, 5 Dec 2023 01:13:13 +0100 Subject: [PATCH 06/27] Removed code formatting from circle symbols in table as it looks strange on GitHub --- text/0000-scoped-impl-trait-for-type.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 960682e2214..52613c4de80 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -1971,9 +1971,9 @@ Circles denote *implementation environment*s: | | | |-|-| -| `◯` | indistinct from global | -| `ⓐ`, `ⓑ`, `ⓒ`, `ⓓ` | respectively as in module `a`, `b`, `c`, `d` | -| `⊙` | caller-side | +| ◯ | indistinct from global | +| ⓐ, ⓑ, ⓒ, ⓓ | respectively as in module `a`, `b`, `c`, `d` | +| ⊙ | caller-side | The calls infer discrete `HashSet`s with different `Hash` implementations as follows: From b0cd5c2cfb07782601d6487e9e62a32af3184601 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Tue, 5 Dec 2023 04:53:11 +0100 Subject: [PATCH 07/27] Added missing parens + Small formatting fix --- text/0000-scoped-impl-trait-for-type.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 52613c4de80..859dc815355 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -1967,7 +1967,7 @@ pub mod d { Note that the lines annotated with `// ⚠` produce a warning due to the lower visibility of the scoped implementation in `b`. -Circles denote *implementation environment*s: +Circles denote *implementation environments*: | | | |-|-| @@ -2154,7 +2154,7 @@ The combination ∘ is not directly expressible in `TypeId::of::<>` calls (as ev use d::{impl Hash for A}; fn observe() { - TypeId::of::; // '`A` ∘ `Hash in d`' + TypeId::of::(); // '`A` ∘ `Hash in d`' } observe::
(); From 047fc15976c04906c96da2035b2a0aef99df89aa Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Fri, 8 Dec 2023 17:04:42 +0100 Subject: [PATCH 08/27] Pre-RFC v4 - Distinguish between implementation-aware generics and implementation-invariant generics - Minor edits and fixes --- text/0000-scoped-impl-trait-for-type.md | 193 +++++++++++++++++++++--- 1 file changed, 175 insertions(+), 18 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 859dc815355..df1433fa29d 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other scopes (including other crates), and to differentiate type identity of generics by which scoped trait implementations are available to each discretised generic type parameter. +This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other scopes (including other crates), and differentiates type identity of most generics by which scoped trait implementations are available to each specified generic type parameter. This (along with some details specified below) enables any crate to @@ -112,7 +112,7 @@ use ::{impl Trait for Type}; ### Scoped implementations and generics [scoped-implementations-and-generics]: #scoped-implementations-and-generics -Scoped implementations are resolved on generic type parameters where those are specified, and become part of the (now less generic) host type's identity: +Scoped implementations are resolved on most generics' type parameters where those are specified, and become part of the (now less generic) host type's identity: ```rust struct Type(T); @@ -149,6 +149,8 @@ Alias::type_fn(); // "nested" This works equally not just for type aliases but also fields, `let`-bindings and also where generic type parameters are inferred automatically from expressions (for example to call a constructor). +Note that some utility types, like references, tuples, `Option`, `Result` and closure traits, do not bind implementations eagerly but only when used to specify another generic. You can find a list of these types in the reference. (← i.e. "insert link here".) + ## **19.2.** Advanced Traits The section [Using the Newtype Pattern to Implement External Traits on External Types] is updated to mention scoped implementations, to make them more discoverable when someone arrives from an existing community platform answer regarding orphan rule workarounds. It should also mention that newtypes are preferred over scoped implementations when use of the type is semantically different, to let the type checker distinguish it from others. @@ -240,7 +242,7 @@ The core Rust language grammar is extended as follows: > > **TraitCoverageNoWhereClause**: >   `impl` *GenericParams*? `!`? *TypePath* `for` *Type* - + where a trait implementation with that `use`-prefix provides the implementation *only* as item in the containing item scope. (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) @@ -337,6 +339,8 @@ See also [scoped-implementation-of-external-sealed-trait]. When a type parameter is specified, either explicitly or inferred from an expression, it captures a view of *all* implementations that are applicable to its type there. This is called the type parameter's *implementation environment*. +(For trait objects, associated types are treated as type parameters for the purposes of this proposal.) + When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type parameters are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type parameter is specified. When a generic type parameter is used to discretise another generic, the captured environment is the one captured in the former but overlaid with modifications applicable to that generic type parameter's opaque type. @@ -351,7 +355,12 @@ The type identity and `TypeId::of::<…>()` of discrete types, including discret ## Type identity of generic types [type-identity-of-generic-types]: #type-identity-of-generic-types -The type identity of generic types is derived from the types specified for their type parameters as well as the *full* *implementation environment* of each of their type parameters: +### Implementation-aware generics +[implementation-aware-generics]: #implementation-aware-generics + +Generics that are not [implementation-invariant-generics] are implementation-aware generics. + +The type identity of implementation-aware generic types is derived from the types specified for their type parameters as well as the *full* *implementation environment* of each of their type parameters and their associated types: ```rust #[derive(Default)] @@ -438,16 +447,39 @@ fn main() { As mentioned in [type-identity-of-discrete-types], implementations on the generic type *itself* do *not* affect its type identity, as can be seen with `Alias4` above. -The `TypeId` of discretised generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] instead. +The `TypeId` of these generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] or `(…,)`-tuple-types combining them instead. ¹ With the current implementation, this would likely say `Generic<_>: From>>`, which isn't helpful. With [explicit-binding], it could say `Generic: From>>`. (For a practical example, see [logical-consistency] [of-generic-collections].) +### Implementation-invariant generics +[implementation-invariant-generics]: #implementation-invariant-generics + +The following generics that never rely in the consistency of implementation of their type parameters are implementation-invariant: + +- `&T`, `&mut T` (references), +- `*const T`, `*mut T` (pointers), +- `[T; N]`, `[T]` (arrays and slices), +- `(T,)`, `(T, U, ..)` (tuples), +- *superficially*\* `fn(T) -> U` and similar (function pointers), +- *superficially*\* `Fn(T) -> U`, `FnMut(T) -> U`, `FnOnce(T) -> U`, `Future`, `Iterator`, `std::ops::Coroutine` and similar (closures), +- `Pin

`, `NonNull`, `Box`, `Rc`, `Arc`, `Weak`, `Option`, `Result`\*\*. + +Implementation-invariant generics never capture implementation environments on their own. Instead, their effective implementation environments follow that of their host, acting as if they were captured in the same scope. + +The type identity of implementation-invariant generics seen on their own does not depend on the implementation environment. + +\* superficially: The underlying instance may well use a captured implementation, but this isn't surfaced in signatures. For example, a closure defined where `usize: PartialOrd in reverse + Ord in reverse` is just `FnOnce(usize)` but will use `usize: PartialOrd in reverse + Ord in reverse` privately when called. + +\*\* but see [which-structs-should-be-implementation-invariant]. + +See also [why-specific-implementation-invariant-generics]. + ## `TypeId` of generic type parameters' opaque types [typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types -In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to the captured *implementation environment*, but *only according implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: +In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to the captured *implementation environment*, but *only according to implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: ```rust use std::any::TypeId; @@ -568,7 +600,7 @@ impl<'a, S: BuildHasher + Clone> ErasedHashSet<'a, S> { In particular, this code will ignore any scoped implementations on `T` that are not `Hash`, `Eq` or (implicitly) `PartialEq`, while any combination of distinct discrete type and *implementation environments* with distinct `Hash`, `Eq` or `PartialEq` implementations is cleanly separated. -See also [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for how to lint for an implementation of this collection that uses `TypeId::of::()` as key, which *also* remains sound and deterministic but distinguishes too aggressively by irrelevant scoped implementations in consumer code, leading to unexpected behaviour. +See also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for how to lint for an implementation of this collection that uses `TypeId::of::>()` as key, which *also* remains sound and deterministic but distinguishes too aggressively by irrelevant scoped implementations in consumer code, leading to unexpected behaviour. (For an example of `TypeId` behaviour, see [logical-consistency] [of-type-erased-collections].) @@ -595,9 +627,9 @@ then in another crate - if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again then and - if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured *implementation environment*, because `method` has observed the *exact* type parameter through its constraints. -(In short: Don't use external-to-your-code implementations with the instance in any combination that couldn't have been done without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) +(In short: Don't use external-to-your-code implementations with the instance in any combination that wouldn't have been possible without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) -See [typeid-of-generic-type-parameters-opaque-types] for details on what this partial transmutation permission is for, and [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for a future incompatibility lint that could be used to warn implementations where this is relevant. +See [typeid-of-generic-type-parameters-opaque-types] for details on what this partial transmutation permission is for, and [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a future incompatibility lint that could be used to warn implementations where this is relevant. ## No interception/no proxies @@ -1071,18 +1103,18 @@ Crate `b` cannot define scoped implementations of the external sealed trait `Sea See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. -## Behaviour change/Warning: `TypeId` of generic discretised using generic type parameters -[behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters +## Behaviour change/Warning: `TypeId` of implementation-aware generic discretised using generic type parameters +[behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters -As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for generics they discretise using type parameters may be called on the same value instance. +As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for [implementation-aware-generics] they discretise using type parameters may be called on the same value instance. -Notably, this affects `TypeId::of::()` in implementations with generic targets, but not in unspecific blanket implementations on the type parameter itself. +Notably, this affects `TypeId::of::()` in implementations with most generic targets, but not in unspecific blanket implementations on the type parameter itself. This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show the expected `TypeId` distinction. -Instead of `TypeId::of::<(U, V, W)>()`, `(TypeId::of::(), TypeId::of::(), TypeId::of::())` can usually be used. +Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can be used, as tuples are [implementation-invariant-generics]. ## Resolution on generic type parameters [resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters @@ -1397,9 +1429,9 @@ There are a few ways to mitigate this, but they all have significant drawbacks: ## Unexpected behaviour of `TypeId::of::()` in implementations on generics in the consumer-side presence of scoped implementations and `transmute` -As explained in [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s in e.g. type-erased value-keyed collection like the `ErasedHashSet` example in the latter section. +As explained in [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s as e.g. for the `HashSet`s inside the type-erased value-keyed collection like the `ErasedHashSet` example in the [typeid-of-generic-type-parameters-opaque-types] section. -This use case appears to be niche enough in Rust to not have an obvious example on crates.io, but see [behaviour-changewarning-typeid-of-generic-discretised-using-generic-type-parameters] for a lint that aims to mitigate issues in this regard and could be used to survey potential issues. +This use case appears to be niche enough in Rust to not have an obvious example on crates.io, but see [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a lint that aims to mitigate issues in this regard and could be used to survey potential issues. ## More `use`-declaration clutter, potential inconsistencies between files @@ -1670,6 +1702,7 @@ Unlike with external newtypes, there are no potential conflicts beyond overlappi - or at worst by moving a generic implementation into a submodule and importing it for discrete types. ### Error handling and conversions +[error-handling-and-conversions]: #error-handling-and-conversions When implementing services, it's a common pattern to combine a framework that dictates function signatures with one or more unrelated middlewares that have their own return and error types. The example below is a very abridged example of this. @@ -2161,6 +2194,40 @@ The combination ∘ is not directly expressible in `TypeId::of::<>` calls (as ev } ``` +##### with multiple erased type parameters + +By replacing the lines + +```rust +TypeId::of::>(); // ❶ +TypeId::of::(); // ❷ +``` + +with + +```rust +TypeId::of::>(); // ❶ +TypeId::of::<(T)>(); // ❷ +``` + +(and analogous inside the discrete functions), the `TypeId` table above changes as follows: + +| within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | +|-|-|-| +| `a::discrete` | `HashSet<(A,)>` | `(A,)` | +| `a::generic` | `HashSet<(A: Hash in d + Trait in d,)>` | `(A,)` | +| `a::bounded` | `HashSet<(A: Hash in d + Trait in d,)>` | `(A` ∘ `Hash in d,)` | +| `b::discrete` | `HashSet<(B: Hash in `***`b`***` + Trait in`***` b`***`,)>` | `(B,)` | +| `b::generic` | `HashSet<(B: Hash in d + Trait in d,)>` | `(B,)` | +| `b::bounded` | `HashSet<(B: Hash in d + Trait in d,)>` | `(B` ∘ `Hash in d,)` | +| `c::discrete` | `HashSet<(C,)>` | `(C,)` | +| `c::generic` | `HashSet<(C: Hash in d + Trait in d,)>` | `(C,)` | +| `c::bounded` | `HashSet<(C: Hash in d + Trait in d,)>` | `(C` ∘ `Hash in d,)` | + +As you can see, the type identity of the tuples appears distinct when contributing to an implementation-aware generic's type identity but (along with the `TypeId`) remains appropriately fuzzy when used alone. + +This scales up to any number of type parameters used in implementation-invariant generics, which means an efficient `ErasedHashMap` can be constructed by keying storage on the `TypeId::of::<(K, V)>()` where `K: Hash + Eq` and `V` are the generic type parameters of its functions. + ### Logical stability - Non-breaking changes to external crates cannot change the meaning of the program. @@ -2283,6 +2350,27 @@ Rustdoc should be able to detect and annotate captured scoped implementations in Implementation origin and documentation could be surfaced by rust-analyzer in relevant places. +## Why specific [implementation-invariant-generics]? +[why-specific-implementation-invariant-generics]: #why-specific-implementation-invariant-generics + +This is a *not entirely clean* ergonomics/stability trade-off, as well as a clean resolution path for [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]. + +> It is also the roughest part of this proposal, in my eyes. If you have a better way of dealing with the aware/invariant distinction, please do suggest it! + +The main issue is that generics in the Rust ecosystem do not declare which trait implementations on their type parameters need to be consistent during their instances' lifetime, if any, and that traits like `PartialOrd` that do provide logical consistency guarantees over time are not marked as such in a compiler-readable way. + +Ignoring this and not having distinction of [implementation-aware-generics]' discretised variants would badly break logical consistency of generic collections like `BTreeSet`, which relies on `Ord` to function. + +On the other hand, certain types (e.g. references and (smart) pointers) that often wrap values in transit between modules *really* don't care about implementation consistency on these types. If these were distinct depending on available implementations on their values, it would create *considerable* friction while defining public APIs in the same scope as `struct` or `enum` definitions that require scoped implementations for `derive`s. + +Drawing a line manually here is an attempt to un-break this *by default* for the most common cases while maintaining full compatibility with existing code and keeping awareness of scoped `impl Trait for Type` entirely optional for writing correct and user-friendly APIs. + +As a concrete example, this ensures that `Box>>` is automatically interchangeable even if spelled out in the presence of scoped [error-handling-and-conversions] affecting `Error`, but that `BinaryHeap>` and `BinaryHeap>` don't mix. + +Functions pointers and closure trait( object)s should probably be fairly easy to pass around, with their internally-used bindings being an implementation detail. Fortunately, the Rust ecosystem already uses more specific traits for most configuration for better logical safety, so it's likely not too messy to make these implementation-invariant. + +Traits and trait objects cannot be implementation invariant (including for their associated types!) because it's possible to define `OrderedExtend` and `OrderedIterator` traits with logical consistency requirement on `Ord` between them. + ## Alternatives ### Named implementations @@ -2309,6 +2397,12 @@ Scoped `impl Trait for Type` together with its warnings [scoped-implementation-i [RFC: Hidden trait implementations]: https://github.com/rust-lang/rfcs/pull/2529 +### Required-explicit binding of scoped implementations inside generics + +This could avoid the distinction between [implementation-aware-generics] and [implementation-invariant-generics] to some extent, at the cost of likely overall worse ergonomics when working with scoped implementations. + +It's also likely to make `derive`-compatibility of scoped implementations inconsistent, because some macros may require explicit binding on field types while others would not. + # Prior art [prior-art]: #prior-art @@ -2333,7 +2427,7 @@ Some features are largely equivalent: |---|---|---| | Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | | Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | -| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available scoped implementations on discretised type parameters are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of a type parameter's captured *implementation environment*.

| +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of a type parameter's captured *implementation environment*.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing implementations.

| ## A Language for Generic Programming in the Large @@ -2358,7 +2452,7 @@ and key differences: | - | (Rust) Global implementations | The automatic availability of global implementations between separately imported traits and types offers more convenience especially when working with common traits, like those backing operators in Rust. | | Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | | - | (Rust) Trait objects | 𝒢 does not appear to support runtime polymorphism beyond function pointers. Scoped `impl Trait for Type` is seamlessly compatible with `dyn Trait` coercions (iff `Trait` is object-safe). | -| (unclear?) | Available implementations on discretised type parameters become part of the type identity. |

This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour.

The tradeoff is that it may be difficult to explicitly annotate types in cases of mixed bindings with this RFC. As newtypes and named configuration token types are still preferred for changed behaviour, such cases will hopefully be limited. Otherwise, see [explicit-binding] for bikeshedded syntax.

| +| (unclear?) | Available implementations on discretised type parameters become part of the type identity of implementation-aware generics. |

This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour.

The tradeoff is that it may be difficult to explicitly annotate types in cases of mixed bindings with this RFC. As newtypes and named configuration token types are still preferred for changed behaviour, such cases will hopefully be limited. Otherwise, see [explicit-binding] for bikeshedded syntax.

| # Unresolved questions [unresolved-questions]: #unresolved-questions @@ -2417,6 +2511,40 @@ and key differences: See [explicit-binding] in *Future possibilities* below for one possibility. +## Which `struct`s should be implementation-invariant? +[which-structs-should-be-implementation-invariant]: #which-structs-should-be-implementation-invariant + +This is a tough question because, runtime behaviour difference of [of-type-erased-collections] aside, the following makes shifting a type from [implementation-aware-generics] to [implementation-invariant-generics] a compilation-breaking change: + +```rust +struct Type; +struct Generic(T); +trait Trait {} + +mod a { + use super::{Type, Generic, Trait}; + pub use impl Trait for Type {} + pub type Alias = Generic; +} + +mod b { + use super::{Type, Generic, Trait}; + pub use impl Trait for Type {} + pub type Alias = Generic; +} + +use impl Trait for a::Alias {} +use impl Trait for b::Alias {} +``` + +(It is *theoretically* possible to do such a later adjustment as part of an edition, even considering `TypeId` behaviour I think, but it's certainly not pretty.) + +Splitting this along the line of "structs that use `<>` around type parameters" would feel cleaner, but the basic smart pointers, `Pin

`, `Option` and `Result` appear in crate API signatures enough that not including them would create considerable friction. + +Other candidates for consideration: + +- Other `DispatchFromDyn` types in the standard library like `Cell`, `SyncUnsafeCell`, `UnsafeCell` + # Future possibilities [future-possibilities]: #future-possibilities @@ -2855,6 +2983,35 @@ where } ``` +This could also enable adjusted borrowing: + +```rust +// In the standard library. + +use std::mem; + +impl HashSet { + fn as_with_item_impl(&self) -> HashSet + where + T: ?Hash + ?Eq, // Observe implementations without requiring them. + U: ?Hash + ?Eq, + T == U, // Comparison in terms of innate type identity and observed implementations. + { + unsafe { + // SAFETY: This type requires only the `Hash` and `Eq` implementations to + // be consistent for correct function. All other implementations on + // generic type parameters may be exchanged freely. + // For the nested types this is an identity-transform, as guaranteed + // by `T == U` and the shared `S` which means the container is also + // guaranteed to be layout compatible. + &*(self as *const HashSet as *const HashSet) + } + } +} +``` + +(But at that point, it may be better to use something like an unsafe marker trait or unsafe trait with default implementations.) + ## Scoped bounds as contextual alternative to sealed traits This is probably pretty strange, and may not be useful at all, but it likely doesn't hurt to mention this. From e73714e9c29509163d1373d8cca8833248cffea6 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Fri, 8 Dec 2023 17:39:00 +0100 Subject: [PATCH 09/27] Small formatting fix and clarification --- text/0000-scoped-impl-trait-for-type.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index df1433fa29d..c27d1db98bf 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -466,11 +466,11 @@ The following generics that never rely in the consistency of implementation of t - *superficially*\* `Fn(T) -> U`, `FnMut(T) -> U`, `FnOnce(T) -> U`, `Future`, `Iterator`, `std::ops::Coroutine` and similar (closures), - `Pin

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of a type parameter's captured *implementation environment*.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing implementations.

| +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of a type parameter's captured *implementation environment*.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing implementation environments in their type parameters.

| ## A Language for Generic Programming in the Large @@ -2457,7 +2613,7 @@ and key differences: # Unresolved questions [unresolved-questions]: #unresolved-questions -- I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" in better? +- I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" is better? - In macros, which function-call token should provide the resolution context from where to look for scoped `impl Trait for Type`s (in all possible cases)? @@ -2507,10 +2663,6 @@ and key differences: } ``` -- How important is explicit binding? If it should be included here, which syntax should it use? - - See [explicit-binding] in *Future possibilities* below for one possibility. - ## Which `struct`s should be implementation-invariant? [which-structs-should-be-implementation-invariant]: #which-structs-should-be-implementation-invariant @@ -3012,7 +3164,7 @@ impl HashSet { (But at that point, it may be better to use something like an unsafe marker trait or unsafe trait with default implementations.) -## Scoped bounds as contextual alternative to sealed traits +## Sealed trait bounds This is probably pretty strange, and may not be useful at all, but it likely doesn't hurt to mention this. @@ -3027,9 +3179,15 @@ pub use impl Trait for Type1 {} pub use impl Trait for Type2 {} ``` -With this construct, `function` could privately rely on implementation details of `Trait` on `Type1` and `Type2` without defining a new sealed wrapper trait. +With this construct, `function` could privately rely on implementation details of `Trait` on `Type1` and `Type2` without defining a new sealed wrapper trait. It also becomes possible to easily define multiple sealed sets of implementations this way, by defining modules that export them. + +Overall this would act as a more-flexible but also more-explicit counterpart to sealed traits. + +Iff the caller is allowed to use this function without restating the binding, then removing the scope would be a breaking change (as it is already with bindings captured on type parameters in public signatures, so that would be consistent for this syntactical shape). + +Binding an implementation in a call as `function::()` while it is constrained as `fn function() { … }` MUST fail for distinct modules `a` and `b` even if the implementations are identical, as otherwise this would leak the implementation identity into the set of breaking changes. -If the caller is allowed to use this function without restating the binding, then removing the scope would be a breaking change (as it is already with bindings captured on type parameters in public signatures, so that would be consistent for this syntactical shape). +> That convenience (automatically using the correct implementations even if not in scope) also really should exist only iff there already is robust, near-effortless tooling for importing existing scoped implementations where missing. Otherwise this features here *would* get (ab)used for convenience, which would almost certainly lead to painful overly sealed APIs. ## Glue crate suggestions [glue-crate-suggestions]: #glue-crate-suggestions @@ -3060,3 +3218,69 @@ my-crate_bevy_reflect_glue = "0.2.1" (This sketch doesn't take additional registries into account.) Ideally, crates.io should only accept existing crates here (but with non-existing version numbers) and Cargo should by default validate compatibility where possible during `cargo publish`. + +## Reusable limited-access APIs + +Given a newtype of an unsized type, like + +```rust +#[repr(transparent)] +pub struct MyStr(str); +``` + +for example, there is currently no safe-Rust way to convert between `&str` and `&MyStr` or `Box` and `Box`, even though *in the current module which can see the field* this is guaranteed to be a sound operation. + +One good reason for this is that there is no way to represent this relationship with a marker trait, since any global implementation of such a trait would give outside code to this conversion too. + +With scoped `impl Trait for Type`, the code above could safely imply a marker implementation like the following in the same scope: + +```rust +// Visibility matches newtype or single field, whichever is more narrow. + +use unsafe impl Transparent for MyStr {} +use unsafe impl Transparent for str {} +// Could symmetry be implied instead? +``` + +(`Transparent` can and should be globally reflexive.) + +This would allow safe APIs with unlimited visibility like + +```rust +pub fn cast, U>(value: T) -> U { + unsafe { + // SAFETY: This operation is guaranteed-safe by `Transparent`. + std::mem::transmute(value) + } +} +``` + +and + +```rust +unsafe impl, U> Transparent> for Box {} +unsafe impl<'a, T: Transparent, U> Transparent<&'a U> for &'a T {} +unsafe impl<'a, T: Transparent, U> Transparent<&'a mut U> for &'a mut T {} +``` + +which due to their bound would only be usable where the respective `T: Transparent`-implementation is in scope, that is where by-value unwrapping-and-then-wrapping would be a safe operation (for `Sized` types in that position). + +Overall, this would make unsized newtypes useful without `unsafe`, by providing a compiler-validated alternative to common reinterpret-casts in their implementation. The same likely also applies to certain optimisations for `Sized` that can't be done automatically for unwrap-then-wrap conversions as soon as a custom `Allocator` with possible side-effects is involved. + +If a module wants to publish this marker globally, it can do so with a separate global implementation of the trait, which won't cause breakage. (As noted in [efficient-compilation], the compiler should treat implementations of empty traits as identical early on, so that no code generation is unnecessarily duplicated.) + +> *Could* sharing pointers like `Arc` inherit this marker from their contents like `Box` could? I'm unsure. They probably *shouldn't* since doing this to exposed shared pointers could easily lead to hard-to-debug problems depending on drop order. +> +> A global +> +> ```rust +> unsafe impl, U> Transparent> for UnsafeCell {} +> ``` +> +> should be unproblematic, but a global +> +> ```rust +> unsafe impl Transparent for UnsafeCell {} +> ``` +> +> (or vice versa) **must not** exist to allow the likely more useful implementations on `&`-like types. From b0fe6b3fceeaa64f75efaf195aa76e79c44ead00 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 5 May 2024 16:20:50 +0200 Subject: [PATCH 11/27] Pre-RFC v6 - Implemented the former Future Possibilities section "Explicit binding" into the main text as "inline implementation environments", mainly in form of grammar extensions. - Specified that call expressions capture the implementation environment of their function operand, acting as host for implementation-invariant generics there. - Miscellaneous wording clarifications and fixes. (Turns out you can't call a tuple struct's implicit constructor through a type alias, whoops.) --- text/0000-scoped-impl-trait-for-type.md | 483 +++++++++++++----------- 1 file changed, 266 insertions(+), 217 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 9950c71a3dd..5e0f8062050 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other scopes (including other crates), and differentiates type identity of most generics by which scoped trait implementations are available to each specified generic type parameter. +This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other item scopes (including other crates), and differentiates type identity of most generics by which scoped trait implementations are available to each discretised generic type parameter (also adding syntax to specify differences to these captured *implementation environments* directly on generic type arguments). This (along with some details specified below) enables any crate to @@ -24,7 +24,7 @@ This document uses "scoped implementation" and "scoped `impl Trait for Type`" in While orphan rules regarding trait implementations are necessary to allow crates to add features freely without fear of breaking dependent crates, they limit the composability of third party types and traits, especially in the context of derive macros. -For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between crates. +For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between derive macro crates. Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions in order to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. @@ -34,14 +34,6 @@ This RFC aims to address these pain points by creating a new path of least resis For realistic examples of the difference this makes, please check the [rationale-and-alternatives] section. -# (Pending changes to this draft) - -It should be possible to specify differences in the implementation environment directly where it is captured, e.g. as `BTreeSet`, without bringing these implementations into scope. - -As this requires additional grammar changes and overall more adjustments to this document, I plan to tackle that a bit later. - -For now, see [explicit-binding] in *Future possibilities* below for more, but less rigorous, text about one possibility. - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -123,6 +115,7 @@ use ::{impl Trait for Type}; Scoped implementations are resolved on most generics' type parameters where those are specified, and become part of the (now less generic) host type's identity: ```rust +#[derive(Default)] struct Type(T); trait Trait { @@ -136,6 +129,8 @@ impl Type { } mod nested { + use super::{Trait, Type}; + use impl Trait for () { fn trait_fn() { println!("nested"); @@ -146,13 +141,17 @@ mod nested { } use nested::Alias; -Alias::type_fn(); // "nested" +fn main() { + Alias::type_fn(); // "nested" + + // Type::<()>::type_fn(); + // ^^^^^^^ error[E0599]: the function or associated item `type_fn` exists for struct `Type<()>`, but its trait bounds were not satisfied -// Type::<()>::type_fn(); -// ^^^^^^^ error[E0599]: the function or associated item `type_fn` exists for struct `Type<()>`, but its trait bounds were not satisfied + // let t: Type<()> = Alias::default(); + // ^^^^^^^^^ error[E0308]: mismatched types -// let t: Type<()> = Alias(()); -// ^^^^^^^^^ error[E0308]: mismatched types + let t: Type<() as Trait in nested> = Alias::default(); +} ``` This works equally not just for type aliases but also fields, `let`-bindings and also where generic type parameters are inferred automatically from expressions (for example to call a constructor). @@ -235,18 +234,18 @@ The core Rust language grammar is extended as follows: - [*TraitImpl*]'s definition is prepended with (*Visibility*? `use`)? and refactored for partial reuse to arrive at - > *TraitImpl*: + > *TraitImpl* : >   **(*Visibility*? `use`)?** `unsafe`? ***TraitCoverage*** >   `{` >    *InnerAttribute*\* >    *AssociatedItem*\* >   `}` > - > **TraitCoverage**: + > ***TraitCoverage*** : >   ***TraitCoverageNoWhereClause*** >   *WhereClause*? > - > **TraitCoverageNoWhereClause**: + > ***TraitCoverageNoWhereClause*** : >   `impl` *GenericParams*? `!`? *TypePath* `for` *Type* where a trait implementation with that `use`-prefix provides the implementation *only* as item in the containing item scope. @@ -259,15 +258,15 @@ The core Rust language grammar is extended as follows: - [*UseTree*]'s definition is extended for importing scoped implementations by inserting the extracted *TraitCoverage* and *TraitCoverageNoWhereClause* rules as follows: - > *UseTree*: + > *UseTree* : >   (*SimplePath*? `::`)? `*` - >  | (*SimplePath*? `::`)? `{` + >   | (*SimplePath*? `::`)? `{` >   ( >    (**(**‍*UseTree* **| *TraitCoverageNoWhereClause*)** (`,` **(**‍*UseTree* **| *TraitCoverageNoWhereClause*)**)\* **(**`,` ***TraitCoverage*?)**?)? - >   **| *TraitCoverage*** + >    **| *TraitCoverage*** >   ) >   `}` - >  | *SimplePath* (`as` (IDENTIFIER | `_`))? + >   | *SimplePath* (`as` (IDENTIFIER | `_`))? Allowing a trailing *TraitCoverage* with *WhereClause* in a braced list is intended for ergonomics, but rustfmt should brace it individually by default, then append a trailing comma where applicable as usual. A '`,`' in the *WhereClause* here is not truly ambiguous because *WhereClauseItem*s contain '`:`', but allowing that ahead of others would likely be visually confusing and tricky to implement (requiring an arbitrarily long look-ahead). Alternatively to allowing a trailing *TraitCoverage* in mixed lists, an error similar to [E0178] could be emitted. @@ -287,6 +286,71 @@ The core Rust language grammar is extended as follows: [*UseTree*]: https://doc.rust-lang.org/reference/items/use-declarations.html?highlight=UseTree#use-declarations +- [*TypeParam*], [*GenericArg*] and [*GenericArgsBinding*] are extended to accept *implementation environments* inline: + + > *TypeParam* : + >   IDENTIFIER ( `:` *TypeParamBounds*? )? ( `=` *Type* ***ImplEnvironment*?** )? + > + > *GenericArg* : + >   *Lifetime* | *Type* ***ImplEnvironment*?** | *GenericArgsConst* | *GenericArgsBinding* + > + > *GenericArgsBinding* : + >   IDENTIFIER `=` *Type* ***ImplEnvironment*?** + > + > ***ImplEnvironment* :** + > **  `as` ( *ImplEnvironmentEntry* ( `+` *ImplEnvironmentEntry* )\* `+`? )?** + > + > ***ImplEnvironmentEntry* :** + > **  (** + > **   *ForLifetimes*? *TypePath*** + > **   | ( *ForLifetimes*? *TypePath* )** + > **  )** + > **  `in` ( `::` | *SimplePath* )** + + When detecting conflicting implementations, the *ImplEnvironment* is treated as creating a distinct scope nested in its surrounding scope. Each resulting *implementation environment* must be conflict-free, but between them they *can* contain conflicting implementations. + + Even when an *ImplEnvironment* is added as above, the resulting *implementation environment* still captures scoped implementations from the surrounding scope for all traits that were not specified inline! A global implementation can be used explicitly by sourcing it from `::` instead of a module. + + For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit inline *implementation environments* should be allowed where no matching bound is present, but should produce an [unused-scoped-implementation] warning iff neither published nor used in the same crate (including for type identity distinction). + + > Whether inline *implementation environments* would inherit from each other is intentionally left unspecified, as identical types can't be nested without indirection, which ensures such a situation isn't relevant. + + [*TypeParam*]: https://doc.rust-lang.org/reference/items/generics.html?highlight=TypeParam#generic-parameters + [*GenericArg*]: https://doc.rust-lang.org/reference/paths.html?highlight=GenericArg#paths-in-expressions + [*GenericArgsBinding*]: https://doc.rust-lang.org/reference/paths.html?highlight=GenericArgsBinding#paths-in-expressions + +- Further type specification syntax is extended as follows: + + > *ParenthesizedType* : + >   `(` *Type* ***ImplEnvironment*?** `)` + > + > *TupleType* : + >   `(` `)` + >   | `(` ( *Type* ***ImplEnvironment*?** `,` )+ **(** *Type* ***ImplEnvironment*? )**? `)` + > + > *ArrayType* : + >   `[` *Type* ***ImplEnvironment*?** `;` *Expression* `]` + > + > *SliceType* : + >   `[` *Type* ***ImplEnvironment*?** `]` + + > Closure types are not extended with *ImplEnvironment* because *implementation environments* annotated on their parameters would never be effective. + > + > Extending *ParenthesizedType* this way is necessary to specify *implementation environments* for pointer types' generic type parameters, e.g. `&(Type as Trait in module)`. + +- [*QualifiedPathType*] is also extended for this purpose, but can additionally act as *implementation environment* scope that also affects the *implementation environment* of nested types, using a clause starting with `where`: + + > *QualifiedPathType* : + >   `<` *Type* ( `as` *TypePath* **(`in` (`::` | *SimplePath* ) )?** )? **( `where` ( *Type* *ImplEnvironment* `,` )\* ( *Type* *ImplEnvironment* )? )?** `>` + + The form `` is syntactic sugar for `<(Type as Trait in module) as Trait>`, to avoid repetition of potentially long traits. + + Implementations imported after `where` must be valid, but don't necessarily have to be relevant. + + > I am **not** confident that `where` is the right keyword here, but it seems like this best option among the already-existing ones. `use`-syntax feels far too verbose here. Maybe the above but with `using` or `with` in place of `where`? + + [*QualifiedPathType*]: https://doc.rust-lang.org/reference/paths.html?highlight=QualifiedPathType#qualified-paths + ## No scoped `impl Trait for Type` of auto traits, `Copy` and `Drop` Implementations of auto traits state guarantees about private implementation details of the covered type(s), which an external implementation can almost never do soundly. @@ -353,6 +417,8 @@ When a generic type parameter is used to discretise another generic, the capture Note that type parameter defaults too capture their *implementation environment* where they are specified, so at the initial definition site of the generic. This environment is used whenever the type parameter default is used. +In order to avoid too much friction, [implementation-invariant-generics] are exempt from acting as host for *implementation environments* on their own. + ## Type identity of discrete types [type-identity-of-discrete-types]: #type-identity-of-discrete-types @@ -439,11 +505,11 @@ fn main() { // as from its perspective, the binding is stripped due to being top-level. Alias1::nested_convertible(Alias2::default()); - // The reflexive `impl Into for T` does not to the generic here, + // The reflexive `impl Into for T` does not apply between the aliases here, // as the distinct capture in the type parameter affects its inherent identity. // (It's unfortunately not possible to generically implement this conversion without specialisation.) // Alias1::default().conv::(); - // ^^^^ error[E0277]: the trait bound `[…]¹` is not satisfied + // ^^^^ error[E0277]: the trait bound `Generic: From>>` is not satisfied // Identical types are interchangeable. Alias2::identical(Alias3::default()); @@ -453,16 +519,14 @@ fn main() { As mentioned in [type-identity-of-discrete-types], implementations on the generic type *itself* do *not* affect its type identity, as can be seen with `Alias4` above. -The `TypeId` of these generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] or `(…,)`-tuple-types combining them instead. - -¹ With the current implementation, this would likely say `Generic<_>: From>>`, which isn't helpful. With [explicit-binding], it could say `Generic: From>>`. +The `TypeId` of these generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] or `(…,)`-tuple-types combining them instead of on `TypeId::of::()`. (see also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]) (For a practical example, see [logical-consistency] [of-generic-collections].) ### Implementation-invariant generics [implementation-invariant-generics]: #implementation-invariant-generics -The following generics that never rely in the consistency of implementation of their type parameters are implementation-invariant: +The following generics that never rely on the consistency of trait implementations on their type parameters are implementation-invariant: - `&T`, `&mut T` (references), - `*const T`, `*mut T` (pointers), @@ -474,7 +538,14 @@ The following generics that never rely in the consistency of implementation of t Implementation-invariant generics never capture *implementation environments* on their own. Instead, their effective *implementation environments* follow that of their host, acting as if they were captured in the same scope. -The type identity of implementation-invariant generics seen on their own does not depend on the implementation environment. +The type identity of implementation-invariant generics seen on their own does not depend on the *implementation environment*. This also means that the `TypeId` of `Option` does not take into account differences of implementations *on* `T`. However, differences of implementations *in* `T` can still distinguish the types, in cases where the type identity (and possibly `TypeId`) of `T` *itself* are different. An example for this are generic type parameters' effective types that can have bounds-relevant implementations observably baked into them. + +Hosts are: + +- Type aliases (see [type-aliases-are-opaque-to-scoped-implementations]), +- [implementation-aware-generics], +- types written as *QualifiedPathType* (see [grammar-changes] to *QualifiedPathType*) and +- the *function operand* of [call expressions] (see [call-expressions-function-operand-captures-its-implementation-environment]). \* superficially: The underlying instance may well use a captured implementation internally, but this isn't surfaced in signatures. For example, a closure defined where `usize: PartialOrd in reverse + Ord in reverse` is just `FnOnce(usize)` but will use `usize: PartialOrd in reverse + Ord in reverse` privately when called. @@ -482,6 +553,73 @@ The type identity of implementation-invariant generics seen on their own does no See also [why-specific-implementation-invariant-generics]. +[call expressions]: https://doc.rust-lang.org/reference/expressions/call-expr.html#call-expressions + +## Call expressions' *function operand* captures its *implementation environment* +[call-expressions-function-operand-captures-its-implementation-environment]: #call-expressions-function-operand-captures-its-implementation-environment + +Call expressions capture the *implementation environment* in their *function operand*, acting as host for [implementation-invariant-generics]. This enables call expressions such as + +```rust +Option::::fmt(…) +``` + +where `fmt` receives the specified scoped implementation by observing it through the `T: Debug` bound on its implementing `impl` block. + +If no observing bound exists, code of this form should produce a warning spanning the `Trait in module` tokens. (see [unused-scoped-implementation]) + +## Type aliases are opaque to scoped implementations +[type-aliases-are-opaque-to-scoped-implementations]: #type-aliases-are-opaque-to-scoped-implementations + +As scoped `impl Trait for Type` is a fully lexically-scoped feature, the *implementation environment* present in a scope does not affect types hidden behind a type alias, except for the top-level type directly: + +```rust +trait Trait { + fn method(&self) -> &str; +} + +impl Trait for Type { + fn method(&self) -> &str { + "global" + } +} + +mod m1 { + use super::Type; + + pub type Alias = [Type; 1]; +} + +mod m2 { + use super::{Type, Trait}; + + pub use impl Trait for Type { + fn method(&self) -> &str { + "scoped" + } + } + + pub use impl Trait for [T; 1] { + fn method(&self) -> &str { + self[0].method() + } + } +} + +fn main() { + use m1::Alias; + use m2::{ + impl Trait for Type, + impl Trait for [Type; 1], + }; + + assert_eq!([Type].method(), "scoped"); + assert_eq!(Alias::default().method(), "global"); +} +``` + +Scoped implementations may still be observed through bounded generic type parameters on the type alias itself. (see [binding-choice-by-implementations-bounds]) + ## `TypeId` of generic type parameters' opaque types [typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types @@ -499,16 +637,14 @@ impl Trait for Type {} struct Generic(T); mod nested { - use super::{Trait, Type, Generic}; - use impl Trait for Type {}; - pub type B = Generic; + pub(super) use impl super::Trait for super::Type {} } // `A` and `B` are distinct due to different captured implementation environments. type A = Generic; -use nested::B; +type B = Generic; -fn no_bound(_: (T,), _: (U,)) { +fn no_bound(_: Generic, _: Generic) { assert_eq!(TypeId::of::(), TypeId::of::()); assert_ne!(TypeId::of::>(), TypeId::of::>()); @@ -516,7 +652,7 @@ fn no_bound(_: (T,), _: (U,)) { assert_eq!(TypeId::of::(), TypeId::of::()); } -fn yes_bound(_: (T,), _: (U,)) { +fn yes_bound(_: Generic, _: Generic) { assert_ne!(TypeId::of::(), TypeId::of::()); assert_ne!(TypeId::of::>(), TypeId::of::>()); @@ -534,7 +670,7 @@ In particular: - If no bound-relevant scoped implementations are captured in a type parameter, then the `TypeId` of the opaque type of that type parameter is identical to that of the discrete type specified for that type parameter. - Distinct sets of bound-relevant captured scoped implementations lead to distinct `TypeId`s of the opaque type of a type parameter. -- If the set of bound-relevant captured scoped implementations in two generic type parameters is the same, and the captured discrete type is identical, then the `TypeId` of the opaque types of these generic type parameters is identical. +- If the set of bound-relevant captured scoped implementations in two generic type parameters is the same, and the wrapped discrete type is identical, then the `TypeId` of the opaque types of these generic type parameters is identical. - If a generic type parameter is distinguishable this way, it remains distinguishable in called implementations even if those have fewer bounds - the relevant distinction is 'baked' into the generic type parameter's opaque type. These rules (and the transmutation permission in [layout-compatibility]) allow the following collection to remain sound with minimal perhaps unexpected behaviour: @@ -635,7 +771,7 @@ impl Type { then in another crate -- if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again then and +- if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again afterwards, and - if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured *implementation environment*, because `method` has observed the *exact* type parameter through its constraints. (In short: Don't use external-to-your-code implementations with the instance in any combination that wouldn't have been possible without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) @@ -665,12 +801,14 @@ This is intentional, as it makes the following code trivial to reason about: Implementations bind to other implementations as follows: -| `where`-clause on `impl`? | binding-site of used trait | monomorphised by used trait? | +| `where`-clause¹ on `impl`? | binding-site of used trait | monomorphised by used trait? | |-|-|-| | Yes. | Bound at each binding-site of `impl`. | Yes, like-with or as-part-of type parameter distinction. | | No. | Bound once at definition-site of `impl`. | No. | -A convenient way to think about this is that *`impl`-implementations are blanket implementations over `Self` in different implementation environments*. +¹ Or equivalent generic type parameter bound, where applicable. For all purposes, this RFC treats them as semantically interchangeable. + +A convenient way to think about this is that *`impl`-implementations with bounds are blanket implementations over `Self` in different implementation environments*. Note that `Self`-bounds on associated functions do **not** cause additional monomorphic variants to be emitted, as these continue to only filter the surrounding implementation. @@ -816,7 +954,7 @@ However, due to the `where Type: Trait` bound *on the implementation*, that impl `FnBoundedMonomorphic`'s implementations are monomorphic from the get-go just like `Monomorphic`'s. -Due to the narrower bounds on functions, their availability can vary between receivers but always matches that of the global implementation environment: +Due to the narrower bounds on functions, their availability can vary between receivers but always matches that of the global *implementation environment*: #### `::where_trait` @@ -850,9 +988,9 @@ As this discrete implementation's bound isn't over the `Self` type (and does not ## Binding and generics -`where`-clauses without generics or `Self` type, like `where (): Debug`, **do not** affect binding of implementations within an `impl` or `fn`, as the non-type-parameter-type `()` is unable to receive an implementation environment from the discretisation site. +`where`-clauses without generics or `Self` type, like `where (): Debug`, **do not** affect binding of implementations within an `impl` or `fn`, as the non-type-parameter-type `()` is unable to receive an *implementation environment* from the discretisation site. -However, `where (): From` **does** take scoped implementations into account because the blanket `impl From for U where T: Into {}` is sensitive to `T: Into<()>` which is part of the implementation environment captured in `T`! +However, `where (): From` **does** take scoped implementations into account because the blanket `impl From for U where T: Into {}` is sensitive to `T: Into<()>` which is part of the *implementation environment* captured in `T`! This sensitivity even extends to scoped `use impl From for ()` at the discretisation site, as the inverse blanket implementation of `Into` creates a scoped implementation of `Into` wherever a scoped implementation of `From` exists. This way, existing symmetries are fully preserved in all contexts. @@ -895,14 +1033,14 @@ fn function2(x: impl DerefMut) { } } - function1(Type1(Type2)); // <-- Clearly impossible. - function2(Type1(Type2)); // <-- Unexpected behaviour if allowed. + // function1(Type1(Type2)); // <-- Clearly impossible. + // function2(Type1(Type2)); // <-- Unexpected behaviour if allowed. } ``` Clearly, `function1` cannot be used here, as its generic bounds would have to bind to incompatible implementations. -But what about `function2`? Here, the bound is implicit but `Deref::deref` can still be accessed. For type compatibility, this would have to be the shadowed global implementation, which is most likely unintended decoherence. +But what about `function2`? Here, the bound is implicit but `Deref::deref` could still be accessed if the function could be called. For type compatibility, this would have to be the shadowed global implementation, which is most likely unintended decoherence. As such, **shadowing a trait implementation also shadows all respective subtrait implementations**. Note that the subtrait *may* still be immediately available (again), if it is implemented with a generic target and all bounds can be satisfied in the relevant scope: @@ -973,16 +1111,16 @@ impl Trait1 for Type { } ``` -In this case, the implementation of `Trait2` is *not* shadowed at all. Additionally, since `self.trait1();` here binds `Trait` on `Type` directly, rather than on a generic type parameter, it uses whichever `impl Trait1 for Type` is in scope *where it is written*. +In this case, the implementation of `Trait2` is *not* shadowed at all. Additionally, since `self.trait1();` here binds `Trait` on `Type` directly, rather than on a bounded generic type parameter, it uses whichever `impl Trait1 for Type` is in scope *where it is written*. ## Warnings ### Unused scoped implementation [unused-scoped-implementation]: #unused-scoped-implementation -Scoped implementations and `use`-declarations of such receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. +Scoped implementations and `use`-declarations of such (including those written as *ImplEnvironmentEntry*) receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. -(rust-analyzer should suggest removing an unused `use`-declaration as fix in either case.) +(rust-analyzer should suggest removing any unused `use`-declarations as fix in either case.) An important counter-example: @@ -1014,7 +1152,7 @@ Since `Alias` is exported, the compiler cannot determine within the library alon ### Global trait implementation available [global-trait-implementation-available]: #global-trait-implementation-available -Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that at least partially (in either sense) shadow another scoped implementation. +Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that *at all* shadow another scoped implementation. (Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) @@ -1029,7 +1167,7 @@ use impl Foo for T where T: Foo { } --------- ^^^^^^ ``` -A Rust developer may write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. +A Rust developer may want to write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. The warning should explain that and why the bound is impossible to satisfy. @@ -1097,7 +1235,7 @@ pub struct Struct { should produce eight warnings (or four/three warnings with multiple primary spans each, if possible). The warning should explain that the type can't be referred to by fully specified name outside the crate/module and that the implementation may be callable from code outside the crate/module. -(If [explicit-binding] is added to the RFC and used in such a way, then the warning should show up on the `Trait in module` span instead.) +If the binding is specified via inline *implementation environment*, then the warning should show up on the `Trait in module` span instead. Note that as with other private-in-public warnings, replacing @@ -1117,6 +1255,10 @@ use nested::{impl Trait for Type}; in the code sample above should silence the warning. +In some cases, adding `as Trait in ::` to the generic type argument could be suggested as quick-fix, though generally it's better to fix this warning by moving the scoped implementation into a nested scope or moving it into a module and importing it into nested scopes as needed. + +> This warning can't be suppressed for private traits because the presence of their scoped implementation on a generic type parameter still affects the `TypeId` of the capturing generic, which here is visible outside of the discretising module. + ### Imported implementation is less visible than item/field it is captured in [imported-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #imported-implementation-is-less-visible-than-itemfield-it-is-captured-in @@ -1183,7 +1325,7 @@ impl Trait for Type {} ### Incompatible or missing supertrait implementation [incompatible-or-missing-supertrait-implementation]: #incompatible-or-missing-supertrait-implementation -Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those. +Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those being in scope too (to avoid perceived and hard to reason-about inconsistencies). ```rust struct Type; @@ -1246,7 +1388,7 @@ Notably, this affects `TypeId::of::()` in implementations with most generi This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. -In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show the expected `TypeId` distinction. +In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show only the expected `TypeId` distinction. Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can be used, as tuples are [implementation-invariant-generics]. @@ -1416,7 +1558,7 @@ fn function2() -> impl Trait { In this case, the returned opaque types use the respective inner scoped implementation, as it binds on the `()` expression. -These functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: +The following functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: ```rust trait Trait {} @@ -1609,7 +1751,7 @@ Complexity aside, this could cause compiler performance issues since caching wou Fortunately, at least checking whether scoped implementations exist at all for a given trait and item scope should be reasonably inexpensive, so this hopefully won't noticeably slow down compilation of existing code. -That implementation environment binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. +That *implementation environment* binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. ## Cost of additional monomorphised implementation instances @@ -1838,7 +1980,9 @@ pub struct DataBundle { Even in cases where the glue API cannot be removed, it's still possible to switch to this simplified, easier to consume implementation and deprecate the original indirect API. -Note that the imported scoped implementations are *not* visible in the public API here, since they do not appear on generic type parameters in public items. There may still be situations in which defining a type alias is necessary to keep some scoped implementations away from generic type parameters. For a possible future way to eliminate that remaining friction, see [explicit-binding] in the [future-possibilities] section below. +Note that the imported scoped implementations are *not* visible in the public API here, since they do not appear on generic type parameters in public items. There may still be situations in which defining a type alias is necessary to keep some scoped implementations away from generic type parameters. In some cases, it could be enough to add `as Trait in ::` to generic type arguments to restore their *implementation environment* to contain global implementations only. + +> In some cases, where a field type is quoted in a derive macro directly, writing `(Type as Trait in module)` only there could *in theory* also work, but this would heavily depend on the macro's implementation details. See also [should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused]. Unlike with external newtypes, there are no potential conflicts beyond overlapping imports and definitions in the same scope. These conflicts can *always* be resolved both without editing code elsewhere and without adding an additional implementation: @@ -2308,19 +2452,19 @@ pub mod d { `// ⚠` and non-digit circles have the same meanings as above. -The following table describes how the types are observed at runtime in the lines marked with ❶ and ❷. It borrows some syntax from [explicit-binding] to express this clearly, but **denotes types as if seen from the global *implementation environment***. +The following table describes how the types are observed at runtime in the lines marked with ❶ and ❷. Types are denoted as if seen from the global *implementation environment* with differences written inline, which should resemble how they are formatted in compiler messages and tooling. | within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | |-|-|-| | `a::discrete` | `HashSet
` | `A` | -| `a::generic` | `HashSet` | `A` | -| `a::bounded` | `HashSet` | `A` ∘ `Hash in d` | -| `b::discrete` | `HashSet` | `B` | -| `b::generic` | `HashSet` | `B` | -| `b::bounded` | `HashSet` | `B` ∘ `Hash in d` | +| `a::generic` | `HashSet` | `A` | +| `a::bounded` | `HashSet` | `A` ∘ `Hash in d` | +| `b::discrete` | `HashSet` | `B` | +| `b::generic` | `HashSet` | `B` | +| `b::bounded` | `HashSet` | `B` ∘ `Hash in d` | | `c::discrete` | `HashSet` | `C` | -| `c::generic` | `HashSet` | `C` | -| `c::bounded` | `HashSet` | `C` ∘ `Hash in d` | +| `c::generic` | `HashSet` | `C` | +| `c::bounded` | `HashSet` | `C` ∘ `Hash in d` | The combination ∘ is not directly expressible in `TypeId::of::<>` calls (as even a direct top-level annotation would be ignored without bounds). Rather, it represents an observation like this: @@ -2360,14 +2504,14 @@ TypeId::of::<(T)>(); // ❷ | within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | |-|-|-| | `a::discrete` | `HashSet<(A,)>` | `(A,)` | -| `a::generic` | `HashSet<(A: Hash in d + Trait in d,)>` | `(A,)` | -| `a::bounded` | `HashSet<(A: Hash in d + Trait in d,)>` | `(A` ∘ `Hash in d,)` | -| `b::discrete` | `HashSet<(B: Hash in `***`b`***` + Trait in`***` b`***`,)>` | `(B,)` | -| `b::generic` | `HashSet<(B: Hash in d + Trait in d,)>` | `(B,)` | -| `b::bounded` | `HashSet<(B: Hash in d + Trait in d,)>` | `(B` ∘ `Hash in d,)` | +| `a::generic` | `HashSet<(A as Hash in d + Trait in d,)>` | `(A,)` | +| `a::bounded` | `HashSet<(A as Hash in d + Trait in d,)>` | `(A` ∘ `Hash in d,)` | +| `b::discrete` | `HashSet<(B as Hash in `***`b`***` + Trait in`***` b`***`,)>` | `(B,)` | +| `b::generic` | `HashSet<(B as Hash in d + Trait in d,)>` | `(B,)` | +| `b::bounded` | `HashSet<(B as Hash in d + Trait in d,)>` | `(B` ∘ `Hash in d,)` | | `c::discrete` | `HashSet<(C,)>` | `(C,)` | -| `c::generic` | `HashSet<(C: Hash in d + Trait in d,)>` | `(C,)` | -| `c::bounded` | `HashSet<(C: Hash in d + Trait in d,)>` | `(C` ∘ `Hash in d,)` | +| `c::generic` | `HashSet<(C as Hash in d + Trait in d,)>` | `(C,)` | +| `c::bounded` | `HashSet<(C as Hash in d + Trait in d,)>` | `(C` ∘ `Hash in d,)` | As you can see, the type identity of the tuples appears distinct when contributing to an implementation-aware generic's type identity but (along with the `TypeId`) remains appropriately fuzzy when used alone. @@ -2432,7 +2576,7 @@ In each case, the meaning of identical grammar elements lines up exactly - only When looking for the scoped implementation affecting a certain type, strict shadowing ensures that it is always the closest matching one that is effective. -As such, readers can stop scanning once they encounter a match, instead of checking the entire file's length for another implementation that may be present in the outermost scope. +As such, readers can stop scanning once they encounter a match (or module boundary, whether surrounding or nested), instead of checking the entire file's length for another implementation that may be present in the outermost scope. Aside from *implementation environments* captured *inside* generics, scoped implementations cannot influence the behaviour of another file without being mentioned explicitly. @@ -2491,7 +2635,7 @@ In the long run, this can lead to less near-duplicated functionality in the depe Scoped implementations can be documented and appear as separate item category in rustdoc-generated pages. -Rustdoc should be able to detect and annotate captured scoped implementations in public signatures automatically. This, in addition to warnings, should be another tool to help avoid accidental exposure of scoped implementations. +Rustdoc should be able to detect and annotate captured scoped implementations in public signatures automatically. This, in addition to warnings, could be another tool to help avoid accidental exposure of scoped implementations. Implementation origin and documentation could be surfaced by rust-analyzer in relevant places. @@ -2510,11 +2654,11 @@ On the other hand, certain types (e.g. references and (smart) pointers) that oft Drawing a line manually here is an attempt to un-break this *by default* for the most common cases while maintaining full compatibility with existing code and keeping awareness of scoped `impl Trait for Type` entirely optional for writing correct and user-friendly APIs. -As a concrete example, this ensures that `Box>>` is automatically interchangeable even if spelled out in the presence of scoped [error-handling-and-conversions] affecting `Error`, but that `BinaryHeap>` and `BinaryHeap>` don't mix. +As a concrete example, this ensures that `Box>>` is automatically interchangeable even if spelled out in the presence of scoped [error-handling-and-conversions] affecting `Error`, but that `BinaryHeap>` and `BinaryHeap>` don't mix. Functions pointers and closure trait( object)s should probably be fairly easy to pass around, with their internally-used bindings being an implementation detail. Fortunately, the Rust ecosystem already uses more specific traits for most configuration for better logical safety, so it's likely not too messy to make these implementation-invariant. -Traits and trait objects cannot be implementation invariant (including for their associated types!) because it's possible to define `OrderedExtend` and `OrderedIterator` traits with logical consistency requirement on `Ord` between them. +Traits and trait objects cannot be implementation invariant by default (including for their associated types!) because it's already possible to define `OrderedExtend` and `OrderedIterator` traits with logical consistency requirement on `Ord` between them. ## Efficient compilation [efficient-compilation]: #efficient-compilation @@ -2531,13 +2675,11 @@ The compiler should treat implementations of the same empty trait on the same ty ### Named implementations -Named implementations/models could be used to more-easily use potentially conflicting implementations in the same scope, but in exchange they would have to always be bound explicitly, which would likely hinder use outside of `derive`s and generics to an inconvenient level. +Use of named implementations is not as obvious as stating the origin-trait-type triple in close proximity, so code that uses named implementations tends to be harder to read. -Additionally, the use of named implementations is not as obvious as stating the origin-trait-type triple in close proximity. +Like named implementations, the scope-identified implementations proposed here can be written concisely in generic parameter lists (as `Type as Trait in module`), limiting the code-writing convenience advantage of named implementations. Where needed, the module name can be chosen to describe specific function, e.g. exporting reverse-ordering `Ord` and `PartialOrd` implementations from a module called `reverse`. -Scoped `impl Trait for Type` would not require proper-named models for later [explicit-binding], as the module already uniquely identifies an implementation for each type-trait combination. - -(See also [prior-art]: [lightweight-flexible-object-oriented-generics].) +If named implementations can't be brought into scope (see Genus in [lightweight-flexible-object-oriented-generics]), that limits their practical application to where they can be captured in [implementation-aware-generics]. Bringing named implementations into scope would be more verbose than for module-trait-type-identified as subsetting would still be required to preserve useful room for library crate evolution. ### Weakening coherence rules @@ -2574,7 +2716,7 @@ There are some parallels between Genus's models and the scoped `impl Trait for T | Genus | scoped `impl Trait for Type` | reasoning | |---|---|---| | Proper-named models | Anonymous scoped implementations | Use of existing coherence constraints for validation. Forced subsetting in `use`-declarations improves stability. The `impl Trait for Type` syntax stands out in `use`-declarations and is intuitively readable. | -| Explicit bindings of non-default models | Only implicit bindings | Focus on simplicity. Mixed bindings for definitions with the same scope/type/trait triple are rare and can be emulated with newtypes where needed. More natural use with future specialisation. | +| Explicit bindings of non-default models | Mainly implicit bindings, but explicit bindings of scoped *and global* implementations are possible in some places. | Focus on simplicity and ergonomics of the most common use-case. More natural use with future specialisation. | | Comparing containers inherently constrain type parameters in their type definition. | Available scoped implementations for discretised type parameters become part of the type identity. |

This is a tradeoff towards integration with Rust's ecosystem, as generics are generally not inherently bounded on collection types in Rust.

There is likely some friction here with APIs that make use of runtime type identity. See [split-type-identity-may-be-unexpected].

| Some features are largely equivalent: @@ -2583,7 +2725,7 @@ Some features are largely equivalent: |---|---|---| | Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | | Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | -| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of a type parameter's captured *implementation environment*.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing implementation environments in their type parameters.

| +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of unrelated parts of the top-level *implementation environment* of their type parameters.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing *implementation environments* in their type parameters.

| ## A Language for Generic Programming in the Large @@ -2606,62 +2748,24 @@ and key differences: |---|---|---| | Only discrete model imports | Includes generic imports and re-exports | This is pointed out as '[left] for future work' in the paper. Here, it follows directly from the syntax combination of Rust's `use` and `impl Trait for Type` items. | | - | (Rust) Global implementations | The automatic availability of global implementations between separately imported traits and types offers more convenience especially when working with common traits, like those backing operators in Rust. | -| Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | +| Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation or module boundary.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | | - | (Rust) Trait objects | 𝒢 does not appear to support runtime polymorphism beyond function pointers. Scoped `impl Trait for Type` is seamlessly compatible with `dyn Trait` coercions (iff `Trait` is object-safe). | -| (unclear?) | Available implementations on discretised type parameters become part of the type identity of implementation-aware generics. |

This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour.

The tradeoff is that it may be difficult to explicitly annotate types in cases of mixed bindings with this RFC. As newtypes and named configuration token types are still preferred for changed behaviour, such cases will hopefully be limited. Otherwise, see [explicit-binding] for bikeshedded syntax.

| +| (unclear?) | Available implementations on discretised type parameters become part of the type identity of implementation-aware generics. | This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour. | # Unresolved questions [unresolved-questions]: #unresolved-questions -- I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" is better? - -- In macros, which function-call token should provide the resolution context from where to look for scoped `impl Trait for Type`s (in all possible cases)? - - This doesn't matter for `Span::call_site()` vs. `Span::mixed_site()` since scoped implementations would resolve transparently through both, but it does matter for `Span::def_site()` which should exclude them. - - It very much does matter if one of the opt-in mitigations for [first-party-implementation-assumptions-in-macros] is implemented. - -- Should outer generic type parameters be visible on/in scoped `impl Trait for Type`, including `use`-declarations? - - That would enable the following pattern: +## "global" implementations - ```rust - use some_crate::Trait; - - fn function(value: T) -> impl Trait { - use impl Trait for T { - // ... - } - - #[derive(Trait)] // Based on fields' `: Trait`. - struct Returned { - field: T, - } +I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" is better? - Returned { field: value } - } - ``` +## Precise resolution location of *implementation environments* in function calls - However, if [explicit-binding] is added then that is unnecessary, as the following would work: - - ```rust - use some_crate::Trait; - - fn function(value: T) -> impl Trait { - mod scoped { - use impl some_crate::Trait for T { - // ... - } - } +In macros, which function-call token should provide the resolution context from where to look for scoped `impl Trait for Type`s (in all possible cases)? - #[derive(Trait)] // Based on fields' `: Trait`. - struct Returned { - field: T, - } +This doesn't matter for `Span::call_site()` vs. `Span::mixed_site()` since scoped implementations would resolve transparently through both, but it does matter for `Span::def_site()` which should exclude them. - Returned:: { field: value } - } - ``` +It very much does matter if one of the opt-in mitigations for [first-party-implementation-assumptions-in-macros] is implemented. ## Which `struct`s should be implementation-invariant? [which-structs-should-be-implementation-invariant]: #which-structs-should-be-implementation-invariant @@ -2697,6 +2801,13 @@ Other candidates for consideration: - Other `DispatchFromDyn` types in the standard library like `Cell`, `SyncUnsafeCell`, `UnsafeCell` +## Should it be an error to specify an *implementation environment* in places where it's guaranteed to be unused? +[should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused]: #should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused + +With the given [grammar-changes], it's possible to write `fn((Type as Trait in module))`, but, at least without a surrounding host, here the *implementation environment* written inline is completely ineffective because function pointer types are discretised [implementation-invariant-generics]. + +On the other hand, making it an error rather than a [unused-scoped-implementation] warning could easily cause problems for macros. + # Future possibilities [future-possibilities]: #future-possibilities @@ -2724,7 +2835,7 @@ To use a global implementation not available through one of its dependencies, a extern impl Trait for Type; ``` -This would result in a compile error if the declaration is not fully covered by a global trait implementation. +This would result in a compile error or link-time error if the declaration is not fully covered by a global trait implementation. If the trait implementation is later made available plainly (that is: without `use`, subject to orphan rules) by a dependency, a warning should appear on the `extern impl` declaration, along with the suggestion to remove the `extern impl` item. @@ -2995,92 +3106,15 @@ This is likely a rather niche use-case. It could also be useful in the context of [scoped-fallback-implementations]. -## Explicit binding -[explicit-binding]: #explicit-binding - -It could be possible to explicitly state bindings. Here is an example: - -```rust -use std::collections::BinaryHeap; - -// Contains discrete implementations of `PartialOrd` and `Ord` that invert the comparison. -mod reverse; - -// Uses whichever implementation is in scope. -let max_heap: BinaryHeap = [1, 3, 2, 4].into(); - -// Explicit binding. Requirements are like for a discrete import. -let min_heap: BinaryHeap = [1, 3, 2, 4].into(); - -while let Some(max) in max_heap.pop() { - println!("{max}"); // 4, 3, 2, 1 -} - -while let Some(min) in min_heap.pop() { - println!("{min}"); // 1, 2, 3, 4 -} - -// Uses whichever implementation is in scope. -dbg!(::cmp(&1, &2)); // […] = Less - -// Explicit binding. Requirements are like for a discrete import. -dbg!(::cmp(&1, &2)); // […] = Greater - -// The previous example is syntactic sugar for general top-level binding: -dbg!(<(u32: PartialOrd in reverse) as PartialOrd>::cmp(&1, &2)); // […] = Greater - -// The forms can be mixed to bind supertraits: -dbg!(<(u32: PartialOrd in _) as Ord in reverse>::cmp(&1, &2)); // […] = Greater - -{ - let mut a = max_heap; - let mut b = min_heap; - - // a.append(&mut b); - // ^^^^^^ error[E0308]: mismatched types -} -``` - -```rust -mod custom_defaults { - use impl Default for &'static str { - // ... - } -} - -#[derive(Default)] -pub struct Struct<'a> { - pub a: (&'a str: Default in custom_defaults), - - // The custom `Default` is not captured here, - // since it's not actually in scope. - pub b: Vec<&'a str>, -} -``` - -This is of course syntax bikeshedding. - -Specifying implementations on fields manually is a way to provide them only to `derive` and other attribute macros, as these top-level implementations do *not* bind to the type and as such are *not* used by code that doesn't restate the type explicitly. (The built-in macros should be defined as doing so from the get-go. Unfortunately, for other macros this is likely an optional implementation detail.) - -Since the specified top-level implementation doesn't bind persistently inside `Struct`, the exported signature is just `struct Struct<'a> {pub a: &'a str, pub b: Vec<&'a str>}`. - -Binding only `PartialEq` would still shadow the discrete global `Ord` implementation, so binding both is required. - -As the scoped implementation of `Ord` in `reverse` is on a discrete type, it requires the specific supertrait implementation that is in scope for its definition. This should make it possible to infer the module here. (See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] below.) - -Top-level bindings require parentheses. To explicitly bind a global implementation, `::` can be used in place of the module path. - -For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit bindings should be allowed where no matching bound is present, but should produce an 'unused' warning iff neither published nor used in the same crate (including for type identity distinction). - ## Implicit import of supertrait implementations of scoped implementations defined on discrete types [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types]: #implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types As subtype implementations defined on discrete types always require specific supertrait implementations, the import of these supertrait implementations could be made implicit. -This would also affect [explicit-binding], changing +This would also affect *implementation environments* modified in generic arguments, changing ```rust -let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); ``` to @@ -3092,8 +3126,7 @@ let min_heap: BinaryHeap = [1, 3, 2, 4].into(); and ```rust -// (How to specify a scoped implementation with supertraits?) -dbg!(::cmp(&1, &2)); // […] = Greater +dbg!(::cmp(&1, &2)); // […] = Greater ``` to @@ -3102,10 +3135,26 @@ to dbg!(::cmp(&1, &2)); // […] = Greater ``` -The downside is that `use`-declarations would become less obvious. Implied supertrait implementation imports could be enabled only for [explicit-binding] to avoid this. +The downside is that `use`-declarations would become less obvious. Implied supertrait implementation imports could be enabled only for *implementation environments* specified inline on generic type parameters as e.g. `Type as Ord in module` to avoid this. If this is added later than scoped `impl Trait for Type`, then private scoped implementations **must not** be implicitly exported through this mechanism. (It's likely a good idea to not allow that anyway, as it would be surprising.) Making previously crate-private implementations available that way could lead to unsoundness. +### Alternatively + +It could be enough to allow inferring the module explicitly by writing `_` instead of its *SimplePath*, so that the snippets above become + +```rust +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +``` + +and + +```rust +dbg!(<(u32 as PartialOrd in _) as Ord in reverse>::cmp(&1, &2)); // […] = Greater +``` + +Here, too, the inference should only be of required supertrait implementations based on explicitly chosen implementations of their subtraits. + ## Conversions where a generic only cares about specific bounds' consistency With specialisation and more expressive bounds, an identity conversion like the following could be implemented: @@ -3168,12 +3217,12 @@ impl HashSet { This is probably pretty strange, and may not be useful at all, but it likely doesn't hurt to mention this. -Consider [explicit-binding] in bounds like here: +Consider *ImplEnvironment* clauses in bounds like here: ```rust use another_crate::{Trait, Type1, Type2}; -pub fn function() {} +pub fn function() {} pub use impl Trait for Type1 {} pub use impl Trait for Type2 {} @@ -3185,14 +3234,14 @@ Overall this would act as a more-flexible but also more-explicit counterpart to Iff the caller is allowed to use this function without restating the binding, then removing the scope would be a breaking change (as it is already with bindings captured on type parameters in public signatures, so that would be consistent for this syntactical shape). -Binding an implementation in a call as `function::()` while it is constrained as `fn function() { … }` MUST fail for distinct modules `a` and `b` even if the implementations are identical, as otherwise this would leak the implementation identity into the set of breaking changes. +> That convenience (automatically using the correct implementations even if not in scope) also really should exist only iff there already is robust, near-effortless tooling for importing existing scoped implementations where missing. Otherwise this feature here *would* get (ab)used for convenience, which would almost certainly lead to painful overly sealed APIs. -> That convenience (automatically using the correct implementations even if not in scope) also really should exist only iff there already is robust, near-effortless tooling for importing existing scoped implementations where missing. Otherwise this features here *would* get (ab)used for convenience, which would almost certainly lead to painful overly sealed APIs. +Binding an implementation in a call as `function::()` while it is constrained as `fn function() { … }` MUST fail for distinct modules `a` and `b` even if the implementations are identical, as otherwise this would leak the implementation identity into the set of breaking changes. ## Glue crate suggestions [glue-crate-suggestions]: #glue-crate-suggestions -If crates move some of their overlay features into glue crates, as explained in [unblock-ecosystem-evolution], it would be nice if they could suggest them if both they and e.g. Serde were `cargo add`ed to the project dependencies. +If crates move some of their overlay features into glue crates, as explained in [unblock-ecosystem-evolution], it would be nice if they could suggest them if both they and e.g. Serde were `cargo add`ed as direct dependencies of a crate currently being worked on. An example of what this could look like: @@ -3263,7 +3312,7 @@ unsafe impl<'a, T: Transparent, U> Transparent<&'a U> for &'a T {} unsafe impl<'a, T: Transparent, U> Transparent<&'a mut U> for &'a mut T {} ``` -which due to their bound would only be usable where the respective `T: Transparent`-implementation is in scope, that is where by-value unwrapping-and-then-wrapping would be a safe operation (for `Sized` types in that position). +which due to their bound would only be usable where the respective `T: Transparent`-implementation is in scope, that is: where by-value unwrapping-and-then-wrapping would be a safe operation (for `Sized` types in that position). Overall, this would make unsized newtypes useful without `unsafe`, by providing a compiler-validated alternative to common reinterpret-casts in their implementation. The same likely also applies to certain optimisations for `Sized` that can't be done automatically for unwrap-then-wrap conversions as soon as a custom `Allocator` with possible side-effects is involved. From 36da3b54dde8496a030dc436e2f6a6b4761ef155 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 12 May 2024 12:37:01 +0200 Subject: [PATCH 12/27] Final RFC-draft for Scoped `impl Trait for Type` - Added more guide-level documentation changes - Reordered warnings and errors to the end of the reference-level explanation - Some small adustments to wording and formatting --- text/0000-scoped-impl-trait-for-type.md | 779 +++++++++++++----------- 1 file changed, 428 insertions(+), 351 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index 5e0f8062050..78077fab226 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -112,7 +112,7 @@ use ::{impl Trait for Type}; ### Scoped implementations and generics [scoped-implementations-and-generics]: #scoped-implementations-and-generics -Scoped implementations are resolved on most generics' type parameters where those are specified, and become part of the (now less generic) host type's identity: +Scoped implementations are resolved on most generics' type arguments where those are specified, and become part of the (now less generic) host type's identity: ```rust #[derive(Default)] @@ -216,6 +216,7 @@ If the type whose API was extended this way later gains the same trait inherentl Be careful about literal coercion when using generic traits this way! For example, if a scoped implementation of `Index` is used and a global `Index` implementation is added later on the same type, the compiler will *not* automatically decide which to use for integer literal indices between these two. ## Rustdoc documentation changes +[rustdoc-documentation-changes]: #rustdoc-documentation-changes ### `use` and `impl` keywords @@ -224,6 +225,82 @@ The documentation pages [for the `use` keyword] and [for the `impl` keyword] are [for the `use` keyword]: https://doc.rust-lang.org/stable/std/keyword.use.html [for the `impl` keyword]: https://doc.rust-lang.org/stable/std/keyword.impl.html +### `TypeId` + +The page for [`TypeId`] gains two sections with the following information: + +```markdown +# `TypeId` and scoped implementations + +To make sure that that are no mix-ups between, for example, `HashSet` and `HashSet`, any such difference implies distinct `TypeId`s between such discretised generics (and that the types are not mutually assignable). + +This also affects trait-bounded generic type parameters: If `T` is bounded on `Hash`, then `TypeId::of::()` results in distinct `TypeId`s in that context depending on the captured implementation. + +However, note that `TypeId::of::()` and `TypeId::of::()` are always equivalent for one definition of `T`, as `TypeId::of`'s implementation does **not** have a `T: Hash` bound! + +For convenience (so that their values are easily interchangeable across crates), the following types ignore scoped implementations *on* their generic arguments in terms of *their own* type identity: […] + +Despite this, differences in *type arguments'* discrete identities (for example from scoped implementations captured *in* them) distinguish the type identity of *all* discretised generics they appear in. + +# `TypeId::of::()` may change for values of generics + +To make type-erased collections sound and unsurprising by default, it's sound to transmute between instances of an external generic type that differ only in their captured scoped implementations, **iff and only iff** no inconsistency is ever observed by bounds (including across separate function calls). + +However, this poses a problem: `TypeId::of::()` (just like the written-out form of any type that doesn't ignore scoped implementations) takes *all* differences in captured implementation environments into account, not just those relevant to trait bounds. + +As such, prefer `TypeId::of::()` whenever possible in order to make only the distinctions you require. You can use tuples to combine multiple type parameters without over-distinguishing: `TypeId::of::<(S, T)>()` +``` + +> These rules and the reasons for them are explained in detail in the [reference-level-explanation] below, as well as in [logical-consistency] as part of [rationale-and-alternatives]. It may be a good idea to link to similar longer explanations from the standard library docs above, even if just as "See also:"-style references for further reading. + +> The `[…]`-placeholder stands for a list of links to each implementation-invariant generic's documentation. + +See also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a way to narrowly alert users of this when relevant, and to scan for the potential impact of these changes ahead of time. + +[`TypeId`]: https://doc.rust-lang.org/stable/std/any/struct.TypeId.html + +### Implementation-invariant generics + +The pages for [implementation-invariant-generics] gain a section similar to the following: + +```markdown +# Implementation-invariant generic + +This type does not by itself capture scoped implementation environments when discretised. See [`TypeId` and scoped implementations] for more information. +``` + +where ``[`TypeId` and scoped implementations]`` is a link to the section added to the `TypeId` page above. + +### `mem::transmute` + +The page for [`transmute`] gains a section with the following information: + +```markdown +# `transmute` and scoped implementations + +It is sound to transmute between discretised generic types that differ only in their captured scoped implementation environments, **but only iff** such differences are **never** observed by bounds on their implementation, including functions that imply such by being implemented for discrete instances of the generic. +``` + +> As far as I can tell, this is only practically relevant for certain kinds of type-erasing collections, like type-erasing hash maps and B-trees, of which I couldn't find any examples on crates.io. +> +> Any straightforward implementations of such collections should also at worst exhibit only unexpected behaviour when consumed in the presence of scoped implementations, rather than unsoundness. + +[`transmute`]: https://doc.rust-lang.org/stable/std/mem/fn.transmute.html + +## Changes to The Rustonomicon + +The page on [Transmutes] gains the following warning in addition to the existing ones: + +```markdown +- It is unsound to change [captured scoped implementations] via transmute for any external type if this change ever causes a contradiction observable by the transmuted value's implementation. + + This can happen due to bounds on called functions and/or because a called function is implemented for a specific type discretised from the generic. +``` + +`[captured scoped implementations]` should link to documentation introducing scoped `impl Trait for Type`. + +[Transmutes]: https://doc.rust-lang.org/stable/nomicon/transmutes.html + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -387,18 +464,18 @@ use private::Sealing; pub trait Sealed: Sealing { fn assumed { - // (2) + // ❷ } } impl Generic { fn assuming { - // (1) + // ❶ } } ``` -In this crate, any code at (1) is currently allowed to make safety-critical assumptions about code at (2) and other implementations of `assumed`. +In this crate, any code at ❶ is currently allowed to make safety-critical assumptions about code at ❷ and other implementations of `assumed`. To ensure this stays sound, scoped `impl Trait for Type` where `Trait` is external requires that all supertraits of `Trait` are visible to the crate defining the scoped implementation or are defined not in `Trait`'s definition crate (meaning they must still be exported from a crate somewhere in the dependency tree). @@ -1113,552 +1190,552 @@ impl Trait1 for Type { In this case, the implementation of `Trait2` is *not* shadowed at all. Additionally, since `self.trait1();` here binds `Trait` on `Type` directly, rather than on a bounded generic type parameter, it uses whichever `impl Trait1 for Type` is in scope *where it is written*. -## Warnings - -### Unused scoped implementation -[unused-scoped-implementation]: #unused-scoped-implementation +## Resolution on generic type parameters +[resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters -Scoped implementations and `use`-declarations of such (including those written as *ImplEnvironmentEntry*) receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. +Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `use impl Trait for T`. -(rust-analyzer should suggest removing any unused `use`-declarations as fix in either case.) +However, a blanket implementation can only be bound on a generic type parameter iff its bounds are fully covered by the generic type parameter's bounds and other available trait implementations on the generic type parameter, in the same way as this applies for global implementations. -An important counter-example: +## Method resolution to scoped implementation without trait in scope -Filename: library/src/lib.rs +[Method calls] can bind to scoped implementations even when the declaring trait is not separately imported. For example: ```rust -pub struct Type; -pub struct Generic; - -pub trait Trait {} -use impl Trait for Type {} - -pub type Alias = Generic; -``` +struct Type; +struct Type2; -Filename: main.rs -```rust -use std::any::TypeId; +mod nested { + trait Trait { + fn method(&self) {} + } +} -use library::{Alias, Generic, Type}; +use impl nested::Trait for Type {} +impl nested::Trait for Type2 {} -assert_ne!(TypeId::of::(), TypeId::of::>()); +Type.method(); // Compiles. +Type2.method(); // error[E0599]: no method named `method` found for struct `Type2` in the current scope ``` -Here, the scoped implementation `use impl Trait for Type {}` **is** accounted for as it is captured into the type identity of `Alias`. +This also equally (importantly) applies to scoped implementations imported from elsewhere. -Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. If it can ensure that that is the case, a (different!) warning could in theory still be shown here. +[Method calls]: https://doc.rust-lang.org/book/ch05-03-method-syntax.html#method-syntax -### Global trait implementation available -[global-trait-implementation-available]: #global-trait-implementation-available +## Scoped implementations do not implicitly bring the trait into scope -Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that *at all* shadow another scoped implementation. +This so that no method calls on other types become ambiguous: -(Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) +```rust +struct Type; +struct Type2; -It would make sense to let the definitions and also alternatively specific global implementations of traits with high implementation stability requirements like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. +mod nested { + trait Trait { + fn method(&self) {} + } -### Self-referential bound of scoped implementation + trait Trait2 { + fn method(&self) {} + } +} -```rust -trait Foo { } +use nested::Trait2; +impl Trait2 for Type {} +impl Trait2 for Type2 {} -use impl Foo for T where T: Foo { } - --------- ^^^^^^ +use impl nested::Trait for Type {} +impl nested::Trait for Type2 {} + +Type.method(); // Compiles, binds to scoped implementation of `Trait`. +Type2.method(); // Compiles, binds to global implementation of `Trait2`. ``` -A Rust developer may want to write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. +(If `Trait` was not yet globally implemented for `Type2`, and `Trait` and `Type2` were defined in other crates, then bringing `Trait` into scope here could introduce instability towards that implementation later being added in one of those crates.) -The warning should explain that and why the bound is impossible to satisfy. +## Shadowing with different bounds -### Private supertrait implementation required by public implementation -[private-supertrait-implementation-required-by-public-implementation]: #private-supertrait-implementation-required-by-public-implementation +Scoped implementations may have different bounds compared to an implementation they (partially) shadow. The compiler will attempt to satisfy those bounds, but if they are not satisfied, then the other implementation is not shadowed for that set of generic type parameters and no additional warning or error is raised. -Consider the following code: +(Warnings for e.g. unused scoped implementations and scoped implementations that only shadow a covering global implementation are still applied as normal. It's just that partial shadowing with different bounds is likely a common use-case in macros.) ```rust -pub struct Type; +struct Type1; +struct Type2; -use impl PartialEq for Type { - // ... +trait Trait1 { + fn trait1() { + println!("1"); + } } +impl Trait1 for T {} // <-- -pub use impl Eq for Type {} +trait Trait2 { + fn trait2() { + println!("2"); + } +} +impl Trait2 for Type2 {} // <-- + +trait Say { + fn say(); +} +impl Say for T +where + T: Trait1, // <-- +{ + fn say() { + T::trait1(); + } +} + +{ + use impl Say for T + where + T: Trait2 // <-- + { + fn say() { + T::trait2(); + } + } + + Type1::say(); // 1 + Type2::say(); // 2 +} ``` -Here, the public implementation relies strictly on the private implementation to also be available. This means it effectively cannot be imported in `use`-declarations outside this module. +## No priority over type-associated methods -See also the error [incompatible-or-missing-supertrait-implementation]. +Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. -### Public implementation of private trait/on private type +## Coercion to trait objects -The code +Due to the coercion into a trait object in the following code, the scoped implementation becomes attached to the value through the pointer meta data. This means it can then be called from other scopes: ```rust -struct Type; -trait Trait {} +use std::fmt::{self, Display, Formatter}; -pub use impl Trait for Type {} - ^^^^^ ^^^^ +fn function() -> &'static dyn Display { + use impl Display for () { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "scoped") + } + } + + &() +} + +println!("{}", function()); // "scoped" ``` -should produce two distinct warnings similarly to those for private items in public signatures, as the limited visibilities of `Type` and `Trait` independently prevent the implementation from being imported in modules for which it is declared as visible. +This behaves exactly as a global implementation would. -### Scoped implementation is less visible than item/field it is captured in -[scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in +Note that the [`DynMetadata`]s of the reference returned above and one that uses the global implementation would compare as distinct even if both are "`&()`". -The code +[`DynMetadata`]: https://doc.rust-lang.org/stable/core/ptr/struct.DynMetadata.html -```rust -pub struct Type; -pub struct Generic(U, V); +## Interaction with return-position `impl Trait` -trait Trait {} // <-- Visibility of the trait doesn't matter for *this* warning. +Consider the following functions: -use impl Trait for Type {} ------------------------ +```rust +trait Trait {} -pub type Alias = Generic; - ^^^^ ^^^^ +fn function() -> impl Trait { + use impl Trait for () {} -pub fn function(value: Generic) -> Generic { - ^^^^ ^^^^ ^^^^ ^^^^ - value + () // Binds on trailing `()`-expression. } -pub struct Struct { - private: Generic, // This is fine. - pub public: Generic, - ^^^^ ^^^^ +fn function2() -> impl Trait { + use impl Trait for () {} + + {} // Binds on trailing `{}`-block used as expression. } ``` -should produce eight warnings (or four/three warnings with multiple primary spans each, if possible). The warning should explain that the type can't be referred to by fully specified name outside the crate/module and that the implementation may be callable from code outside the crate/module. - -If the binding is specified via inline *implementation environment*, then the warning should show up on the `Trait in module` span instead. +In this case, the returned opaque types use the respective inner scoped implementation, as it binds on the `()` expression. -Note that as with other private-in-public warnings, replacing +The following functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: ```rust -use impl Trait for Type {} -``` +trait Trait {} -with +fn function() -> impl Trait { + ^^^^^^^^^^ + use impl Trait for () {} + --------------------- -```rust -mod nested { - use super::{Trait, Type}; - pub use impl Trait for Type {} + // Cannot bind on implicit `()` returned by function body without trailing *Expression*. } -use nested::{impl Trait for Type}; -``` -in the code sample above should silence the warning. +fn function2() -> impl Trait { + ^^^^^^^^^^ + use impl Trait for () {} + --------------------- -In some cases, adding `as Trait in ::` to the generic type argument could be suggested as quick-fix, though generally it's better to fix this warning by moving the scoped implementation into a nested scope or moving it into a module and importing it into nested scopes as needed. + return; // Cannot bind on `return` without expression. + ------- +} +``` -> This warning can't be suppressed for private traits because the presence of their scoped implementation on a generic type parameter still affects the `TypeId` of the capturing generic, which here is visible outside of the discretising module. +(The errors should ideally also point at the scoped implementations here with a secondary highlight, and suggest stating the return value explicitly.) -### Imported implementation is less visible than item/field it is captured in -[imported-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #imported-implementation-is-less-visible-than-itemfield-it-is-captured-in - -This occurs under the same circumstances as above, except that +The binding must be consistent: ```rust trait Trait {} -use impl Trait for Type {} -``` - -is replaced with -```rust -use a_crate::{ - Trait, - impl Trait for Type, -}; +fn function() -> impl Trait { + // error: Inconsistent implementation of opaque return type. + if true { + use impl Trait for () {} + return (); + ---------- + } else { + use impl Trait for () {} + return (); + ^^^^^^^^^^ + } +} ``` -(where here the implementation import is subsetting a blanket import, but that technicality isn't relevant. What matters is that the implementation is from another crate). - -If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in that public signature or item?). - -## Errors - -### Global implementation of trait where global implementation of supertrait is shadowed - -A trait cannot be implemented globally for a discrete type in a scope where the global implementation of any of its supertraits is shadowed on that type. +This function *does* compile, as the outer scoped `impl Trait for ()` is bound on the `if`-`else`-expression as a whole. ```rust -struct Type; - -trait Super {} -trait Sub: Super {} - -impl Super for Type {} +trait Trait {} -{ - use impl Super for Type {} - ----------------------- // <-- Scoped implementation defined/imported here. +fn function() -> impl Trait { + use impl Trait for () {} - impl Sub for Type {} - ^^^^^^^^^^^^^^^^^ //<-- error: global implementation of trait where global implementation of supertrait is shadowed + if true { + use impl Trait for () {} // warning: unused scoped implementation + () + } else { + use impl Trait for () {} // warning: unused scoped implementation + () + } } ``` -### Negative scoped implementation -[negative-scoped-implementation]: #negative-scoped-implementation - -This occurs on all negative scoped implementations. Negative scoped implementations can be parsed, but are rejected shortly after macros are applied. +This compiles because the end of the function is not reachable: ```rust -struct Type; trait Trait {} -impl Trait for Type {} - -{ - use impl !Trait for Type {} - ^^^^^^^^^^^^^^^^^^^^^^^^ error: negative scoped implementation +fn function() -> impl Trait { + { + use impl Trait for () {} + return (); // Explicit `return` is required to bind in the inner scope. + } } ``` -### Incompatible or missing supertrait implementation -[incompatible-or-missing-supertrait-implementation]: #incompatible-or-missing-supertrait-implementation +## Static interception of dynamic calls -Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those being in scope too (to avoid perceived and hard to reason-about inconsistencies). +As a consequence of binding outside of generic contexts, it *is* possible to statically wrap *specific* trait implementations on *concrete* types. This includes the inherent implementations on trait objects: ```rust -struct Type; -trait Super {} -trait Sub: Super {} +use std::fmt::{self, Display, Formatter}; -impl Super for Type {} +{ + use impl Display for dyn Display { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Restore binding to inherent global implementation within this function. + use ::{impl Display for dyn Display}; -mod nested { - pub use impl Super for Type {} - pub use impl Sub for Type {} -} + write!(f, "Hello! ")?; + d.fmt(f)?; + write!(f, " See you!") + } + } -use nested::{impl Sub for Type}; - ^^^^^^^^^^^^^^^^^ error: incompatible supertrait implementation + let question = "What's up?"; // &str + println!("{question}"); // "What's up?" + + let question: &dyn Display = &question; + println!("{question}"); // Binds to the scoped implementation; "Hello! What's up? See you!" +} ``` -Rustc should suggest to import the required scoped implementation, if possible. +## Warnings -See also the warning [private-supertrait-implementation-required-by-public-implementation]. See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] for a potential way to improve the ergonomics here. +### Unused scoped implementation +[unused-scoped-implementation]: #unused-scoped-implementation -### Scoped implementation of external sealed trait -[scoped-implementation-of-external-sealed-trait]: #scoped-implementation-of-external-sealed-trait +Scoped implementations and `use`-declarations of such (including those written as *ImplEnvironmentEntry*) receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. -Given crate `a`: +(rust-analyzer should suggest removing any unused `use`-declarations as fix in either case.) + +An important counter-example: + +Filename: library/src/lib.rs ```rust -mod private { - pub trait Sealing {} -} -use private::Sealing; +pub struct Type; +pub struct Generic; -pub trait Sealed: Sealing {} +pub trait Trait {} +use impl Trait for Type {} -pub use impl Sealed for T {} // Ok. +pub type Alias = Generic; ``` -And crate `b`: - +Filename: main.rs ```rust -use a::{ - Sealed, - impl Sealed for usize, // Ok. -}; +use std::any::TypeId; -use impl Sealed for () {} // Error. - ^^^^^^ +use library::{Alias, Generic, Type}; + +assert_ne!(TypeId::of::(), TypeId::of::>()); ``` -Crate `b` cannot define scoped implementations of the external sealed trait `Sealed`, but can still import them. +Here, the scoped implementation `use impl Trait for Type {}` **is** accounted for as it is captured into the type identity of `Alias`. -See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. +Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. If it can ensure that that is the case, a (different!) warning could in theory still be shown here. -## Behaviour change/Warning: `TypeId` of implementation-aware generic discretised using generic type parameters -[behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters +### Global trait implementation available +[global-trait-implementation-available]: #global-trait-implementation-available -As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for [implementation-aware-generics] they discretise using type parameters may be called on the same value instance. +Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that *at all* shadow another scoped implementation. -Notably, this affects `TypeId::of::()` in implementations with most generic targets, but not in unspecific blanket implementations on the type parameter itself. +(Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) -This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. +It would make sense to let the definitions and also alternatively specific global implementations of traits with high implementation stability requirements like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. -In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show only the expected `TypeId` distinction. +### Self-referential bound of scoped implementation -Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can be used, as tuples are [implementation-invariant-generics]. +```rust +trait Foo { } -## Resolution on generic type parameters -[resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters +use impl Foo for T where T: Foo { } + --------- ^^^^^^ +``` -Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `use impl Trait for T`. +A Rust developer may want to write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. -However, a blanket implementation can only be bound on a generic type parameter iff its bounds are fully covered by the generic type parameter's bounds and other available trait implementations on the generic type parameter, in the same way as this applies for global implementations. +The warning should explain that and why the bound is impossible to satisfy. -## Method resolution to scoped implementation without trait in scope +### Private supertrait implementation required by public implementation +[private-supertrait-implementation-required-by-public-implementation]: #private-supertrait-implementation-required-by-public-implementation -[Method calls] can bind to scoped implementations even when the declaring trait is not separately imported. For example: +Consider the following code: ```rust -struct Type; -struct Type2; +pub struct Type; -mod nested { - trait Trait { - fn method(&self) {} - } +use impl PartialEq for Type { + // ... } -use impl nested::Trait for Type {} -impl nested::Trait for Type2 {} - -Type.method(); // Compiles. -Type2.method(); // error[E0599]: no method named `method` found for struct `Type2` in the current scope +pub use impl Eq for Type {} ``` -This also equally (importantly) applies to scoped implementations imported from elsewhere. +Here, the public implementation relies strictly on the private implementation to also be available. This means it effectively cannot be imported in `use`-declarations outside this module. -[Method calls]: https://doc.rust-lang.org/book/ch05-03-method-syntax.html#method-syntax +See also the error [incompatible-or-missing-supertrait-implementation]. -## Scoped implementations do not implicitly bring the trait into scope +### Public implementation of private trait/on private type -This so that no method calls on other types become ambiguous: +The code ```rust struct Type; -struct Type2; - -mod nested { - trait Trait { - fn method(&self) {} - } - - trait Trait2 { - fn method(&self) {} - } -} +trait Trait {} -use nested::Trait2; -impl Trait2 for Type {} -impl Trait2 for Type2 {} +pub use impl Trait for Type {} + ^^^^^ ^^^^ +``` -use impl nested::Trait for Type {} -impl nested::Trait for Type2 {} +should produce two distinct warnings similarly to those for private items in public signatures, as the limited visibilities of `Type` and `Trait` independently prevent the implementation from being imported in modules for which it is declared as visible. -Type.method(); // Compiles, binds to scoped implementation of `Trait`. -Type2.method(); // Compiles, binds to global implementation of `Trait2`. -``` +### Scoped implementation is less visible than item/field it is captured in +[scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in -(If `Trait` was not yet globally implemented for `Type2`, and `Trait` and `Type2` were defined in other crates, then bringing `Trait` into scope here could introduce instability towards that implementation later being added in one of those crates.) +The code -## Shadowing with different bounds +```rust +pub struct Type; +pub struct Generic(U, V); -Scoped implementations may have different bounds compared to an implementation they (partially) shadow. The compiler will attempt to satisfy those bounds, but if they are not satisfied, then the other implementation is not shadowed for that set of generic type parameters and no additional warning or error is raised. +trait Trait {} // <-- Visibility of the trait doesn't matter for *this* warning. -(Warnings for e.g. unused scoped implementations and scoped implementations that only shadow a covering global implementation are still applied as normal. It's just that partial shadowing with different bounds is likely a common use-case in macros.) +use impl Trait for Type {} +----------------------- -```rust -struct Type1; -struct Type2; +pub type Alias = Generic; + ^^^^ ^^^^ -trait Trait1 { - fn trait1() { - println!("1"); - } +pub fn function(value: Generic) -> Generic { + ^^^^ ^^^^ ^^^^ ^^^^ + value } -impl Trait1 for T {} // <-- -trait Trait2 { - fn trait2() { - println!("2"); - } +pub struct Struct { + private: Generic, // This is fine. + pub public: Generic, + ^^^^ ^^^^ } -impl Trait2 for Type2 {} // <-- +``` -trait Say { - fn say(); -} -impl Say for T -where - T: Trait1, // <-- -{ - fn say() { - T::trait1(); - } -} +should produce eight warnings (or four/three warnings with multiple primary spans each, if possible). The warning should explain that the type can't be referred to by fully specified name outside the crate/module and that the implementation may be callable from code outside the crate/module. -{ - use impl Say for T - where - T: Trait2 // <-- - { - fn say() { - T::trait2(); - } - } +If the binding is specified via inline *implementation environment*, then the warning should show up on the `Trait in module` span instead. - Type1::say(); // 1 - Type2::say(); // 2 +Note that as with other private-in-public warnings, replacing + +```rust +use impl Trait for Type {} +``` + +with + +```rust +mod nested { + use super::{Trait, Type}; + pub use impl Trait for Type {} } +use nested::{impl Trait for Type}; ``` -## No priority over type-associated methods +in the code sample above should silence the warning. -Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. +In some cases, adding `as Trait in ::` to the generic type argument could be suggested as quick-fix, though generally it's better to fix this warning by moving the scoped implementation into a nested scope or moving it into a module and importing it into nested scopes as needed. -## Coercion to trait objects +> This warning can't be suppressed for private traits because the presence of their scoped implementation on a generic type parameter still affects the `TypeId` of the capturing generic, which here is visible outside of the discretising module. -Due to the coercion into a trait object in the following code, the scoped implementation becomes attached to the value through the pointer meta data. This means it can then be called from other scopes: +### Imported implementation is less visible than item/field it is captured in +[imported-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #imported-implementation-is-less-visible-than-itemfield-it-is-captured-in -```rust -use std::fmt::{self, Display, Formatter}; +This occurs under the same circumstances as above, except that -fn function() -> &'static dyn Display { - use impl Display for () { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "scoped") - } - } +```rust +trait Trait {} +use impl Trait for Type {} +``` - &() -} +is replaced with -println!("{}", function()); // "scoped" +```rust +use a_crate::{ + Trait, + impl Trait for Type, +}; ``` -This behaves exactly as a global implementation would. +(where here the implementation import is subsetting a blanket import, but that technicality isn't relevant. What matters is that the implementation is from another crate). -Note that the [`DynMetadata`]s of the reference returned above and one that uses the global implementation would compare as distinct even if both are "`&()`". +If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in that public signature or item?). -[`DynMetadata`]: https://doc.rust-lang.org/stable/core/ptr/struct.DynMetadata.html +## Errors -## Interaction with return-position `impl Trait` +### Global implementation of trait where global implementation of supertrait is shadowed -Consider the following functions: +A trait cannot be implemented globally for a discrete type in a scope where the global implementation of any of its supertraits is shadowed on that type. ```rust -trait Trait {} +struct Type; -fn function() -> impl Trait { - use impl Trait for () {} +trait Super {} +trait Sub: Super {} - () // Binds on trailing `()`-expression. -} +impl Super for Type {} -fn function2() -> impl Trait { - use impl Trait for () {} +{ + use impl Super for Type {} + ----------------------- // <-- Scoped implementation defined/imported here. - {} // Binds on trailing `{}`-block used as expression. + impl Sub for Type {} + ^^^^^^^^^^^^^^^^^ //<-- error: global implementation of trait where global implementation of supertrait is shadowed } ``` -In this case, the returned opaque types use the respective inner scoped implementation, as it binds on the `()` expression. +### Negative scoped implementation +[negative-scoped-implementation]: #negative-scoped-implementation -The following functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: +This occurs on all negative scoped implementations. Negative scoped implementations can be parsed, but are rejected shortly after macros are applied. ```rust +struct Type; trait Trait {} -fn function() -> impl Trait { - ^^^^^^^^^^ - use impl Trait for () {} - --------------------- - - // Cannot bind on implicit `()` returned by function body without trailing *Expression*. -} - -fn function2() -> impl Trait { - ^^^^^^^^^^ - use impl Trait for () {} - --------------------- +impl Trait for Type {} - return; // Cannot bind on `return` without expression. - ------- +{ + use impl !Trait for Type {} + ^^^^^^^^^^^^^^^^^^^^^^^^ error: negative scoped implementation } ``` -(The errors should ideally also point at the scoped implementations here with a secondary highlight, and suggest stating the return value explicitly.) +### Incompatible or missing supertrait implementation +[incompatible-or-missing-supertrait-implementation]: #incompatible-or-missing-supertrait-implementation -The binding must be consistent: +Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those being in scope too (to avoid perceived and hard to reason-about inconsistencies). ```rust -trait Trait {} +struct Type; +trait Super {} +trait Sub: Super {} -fn function() -> impl Trait { - // error: Inconsistent implementation of opaque return type. - if true { - use impl Trait for () {} - return (); - ---------- - } else { - use impl Trait for () {} - return (); - ^^^^^^^^^^ - } +impl Super for Type {} + +mod nested { + pub use impl Super for Type {} + pub use impl Sub for Type {} } + +use nested::{impl Sub for Type}; + ^^^^^^^^^^^^^^^^^ error: incompatible supertrait implementation ``` -This function *does* compile, as the outer scoped `impl Trait for ()` is bound on the `if`-`else`-expression as a whole. +Rustc should suggest to import the required scoped implementation, if possible. -```rust -trait Trait {} +See also the warning [private-supertrait-implementation-required-by-public-implementation]. See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] for a potential way to improve the ergonomics here. -fn function() -> impl Trait { - use impl Trait for () {} +### Scoped implementation of external sealed trait +[scoped-implementation-of-external-sealed-trait]: #scoped-implementation-of-external-sealed-trait - if true { - use impl Trait for () {} // warning: unused scoped implementation - () - } else { - use impl Trait for () {} // warning: unused scoped implementation - () - } +Given crate `a`: + +```rust +mod private { + pub trait Sealing {} } +use private::Sealing; + +pub trait Sealed: Sealing {} + +pub use impl Sealed for T {} // Ok. ``` -This compiles because the end of the function is not reachable: +And crate `b`: ```rust -trait Trait {} +use a::{ + Sealed, + impl Sealed for usize, // Ok. +}; -fn function() -> impl Trait { - { - use impl Trait for () {} - return (); // Explicit `return` is required to bind in the inner scope. - } -} +use impl Sealed for () {} // Error. + ^^^^^^ ``` -## Static interception of dynamic calls +Crate `b` cannot define scoped implementations of the external sealed trait `Sealed`, but can still import them. -As a consequence of binding outside of generic contexts, it *is* possible to statically wrap *specific* trait implementations on *concrete* types. This includes the inherent implementations on trait objects: +See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. -```rust -use std::fmt::{self, Display, Formatter}; +## Behaviour change/Warning: `TypeId` of implementation-aware generic discretised using generic type parameters +[behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters -{ - use impl Display for dyn Display { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Restore binding to inherent global implementation within this function. - use ::{impl Display for dyn Display}; +As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for [implementation-aware-generics] they discretise using type parameters may be called on the same value instance. - write!(f, "Hello! ")?; - d.fmt(f)?; - write!(f, " See you!") - } - } +Notably, this affects `TypeId::of::()` in implementations with most generic targets, but not in unspecific blanket implementations on the type parameter itself. - let question = "What's up?"; // &str - println!("{question}"); // "What's up?" +This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. - let question: &dyn Display = &question; - println!("{question}"); // Binds to the scoped implementation; "Hello! What's up? See you!" -} -``` +In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show only the expected `TypeId` distinction. + +Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can be used, as tuples are [implementation-invariant-generics]. # Drawbacks [drawbacks]: #drawbacks @@ -1704,7 +1781,7 @@ There are a few ways to mitigate this, but they all have significant drawbacks: ## Unexpected behaviour of `TypeId::of::()` in implementations on generics in the consumer-side presence of scoped implementations and `transmute` -As explained in [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s as e.g. for the `HashSet`s inside the type-erased value-keyed collection like the `ErasedHashSet` example in the [typeid-of-generic-type-parameters-opaque-types] section. +As explained in [rustdoc-documentation-changes], [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s as e.g. for the `HashSet`s inside the type-erased value-keyed collection like the `ErasedHashSet` example in the [typeid-of-generic-type-parameters-opaque-types] section. This use case appears to be niche enough in Rust to not have an obvious example on crates.io, but see [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a lint that aims to mitigate issues in this regard and could be used to survey potential issues. From 7af6148b9d446149f621f75b34a7d26d09f5d99c Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 12 May 2024 16:09:39 +0200 Subject: [PATCH 13/27] Renamed the file with the issue number and updated Start Date and RFC PR link --- ....md => 3634-scoped-impl-trait-for-type.md} | 6824 ++++++++--------- 1 file changed, 3412 insertions(+), 3412 deletions(-) rename text/{0000-scoped-impl-trait-for-type.md => 3634-scoped-impl-trait-for-type.md} (97%) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md similarity index 97% rename from text/0000-scoped-impl-trait-for-type.md rename to text/3634-scoped-impl-trait-for-type.md index 78077fab226..2db03456864 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1,3412 +1,3412 @@ -- Feature Name: `scoped_impl_trait_for_type` -- Start Date: (fill me in with today's date, YYYY-MM-DD) -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) - -# Summary -[summary]: #summary - -This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other item scopes (including other crates), and differentiates type identity of most generics by which scoped trait implementations are available to each discretised generic type parameter (also adding syntax to specify differences to these captured *implementation environments* directly on generic type arguments). - -This (along with some details specified below) enables any crate to - -- locally, in item scopes, implement nearly any trait for any expressible type, -- publish these trivially composable implementations to other crates, -- import and use such implementations safely and seamlessly and -- completely ignore this feature when it's not needed\*. - -\* aside from one hopefully very obscure `TypeId` edge case that's easy to accurately lint for. - -This document uses "scoped implementation" and "scoped `impl Trait for Type`" interchangeably. As such, the former should always be interpreted to mean the latter below. - -# Motivation -[motivation]: #motivation - -While orphan rules regarding trait implementations are necessary to allow crates to add features freely without fear of breaking dependent crates, they limit the composability of third party types and traits, especially in the context of derive macros. - -For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between derive macro crates. - -Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions in order to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. - -Another pain point are sometimes missing `Into<>`-conversions when propagating errors with `?`, even though one external residual (payload) type may (sometimes *contextually*) be cleanly convertible into another. As-is, this usually requires a custom intermediary type, or explicit conversion using `.map_err(|e| …)` (or an equivalent function/extension trait). If an appropriate `From<>`-conversion can be provided *in scope*, then just `?` can be used. - -This RFC aims to address these pain points by creating a new path of least resistance that is easy to use and very easy to teach, intuitive to existing Rust-developers, readable without prior specific knowledge, discoverable as needed, has opportunity for rich tooling support in e.g. rust-analyzer and helpful error messages, is quasi-perfectly composable including decent re-use of composition, improves maintainability and (slightly) robustness to major-version dependency changes compared to newtype wrappers, and does not restrict crate API evolution, compromise existing coherence rules or interfere with future developments like specialisation. Additionally, it allows the implementation of more expressive (but no less explicit) extension APIs using syntax traits like in the `PartialEq<>`-example below, without complications should these traits be later implemented in the type-defining crate. - -For realistic examples of the difference this makes, please check the [rationale-and-alternatives] section. - -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation - -Scoped `impl Trait for Type` can be introduced in The Book alongside global trait implementations and mentioned in the standard library documentation examples. - -For example, the following changes could be made: - -## **10.2.** Traits: Defining Shared Behavior - -The following sections are added after [Implementing a Trait on a Type]: - -[Implementing a Trait on a Type]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type - -### Scoped Implementation of a Trait on a Type - -Independently of implementing a trait on a type or set of types *globally*, it's possible to do so only for the current scope, by adding the `use` keyword: - -```rust -use impl Trait for Type { - // ... -} -``` - -With the exception of very few traits related to language features, you can implement any visible trait on any visible type this way, even if both are defined in other crates. - -In other words: The *orphan rule* does not apply to scoped implementations. Instead, item shadowing is used to determine which implementation to use. - -*Scoped implementations are intended mainly as compatibility feature*, to let third party crates provide glue code for other crate combinations. To change the behaviour of an instance or a set of instances from their default, consider using [the newtype pattern] instead. - -[`Hash`]: https://doc.rust-lang.org/stable/std/hash/trait.Hash.html -[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html -[`Eq`]: https://doc.rust-lang.org/stable/std/cmp/trait.Eq.html -[`PartialOrd`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialOrd.html -[`Ord`]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html - -[`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html -[`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html - -[the newtype pattern]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types - -### Publishing and Importing Scoped Implementations - -You can also publish a scoped implementation further by adding a visibility before `use` ...: - -```rust -pub use impl Trait for Type { - // ... -} - -pub use unsafe impl UnsafeTrait for Type { - // ... -} -``` - -... and import it into other scopes: - -```rust -use other_module::{ - impl Trait for Type, - impl UnsafeTrait for Type, -}; -``` - -Note that the scoped implementation of `UnsafeTrait` is imported without the `unsafe` keyword. **It is the implementing crate's responsibility to ensure the exported `unsafe` implementation is sound everywhere it is visible!** - -Generic parameters, bounds and `where`-clauses can be used as normal in each of these locations, though you usually have to brace `impl Trait for Type where /*...*/` individually in `use`-declarations. - -You can import a subset of a generic implementation, by narrowing bounds or replacing type parameters with concrete types in the `use`-declaration. - -Global implementations can be imported from the root namespace, for example to shadow a scoped implementation: - -```rust -use ::{impl Trait for Type}; -``` - -### Scoped implementations and generics -[scoped-implementations-and-generics]: #scoped-implementations-and-generics - -Scoped implementations are resolved on most generics' type arguments where those are specified, and become part of the (now less generic) host type's identity: - -```rust -#[derive(Default)] -struct Type(T); - -trait Trait { - fn trait_fn(); -} - -impl Type { - fn type_fn() { - T::trait_fn(); - } -} - -mod nested { - use super::{Trait, Type}; - - use impl Trait for () { - fn trait_fn() { - println!("nested"); - } - } - - pub type Alias = Type<()>; -} -use nested::Alias; - -fn main() { - Alias::type_fn(); // "nested" - - // Type::<()>::type_fn(); - // ^^^^^^^ error[E0599]: the function or associated item `type_fn` exists for struct `Type<()>`, but its trait bounds were not satisfied - - // let t: Type<()> = Alias::default(); - // ^^^^^^^^^ error[E0308]: mismatched types - - let t: Type<() as Trait in nested> = Alias::default(); -} -``` - -This works equally not just for type aliases but also fields, `let`-bindings and also where generic type parameters are inferred automatically from expressions (for example to call a constructor). - -Note that some utility types, like references, tuples, `Option`, `Result` and closure traits, do not bind implementations eagerly but only when used to specify another generic. You can find a list of these types in the reference. (← i.e. "insert link here".) - -## **19.2.** Advanced Traits - -The section [Using the Newtype Pattern to Implement External Traits on External Types] is updated to mention scoped implementations, to make them more discoverable when someone arrives from an existing community platform answer regarding orphan rule workarounds. It should also mention that newtypes are preferred over scoped implementations when use of the type is semantically different, to let the type checker distinguish it from others. - -[Using the Newtype Pattern to Implement External Traits on External Types]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types - -A new section is added: - -### Using Scoped Implementations to Implement External Traits on External Types -[using-scoped-implementations-to-implement-external-traits-on-external-types]: #using-scoped-implementations-to-implement-external-traits-on-external-types - -Since scoped implementations allow crates to reusably implement external traits on external types, they can be used to provide API extensions that make use of syntactic sugar. For example: - -Filename: fruit-comparer/src/lib.rs - -```rust -use apples::Apple; -use oranges::Orange; - -pub use impl PartialEq for Apple { - fn eq(&self, other: &Orange) -> bool { - todo!("Figure out how to compare apples and oranges.") - } -} - -pub use impl PartialEq for Orange { - fn eq(&self, other: &Orange) -> bool { - todo!("Figure out how to compare oranges and apples.") - } -} -``` - -Filename: src/main.rs - -```rust -use apples::Apple; -use oranges::Orange; - -use fruit_comparer::{ - impl PartialEq for Apple, - impl PartialEq for Orange, -}; - -fn main() { - let apple = Apple::new(); - let orange = Orange::new(); - - // Compiles: - dbg!(apple == orange); - dbg!(orange == apple); -} -``` - -If the type whose API was extended this way later gains the same trait inherently, that is not a problem as the consuming code continues to use `fruit_comparer`'s scoped implementation. However, a warning ([global-trait-implementation-available]) is shown by default to alert the maintainers of each crate of the covering global implementation. - -Be careful about literal coercion when using generic traits this way! For example, if a scoped implementation of `Index` is used and a global `Index` implementation is added later on the same type, the compiler will *not* automatically decide which to use for integer literal indices between these two. - -## Rustdoc documentation changes -[rustdoc-documentation-changes]: #rustdoc-documentation-changes - -### `use` and `impl` keywords - -The documentation pages [for the `use` keyword] and [for the `impl` keyword] are adjusted to (very) briefly demonstrate the respective scoped use of `impl Trait for Type`. - -[for the `use` keyword]: https://doc.rust-lang.org/stable/std/keyword.use.html -[for the `impl` keyword]: https://doc.rust-lang.org/stable/std/keyword.impl.html - -### `TypeId` - -The page for [`TypeId`] gains two sections with the following information: - -```markdown -# `TypeId` and scoped implementations - -To make sure that that are no mix-ups between, for example, `HashSet` and `HashSet`, any such difference implies distinct `TypeId`s between such discretised generics (and that the types are not mutually assignable). - -This also affects trait-bounded generic type parameters: If `T` is bounded on `Hash`, then `TypeId::of::()` results in distinct `TypeId`s in that context depending on the captured implementation. - -However, note that `TypeId::of::()` and `TypeId::of::()` are always equivalent for one definition of `T`, as `TypeId::of`'s implementation does **not** have a `T: Hash` bound! - -For convenience (so that their values are easily interchangeable across crates), the following types ignore scoped implementations *on* their generic arguments in terms of *their own* type identity: […] - -Despite this, differences in *type arguments'* discrete identities (for example from scoped implementations captured *in* them) distinguish the type identity of *all* discretised generics they appear in. - -# `TypeId::of::()` may change for values of generics - -To make type-erased collections sound and unsurprising by default, it's sound to transmute between instances of an external generic type that differ only in their captured scoped implementations, **iff and only iff** no inconsistency is ever observed by bounds (including across separate function calls). - -However, this poses a problem: `TypeId::of::()` (just like the written-out form of any type that doesn't ignore scoped implementations) takes *all* differences in captured implementation environments into account, not just those relevant to trait bounds. - -As such, prefer `TypeId::of::()` whenever possible in order to make only the distinctions you require. You can use tuples to combine multiple type parameters without over-distinguishing: `TypeId::of::<(S, T)>()` -``` - -> These rules and the reasons for them are explained in detail in the [reference-level-explanation] below, as well as in [logical-consistency] as part of [rationale-and-alternatives]. It may be a good idea to link to similar longer explanations from the standard library docs above, even if just as "See also:"-style references for further reading. - -> The `[…]`-placeholder stands for a list of links to each implementation-invariant generic's documentation. - -See also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a way to narrowly alert users of this when relevant, and to scan for the potential impact of these changes ahead of time. - -[`TypeId`]: https://doc.rust-lang.org/stable/std/any/struct.TypeId.html - -### Implementation-invariant generics - -The pages for [implementation-invariant-generics] gain a section similar to the following: - -```markdown -# Implementation-invariant generic - -This type does not by itself capture scoped implementation environments when discretised. See [`TypeId` and scoped implementations] for more information. -``` - -where ``[`TypeId` and scoped implementations]`` is a link to the section added to the `TypeId` page above. - -### `mem::transmute` - -The page for [`transmute`] gains a section with the following information: - -```markdown -# `transmute` and scoped implementations - -It is sound to transmute between discretised generic types that differ only in their captured scoped implementation environments, **but only iff** such differences are **never** observed by bounds on their implementation, including functions that imply such by being implemented for discrete instances of the generic. -``` - -> As far as I can tell, this is only practically relevant for certain kinds of type-erasing collections, like type-erasing hash maps and B-trees, of which I couldn't find any examples on crates.io. -> -> Any straightforward implementations of such collections should also at worst exhibit only unexpected behaviour when consumed in the presence of scoped implementations, rather than unsoundness. - -[`transmute`]: https://doc.rust-lang.org/stable/std/mem/fn.transmute.html - -## Changes to The Rustonomicon - -The page on [Transmutes] gains the following warning in addition to the existing ones: - -```markdown -- It is unsound to change [captured scoped implementations] via transmute for any external type if this change ever causes a contradiction observable by the transmuted value's implementation. - - This can happen due to bounds on called functions and/or because a called function is implemented for a specific type discretised from the generic. -``` - -`[captured scoped implementations]` should link to documentation introducing scoped `impl Trait for Type`. - -[Transmutes]: https://doc.rust-lang.org/stable/nomicon/transmutes.html - -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation - -## Grammar changes -[grammar-changes]: #grammar-changes - -The core Rust language grammar is extended as follows: - -- [*TraitImpl*]'s definition is prepended with (*Visibility*? `use`)? and refactored for partial reuse to arrive at - - > *TraitImpl* : - >   **(*Visibility*? `use`)?** `unsafe`? ***TraitCoverage*** - >   `{` - >    *InnerAttribute*\* - >    *AssociatedItem*\* - >   `}` - > - > ***TraitCoverage*** : - >   ***TraitCoverageNoWhereClause*** - >   *WhereClause*? - > - > ***TraitCoverageNoWhereClause*** : - >   `impl` *GenericParams*? `!`? *TypePath* `for` *Type* - - where a trait implementation with that `use`-prefix provides the implementation *only* as item in the containing item scope. - - (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) - - **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [type-parameters-capture-their-implementation-environment]). - - [*TraitImpl*]: https://doc.rust-lang.org/reference/items/implementations.html?highlight=TraitImpl#implementations - -- [*UseTree*]'s definition is extended for importing scoped implementations by inserting the extracted *TraitCoverage* and *TraitCoverageNoWhereClause* rules as follows: - - > *UseTree* : - >   (*SimplePath*? `::`)? `*` - >   | (*SimplePath*? `::`)? `{` - >   ( - >    (**(**‍*UseTree* **| *TraitCoverageNoWhereClause*)** (`,` **(**‍*UseTree* **| *TraitCoverageNoWhereClause*)**)\* **(**`,` ***TraitCoverage*?)**?)? - >    **| *TraitCoverage*** - >   ) - >   `}` - >   | *SimplePath* (`as` (IDENTIFIER | `_`))? - - Allowing a trailing *TraitCoverage* with *WhereClause* in a braced list is intended for ergonomics, but rustfmt should brace it individually by default, then append a trailing comma where applicable as usual. A '`,`' in the *WhereClause* here is not truly ambiguous because *WhereClauseItem*s contain '`:`', but allowing that ahead of others would likely be visually confusing and tricky to implement (requiring an arbitrarily long look-ahead). Alternatively to allowing a trailing *TraitCoverage* in mixed lists, an error similar to [E0178] could be emitted. - - [E0178]: https://doc.rust-lang.org/error_codes/E0178.html - - > Allowing unbraced imports like `use some_crate::impl Trait
for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy quite badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added for convenience, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced. - - Here, *TraitCoverage* imports the specified scoped `impl Trait for Type` for binding and conflict checks as if defined in the scope containing the `use`-declaration. The resulting visibility is taken from *UseDeclaration*, like with *SimplePath*-imported items. - - *TraitCoverage* must be fully covered by the scoped implementation visible in the source module. Otherwise, a compile-error occurs explaining the uncovered case (similarly to the current error(s) for missing trait implementations). - - ***TraitCoverage* may subset the source module's implementation** by having narrower bounds or using concrete types in place of one or more generic type parameters. This causes only the specified subset of the scoped implementation to be imported. - - Note that scoped implementations of `unsafe` traits are imported without `unsafe`. It is the exporting crate's responsibility to ensure a scoped implementation is sound everywhere it is visible. - - Other elements of the coverage must match the source module's implementation exactly, unless specified otherwise. - - [*UseTree*]: https://doc.rust-lang.org/reference/items/use-declarations.html?highlight=UseTree#use-declarations - -- [*TypeParam*], [*GenericArg*] and [*GenericArgsBinding*] are extended to accept *implementation environments* inline: - - > *TypeParam* : - >   IDENTIFIER ( `:` *TypeParamBounds*? )? ( `=` *Type* ***ImplEnvironment*?** )? - > - > *GenericArg* : - >   *Lifetime* | *Type* ***ImplEnvironment*?** | *GenericArgsConst* | *GenericArgsBinding* - > - > *GenericArgsBinding* : - >   IDENTIFIER `=` *Type* ***ImplEnvironment*?** - > - > ***ImplEnvironment* :** - > **  `as` ( *ImplEnvironmentEntry* ( `+` *ImplEnvironmentEntry* )\* `+`? )?** - > - > ***ImplEnvironmentEntry* :** - > **  (** - > **   *ForLifetimes*? *TypePath*** - > **   | ( *ForLifetimes*? *TypePath* )** - > **  )** - > **  `in` ( `::` | *SimplePath* )** - - When detecting conflicting implementations, the *ImplEnvironment* is treated as creating a distinct scope nested in its surrounding scope. Each resulting *implementation environment* must be conflict-free, but between them they *can* contain conflicting implementations. - - Even when an *ImplEnvironment* is added as above, the resulting *implementation environment* still captures scoped implementations from the surrounding scope for all traits that were not specified inline! A global implementation can be used explicitly by sourcing it from `::` instead of a module. - - For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit inline *implementation environments* should be allowed where no matching bound is present, but should produce an [unused-scoped-implementation] warning iff neither published nor used in the same crate (including for type identity distinction). - - > Whether inline *implementation environments* would inherit from each other is intentionally left unspecified, as identical types can't be nested without indirection, which ensures such a situation isn't relevant. - - [*TypeParam*]: https://doc.rust-lang.org/reference/items/generics.html?highlight=TypeParam#generic-parameters - [*GenericArg*]: https://doc.rust-lang.org/reference/paths.html?highlight=GenericArg#paths-in-expressions - [*GenericArgsBinding*]: https://doc.rust-lang.org/reference/paths.html?highlight=GenericArgsBinding#paths-in-expressions - -- Further type specification syntax is extended as follows: - - > *ParenthesizedType* : - >   `(` *Type* ***ImplEnvironment*?** `)` - > - > *TupleType* : - >   `(` `)` - >   | `(` ( *Type* ***ImplEnvironment*?** `,` )+ **(** *Type* ***ImplEnvironment*? )**? `)` - > - > *ArrayType* : - >   `[` *Type* ***ImplEnvironment*?** `;` *Expression* `]` - > - > *SliceType* : - >   `[` *Type* ***ImplEnvironment*?** `]` - - > Closure types are not extended with *ImplEnvironment* because *implementation environments* annotated on their parameters would never be effective. - > - > Extending *ParenthesizedType* this way is necessary to specify *implementation environments* for pointer types' generic type parameters, e.g. `&(Type as Trait in module)`. - -- [*QualifiedPathType*] is also extended for this purpose, but can additionally act as *implementation environment* scope that also affects the *implementation environment* of nested types, using a clause starting with `where`: - - > *QualifiedPathType* : - >   `<` *Type* ( `as` *TypePath* **(`in` (`::` | *SimplePath* ) )?** )? **( `where` ( *Type* *ImplEnvironment* `,` )\* ( *Type* *ImplEnvironment* )? )?** `>` - - The form `` is syntactic sugar for `<(Type as Trait in module) as Trait>`, to avoid repetition of potentially long traits. - - Implementations imported after `where` must be valid, but don't necessarily have to be relevant. - - > I am **not** confident that `where` is the right keyword here, but it seems like this best option among the already-existing ones. `use`-syntax feels far too verbose here. Maybe the above but with `using` or `with` in place of `where`? - - [*QualifiedPathType*]: https://doc.rust-lang.org/reference/paths.html?highlight=QualifiedPathType#qualified-paths - -## No scoped `impl Trait for Type` of auto traits, `Copy` and `Drop` - -Implementations of auto traits state guarantees about private implementation details of the covered type(s), which an external implementation can almost never do soundly. - -`Copy` is not an auto trait, but implementing it on a smart pointer like `Box` would immediately be unsound. As such, this trait must be excluded from all external implementations. - -Shadowing `Drop` for types that are `!Unpin` is similarly unsound without cooperation of the original crate (in addition to likely causing memory leaks in this and more cases). - -## No scoped `impl !Trait for Type` - -Any negative scoped implementation like for example - -```rust -use impl !Sync for Type {} -``` - -is syntactically valid, but rejected by the compiler with a specific error. (See [negative-scoped-implementation].) - -This also applies to `impl Trait`s in `use`-declarations (even though the items they would import cannot be defined anyway. Having a specific error saying that this *isn't possible* would be much clearer than one saying that the imported item doesn't exist). - -## No external scoped implementations of sealed traits -[no-external-scoped-implementations-of-sealed-traits]: #no-external-scoped-implementations-of-sealed-traits - -Consider this library crate: - -```rust -pub struct Generic(T); - -mod private { - // Implemented only on traits that are also `Sealed`. - pub trait Sealing {} -} -use private::Sealing; - -pub trait Sealed: Sealing { - fn assumed { - // ❷ - } -} - -impl Generic { - fn assuming { - // ❶ - } -} -``` - -In this crate, any code at ❶ is currently allowed to make safety-critical assumptions about code at ❷ and other implementations of `assumed`. - -To ensure this stays sound, scoped `impl Trait for Type` where `Trait` is external requires that all supertraits of `Trait` are visible to the crate defining the scoped implementation or are defined not in `Trait`'s definition crate (meaning they must still be exported from a crate somewhere in the dependency tree). - -See also [scoped-implementation-of-external-sealed-trait]. - -## Type parameters capture their *implementation environment* -[type-parameters-capture-their-implementation-environment]: #type-parameters-capture-their-implementation-environment - -When a type parameter is specified, either explicitly or inferred from an expression, it captures a view of *all* implementations that are applicable to its type there. This is called the type parameter's *implementation environment*. - -(For trait objects, associated types are treated as type parameters for the purposes of this proposal.) - -When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type parameters are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type parameter is specified. - -When a generic type parameter is used to discretise another generic, the captured environment is the one captured in the former but overlaid with modifications applicable to that generic type parameter's opaque type. - -Note that type parameter defaults too capture their *implementation environment* where they are specified, so at the initial definition site of the generic. This environment is used whenever the type parameter default is used. - -In order to avoid too much friction, [implementation-invariant-generics] are exempt from acting as host for *implementation environments* on their own. - -## Type identity of discrete types -[type-identity-of-discrete-types]: #type-identity-of-discrete-types - -The type identity and `TypeId::of::<…>()` of discrete types, including discretised generics, are not affected by scoped implementations *on* them. - -## Type identity of generic types -[type-identity-of-generic-types]: #type-identity-of-generic-types - -### Implementation-aware generics -[implementation-aware-generics]: #implementation-aware-generics - -Generics that are not [implementation-invariant-generics] are implementation-aware generics. - -The type identity of implementation-aware generic types is derived from the types specified for their type parameters as well as the *full* *implementation environment* of each of their type parameters and their associated types: - -```rust -#[derive(Default)] -struct Type; -#[derive(Default)] -struct Generic(T); -trait Trait {} - -impl Generic { - fn identical(_: Self) {} - fn nested_convertible>(_: Generic) {} -} - -mod mod1 { - use crate::{Generic, Trait, Type}; - use impl Trait for Type {} // Private implementation, but indirectly published through `Alias1`. - pub type Alias1 = Generic; -} - -mod mod2 { - use crate::{Generic, Trait, Type}; - pub use impl Trait for Type {} // Public implementation. - pub type Alias2 = Generic; -} - -mod mod3 { - use crate::{Generic, Trait, Type}; - use crate::mod2::{impl Trait for Type}; // Reused implementation. - pub type Alias3 = Generic; -} - -mod mod4 { - use crate::{Generic, Trait, Type}; - use impl Trait for Generic {} // Irrelevant top-level implementation. - pub type Alias4 = Generic; -} - -mod mod5 { - use crate::{Generic, Type}; - // No implementation. - pub type Alias5 = Generic; -} - -use mod1::Alias1; -use mod2::Alias2; -use mod3::Alias3; -use mod4::Alias4; -use mod5::Alias5; - -fn main() { - use std::any::TypeId; - - use tap::Conv; - - // Distinct implementations produce distinct types. - assert_ne!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::(), TypeId::of::()); - - // Types with identical captured implementation environments are still the same type. - assert_eq!(TypeId::of::(), TypeId::of::()); - - // Top-level implementations are not part of type identity. - assert_eq!(TypeId::of::(), TypeId::of::()); - - // If the type is distinct, then values aren't assignable. - // Alias1::identical(Alias2::default()); - // ^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types - - // Fulfilled using the global reflexive `impl Into for T` on `Type`, - // as from its perspective, the binding is stripped due to being top-level. - Alias1::nested_convertible(Alias2::default()); - - // The reflexive `impl Into for T` does not apply between the aliases here, - // as the distinct capture in the type parameter affects its inherent identity. - // (It's unfortunately not possible to generically implement this conversion without specialisation.) - // Alias1::default().conv::(); - // ^^^^ error[E0277]: the trait bound `Generic: From>>` is not satisfied - - // Identical types are interchangeable. - Alias2::identical(Alias3::default()); - Alias4::identical(Alias5::default()); -} -``` - -As mentioned in [type-identity-of-discrete-types], implementations on the generic type *itself* do *not* affect its type identity, as can be seen with `Alias4` above. - -The `TypeId` of these generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] or `(…,)`-tuple-types combining them instead of on `TypeId::of::()`. (see also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]) - -(For a practical example, see [logical-consistency] [of-generic-collections].) - -### Implementation-invariant generics -[implementation-invariant-generics]: #implementation-invariant-generics - -The following generics that never rely on the consistency of trait implementations on their type parameters are implementation-invariant: - -- `&T`, `&mut T` (references), -- `*const T`, `*mut T` (pointers), -- `[T; N]`, `[T]` (arrays and slices), -- `(T,)`, `(T, U, ..)` (tuples), -- *superficially*\* `fn(T) -> U` and similar (function pointers), -- *superficially*\* `Fn(T) -> U`, `FnMut(T) -> U`, `FnOnce(T) -> U`, `Future`, `Iterator`, `std::ops::Coroutine` and similar (closures), -- `Pin

This is a tradeoff towards integration with Rust's ecosystem, as generics are generally not inherently bounded on collection types in Rust.

There is likely some friction here with APIs that make use of runtime type identity. See [split-type-identity-may-be-unexpected].

| - -Some features are largely equivalent: - -| Genus | Rust (*without* scoped `impl Trait for Type`) | notes / scoped `impl Trait for Type` | -|---|---|---| -| Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | -| Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | -| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of unrelated parts of the top-level *implementation environment* of their type parameters.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing *implementation environments* in their type parameters.

| - -## A Language for Generic Programming in the Large - -Jeremy G. Siek, Andrew Lumsdaine, 2007 - - - -𝒢 and scoped `impl Trait for Type` are conceptually very similar, though this RFC additionally solves logical consistency issues that arise from having multiple alternative ways to fulfill a constraint and develops some ideas further than the paper. Other differences are largely due to 𝒢 being more C++-like while scoped `impl Trait for Type` attempts smooth integration with all relevant Rust language features. - -A few notable similarities, in the paper's words: - -- equivalent retroactive modeling (where existing Rust's is limited by orphan rules), -- (retained) separate compilation (though *some* information can flow between items in this RFC, but only where such information flows already exist in Rust currently), -- lexically scoped models, -- seemingly the same binding rules on generic type parameters within constrained models/generic implementations, - -and key differences: - -| 𝒢 | Rust / scoped `impl Trait for Type` | notes | -|---|---|---| -| Only discrete model imports | Includes generic imports and re-exports | This is pointed out as '[left] for future work' in the paper. Here, it follows directly from the syntax combination of Rust's `use` and `impl Trait for Type` items. | -| - | (Rust) Global implementations | The automatic availability of global implementations between separately imported traits and types offers more convenience especially when working with common traits, like those backing operators in Rust. | -| Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation or module boundary.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | -| - | (Rust) Trait objects | 𝒢 does not appear to support runtime polymorphism beyond function pointers. Scoped `impl Trait for Type` is seamlessly compatible with `dyn Trait` coercions (iff `Trait` is object-safe). | -| (unclear?) | Available implementations on discretised type parameters become part of the type identity of implementation-aware generics. | This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour. | - -# Unresolved questions -[unresolved-questions]: #unresolved-questions - -## "global" implementations - -I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" is better? - -## Precise resolution location of *implementation environments* in function calls - -In macros, which function-call token should provide the resolution context from where to look for scoped `impl Trait for Type`s (in all possible cases)? - -This doesn't matter for `Span::call_site()` vs. `Span::mixed_site()` since scoped implementations would resolve transparently through both, but it does matter for `Span::def_site()` which should exclude them. - -It very much does matter if one of the opt-in mitigations for [first-party-implementation-assumptions-in-macros] is implemented. - -## Which `struct`s should be implementation-invariant? -[which-structs-should-be-implementation-invariant]: #which-structs-should-be-implementation-invariant - -This is a tough question because, runtime behaviour difference of [of-type-erased-collections] aside, the following makes shifting a type from [implementation-aware-generics] to [implementation-invariant-generics] a compilation-breaking change: - -```rust -struct Type; -struct Generic(T); -trait Trait {} - -mod a { - use super::{Type, Generic, Trait}; - pub use impl Trait for Type {} - pub type Alias = Generic; -} - -mod b { - use super::{Type, Generic, Trait}; - pub use impl Trait for Type {} - pub type Alias = Generic; -} - -use impl Trait for a::Alias {} -use impl Trait for b::Alias {} -``` - -(It is *theoretically* possible to do such a later adjustment as part of an edition, even considering `TypeId` behaviour I think, but it's certainly not pretty.) - -Splitting this along the line of "structs that use `<>` around type parameters" would feel cleaner, but the basic smart pointers, `Pin

`, `Option` and `Result` appear in crate API signatures enough that not including them would create considerable friction. - -Other candidates for consideration: - -- Other `DispatchFromDyn` types in the standard library like `Cell`, `SyncUnsafeCell`, `UnsafeCell` - -## Should it be an error to specify an *implementation environment* in places where it's guaranteed to be unused? -[should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused]: #should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused - -With the given [grammar-changes], it's possible to write `fn((Type as Trait in module))`, but, at least without a surrounding host, here the *implementation environment* written inline is completely ineffective because function pointer types are discretised [implementation-invariant-generics]. - -On the other hand, making it an error rather than a [unused-scoped-implementation] warning could easily cause problems for macros. - -# Future possibilities -[future-possibilities]: #future-possibilities - -## Exporting a scoped implementation as global, `extern impl Trait` - -***This should never be used for IO/serialisation traits.*** - -Application crates may want to provide a specific implementation globally, disregarding orphan rules since there are no downstream crates that could be impacted by future incompatibilities (and crate-local issues are largely mitigated by *Cargo.lock*). - -This could later be allowed using a construct like - -```rust -// Use an external implementation as global: -#[core::unstable_use_as_global] -use impl_crate::{impl Trait for Type}; - -// Provide a local implementation globally: -#[core::unstable_use_as_global] -use impl Trait for Type { /*...*/ } -``` - -To use a global implementation not available through one of its dependencies, a library crate would have to declare it: - -```rust -extern impl Trait for Type; -``` - -This would result in a compile error or link-time error if the declaration is not fully covered by a global trait implementation. - -If the trait implementation is later made available plainly (that is: without `use`, subject to orphan rules) by a dependency, a warning should appear on the `extern impl` declaration, along with the suggestion to remove the `extern impl` item. - -(However, I assume binding to implementations not-from dependencies or the same crate in this way has a lot of implications for code generation.) - -There is previous discussion regarding a similar suggestion in a slightly different context: [[Pre-RFC] Forward impls](https://internals.rust-lang.org/t/pre-rfc-forward-impls/4628) -Perhaps the downsides here could be mitigated by allowing `#[unstable_use_as_global]` very strictly only in application crates compiled with the `cargo --locked` flag. - -## Scoped `impl Trait for Type` of auto traits, `Drop` and/or `Copy` with orphan rules - -The crate in which a type is defined could in theory safely provide scoped implementations for it also for these traits. - -- This is likely more complicated to implement than the scoped `impl Trait for Type`s proposed in this RFC, as these traits interact with more distinct systems. - -- What would be the binding site of `Drop` in `let`-statements? - -- This could interact with linear types, were those to be added later on. - - For example, database transactions could be opt-out linear by being `!Drop` globally but also having their crate provide a scoped `Drop` implementation that can be imported optionally to remove this restriction in a particular consumer scope. - -## Scoped proxy implementations - -In theory it *might* be possible to later add syntax to create an exported implementation that's *not in scope for itself*. - -I'm **very** hesitant about this since doing so would allow transparent overrides of traits (i.e. proxying), which could be abused for JavaScript-style layered overrides through copy-pasting source code together to some extent. - -## Analogous scoped `impl Type` - -This could be considered as more-robust alternative to non-object-safe extension traits defined in third party crates. - -A good example of this use case could be the [tap] crate, which provides generic extension methods applicable to *all* types, but where its use is *theoretically* vulnerable to instability regarding the addition of type-associated methods of the same name(s). - -If instead of (or in addition to!) …: - -```rust -// pipe.rs - -pub trait Pipe { - #[inline(always)] - fn pipe(self, func: impl FnOnce(Self) -> R) -> R - where - Self: Sized, - R: Sized, - { - func(self) - } - - // ... -} - -impl Pipe for T where T: ?Sized {} -``` - -…the extension could be defined as …: - -```rust -pub use impl T where T: ?Sized { - #[inline(always)] - fn pipe(self, func: impl FnOnce(Self) -> R) -> R - where - Self: Sized, - R: Sized, - { - func(self) - } - - // ... -} -``` - -…then: - -- The consumer crate could choose which types to import the extension for, weighing - - ```rust - use tap::pipe::{impl Type1, impl Type2}; - ``` - - against - - ```rust - use tap::pipe::{impl T where T: ?Sized}; - ``` - -- These *scoped extensions would shadow inherent type-associated items of the same name*, guaranteeing stability towards those being added. - - (This should come with some warning labels in the documentation for this feature, since *adding items to an existing public scoped extension* could be considered an easily-breaking change here.) - -This has fewer benefits compared to scoped `impl Trait for Type`, but would still allow the use of such third-party extension APIs in library crates with very high stability requirements. - -An open question here is whether (and how) to allow partially overlapping `use impl Type` in the same scope, in order to not shadow inherent associated items with ones that cannot be implemented for the given type. - -- That could in theory be more convenient to use, but - -- calls could be *subtly* inconsistent at the consumer side, i.e. accidentally calling an inherent method if a scoped extension method was expected and - -- widening a public implementation to overlap more of another exported in the same module could break dependent crates if a wide blanket import applied to narrower extensions. - -As such, *if* this feature was proposed and accepted at some point in the future, it would likely be a good idea to only allow non-overlapping implementations to be exported. - -[tap]: https://crates.io/crates/tap - -## Interaction with specialisation -[interaction-with-specialisation]: #interaction-with-specialisation - -- Scoped `impl Trait for Type` can be used for consumer-side specialisation of traits for binding sites that are in item scope, by partially shadowing an outer scope's implementation. - - Note that this would **not** work on generic type parameters, as the selected implementation is controlled strictly by their bounds (See [resolution-on-generic-type-parameters].), but it would work in macros for the most part. - - This does not interact with [specialisation proper](https://rust-lang.github.io/rfcs/1210-impl-specialization.html), but rather is a distinct, less powerful mechanism. As such, it would not supersede specialisation. - -- Scoped `impl Trait for Type` does not significantly interact with specialisation of global implementations. - - Any global specialisation would only be resolved once it's clear no scoped implementation applies. - -- Specialisation could disambiguate scoped implementations which are provided (implemented or imported) in the same scope. For example, - - ```rust - use dummy_debug::{impl Debug for T}; - use debug_by_display::{impl Debug for T}; - use impl Debug for str { - // ... - } - ``` - - would then compile, in scope resolving `` to the local implementation and otherwise binding `Debug` depending on whether `Display` is available at the binding site for each given type `T`. - - Local implementations do not necessarily have to be more specific compared to imported ones - in keeping with "this is the same as for global implementations", the way in which the scoped implementation is introduced to the scope should not matter to specialisation. - - **When importing scoped implementations from a module, specialisation should apply hierarchically.** First, the specificity of implementations is determined only by `use impl` implementations and `use`-declarations in the importing scope. If the trait bound binds to a `use`-declaration, then the actual implementation is chosen by specificity among those visible in the module they are imported from. If the chosen implementation there is an import, the process repeats for the next module. This ensures stability and coherence when published implementations are specialised in other modules. - - - I'm not sure how well this can be cached in the compiler for binding-sites in distinct scopes, unfortunately. Fortunately, specialisation of scoped `impl Trait for Type` does not seem like a blocker for specialisation of global trait implementations. - - - Should specialisation of scoped implementations require equal visibility? I think so, but this question also seems considerably out of scope for scoped `impl Trait as Type` as a feature itself. - -## Scoped `impl Trait for Type` as associated item - -Scoped `impl Trait for Type` could be allowed and used as associated non-object-safe item as follows: - -```rust -trait OuterTrait { - use impl Trait for Type; -} - -fn function() { - use T::{impl Trait for Type}; - // ...configured code... -} -``` -```rust -impl OuterTrait for OtherType { - // Or via `use`-declaration of scoped implementation(s) defined elsewhere! - // Or specify that the global implementation is used (somehow)! - use impl Trait for Type { - // ... - } -} - -function::(); -``` - -This would exactly supersede the following more verbose pattern enabled by this RFC: - -```rust -trait OuterTrait { - type Impl: ImplTraitFor; -} - -trait ImplTraitFor { - // Copy of trait's associated items, but using `T` instead of the `Self` type and - // e.g. a parameter named `this` in place of `self`-parameters. -} - -fn function() { - use impl Trait for Type { - // Implement using `T::Impl`, associated item by associated item. - } - - // ...configured code... -} -``` - -```rust -struct ImplTraitForType; -impl ImplTraitFor for ImplTraitForType { - // Implement item-by-item, as existing scoped `impl Trait for Type` cannot be used here. -} - -impl OuterTrait for OtherType { - type Impl: ImplTraitFor = ImplTraitForType; -} - -function::(); -``` - -- *In theory* this could be made object-safe if the associated implementation belongs to an object-safe trait, but this would introduce much-more-implicit call indirection into Rust. - -## Scoped fallback implementations -[scoped-fallback-implementations]: #scoped-fallback-implementations - -A scoped fallback implementation could be allowed, for example by negatively bounding it *on the same trait* in the definition or import: - -```rust -#[derive(Debug)] -struct Type1; - -struct Type2; - -{ - use debug_fallback::{impl Debug for T where T: !Debug}; - - dbg!(Type1); // Compiles, uses global implementation. - dbg!(Type2); // Compiles, uses fallback implementation. -} -``` - -This would be a considerably less messy alternative to [autoref-] or [autoderef-specialisation] for macro authors. - -Note that ideally, these fallback implementations would still be required to not potentially overlap with any other (plain or fallback) scoped implementation brought into that same scope. - -[autoref-]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md -[autoderef-specialisation]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html - -## Negative scoped implementations - -It's technically possible to allow negative scoped implementations that only shadow the respective implementation from an outer scope. For example: - -```rust -// signed-indexing/src/arrays/prelude.rs -use core::ops::Index; - -pub use impl !Index for [T; N] {} -pub use impl Index for [T; N] { - type Output = T; - - #[inline] - #[track_caller] - fn index(&self, index: isize) -> &T { - match index { - 0.. => self[index as usize], - ..=-1 => if let Some(index) = self.len().checked_add_signed(index) { - self[index] - } else { - #[inline(never)] - #[track_caller] - fn out_of_bounds(len: usize, index: isize) -> ! { - panic!("Tried to index slice of length {len} with index {index}, which is too negative to index backwards here."); - } - - out_of_bounds(self.len(), index); - }, - } - } -} -``` - -```rust -use signed_indexing::arrays::prelude::*; - -let array = [1, 2, 3]; - -// Unambiguous: -let first = array[0]; -let last = array[-1]; -``` - -This is likely a rather niche use-case. - -It could also be useful in the context of [scoped-fallback-implementations]. - -## Implicit import of supertrait implementations of scoped implementations defined on discrete types -[implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types]: #implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types - -As subtype implementations defined on discrete types always require specific supertrait implementations, the import of these supertrait implementations could be made implicit. - -This would also affect *implementation environments* modified in generic arguments, changing - -```rust -let min_heap: BinaryHeap = [1, 3, 2, 4].into(); -``` - -to - -```rust -let min_heap: BinaryHeap = [1, 3, 2, 4].into(); -``` - -and - -```rust -dbg!(::cmp(&1, &2)); // […] = Greater -``` - -to - -```rust -dbg!(::cmp(&1, &2)); // […] = Greater -``` - -The downside is that `use`-declarations would become less obvious. Implied supertrait implementation imports could be enabled only for *implementation environments* specified inline on generic type parameters as e.g. `Type as Ord in module` to avoid this. - -If this is added later than scoped `impl Trait for Type`, then private scoped implementations **must not** be implicitly exported through this mechanism. (It's likely a good idea to not allow that anyway, as it would be surprising.) Making previously crate-private implementations available that way could lead to unsoundness. - -### Alternatively - -It could be enough to allow inferring the module explicitly by writing `_` instead of its *SimplePath*, so that the snippets above become - -```rust -let min_heap: BinaryHeap = [1, 3, 2, 4].into(); -``` - -and - -```rust -dbg!(<(u32 as PartialOrd in _) as Ord in reverse>::cmp(&1, &2)); // […] = Greater -``` - -Here, too, the inference should only be of required supertrait implementations based on explicitly chosen implementations of their subtraits. - -## Conversions where a generic only cares about specific bounds' consistency - -With specialisation and more expressive bounds, an identity conversion like the following could be implemented: - -```rust -// In the standard library. - -use std::mem; - -impl From> for HashSet -where - T: ?Hash + ?Eq, // Observe implementations without requiring them. - U: ?Hash + ?Eq, - T == U, // Comparison in terms of innate type identity and observed implementations. -{ - fn from(value: HashSet) -> Self { - unsafe { - // SAFETY: This type requires only the `Hash` and `Eq` implementations to - // be consistent for correct function. All other implementations on - // generic type parameters may be exchanged freely. - // For the nested types this is an identity-transform, as guaranteed - // by `T == U` and the shared `S` which means the container is also - // guaranteed to be layout compatible. - mem::transmute(value) - } - } -} -``` - -This could also enable adjusted borrowing: - -```rust -// In the standard library. - -use std::mem; - -impl HashSet { - fn as_with_item_impl(&self) -> HashSet - where - T: ?Hash + ?Eq, // Observe implementations without requiring them. - U: ?Hash + ?Eq, - T == U, // Comparison in terms of innate type identity and observed implementations. - { - unsafe { - // SAFETY: This type requires only the `Hash` and `Eq` implementations to - // be consistent for correct function. All other implementations on - // generic type parameters may be exchanged freely. - // For the nested types this is an identity-transform, as guaranteed - // by `T == U` and the shared `S` which means the container is also - // guaranteed to be layout compatible. - &*(self as *const HashSet as *const HashSet) - } - } -} -``` - -(But at that point, it may be better to use something like an unsafe marker trait or unsafe trait with default implementations.) - -## Sealed trait bounds - -This is probably pretty strange, and may not be useful at all, but it likely doesn't hurt to mention this. - -Consider *ImplEnvironment* clauses in bounds like here: - -```rust -use another_crate::{Trait, Type1, Type2}; - -pub fn function() {} - -pub use impl Trait for Type1 {} -pub use impl Trait for Type2 {} -``` - -With this construct, `function` could privately rely on implementation details of `Trait` on `Type1` and `Type2` without defining a new sealed wrapper trait. It also becomes possible to easily define multiple sealed sets of implementations this way, by defining modules that export them. - -Overall this would act as a more-flexible but also more-explicit counterpart to sealed traits. - -Iff the caller is allowed to use this function without restating the binding, then removing the scope would be a breaking change (as it is already with bindings captured on type parameters in public signatures, so that would be consistent for this syntactical shape). - -> That convenience (automatically using the correct implementations even if not in scope) also really should exist only iff there already is robust, near-effortless tooling for importing existing scoped implementations where missing. Otherwise this feature here *would* get (ab)used for convenience, which would almost certainly lead to painful overly sealed APIs. - -Binding an implementation in a call as `function::()` while it is constrained as `fn function() { … }` MUST fail for distinct modules `a` and `b` even if the implementations are identical, as otherwise this would leak the implementation identity into the set of breaking changes. - -## Glue crate suggestions -[glue-crate-suggestions]: #glue-crate-suggestions - -If crates move some of their overlay features into glue crates, as explained in [unblock-ecosystem-evolution], it would be nice if they could suggest them if both they and e.g. Serde were `cargo add`ed as direct dependencies of a crate currently being worked on. - -An example of what this could look like: - -```toml -[package] -name = "my-crate" -version = "0.1.2" -edition = "2021" - -[dependencies] -# none - -[suggest-with.serde."1"] -my-crate_serde_glue = "0.1.0" - -[suggest-with.bevy_reflect."0.11"] -my-crate_bevy_reflect_glue = "0.1.2" - -[suggest-with.bevy_reflect."0.12"] -my-crate_bevy_reflect_glue = "0.2.1" -``` - -(This sketch doesn't take additional registries into account.) - -Ideally, crates.io should only accept existing crates here (but with non-existing version numbers) and Cargo should by default validate compatibility where possible during `cargo publish`. - -## Reusable limited-access APIs - -Given a newtype of an unsized type, like - -```rust -#[repr(transparent)] -pub struct MyStr(str); -``` - -for example, there is currently no safe-Rust way to convert between `&str` and `&MyStr` or `Box` and `Box`, even though *in the current module which can see the field* this is guaranteed to be a sound operation. - -One good reason for this is that there is no way to represent this relationship with a marker trait, since any global implementation of such a trait would give outside code to this conversion too. - -With scoped `impl Trait for Type`, the code above could safely imply a marker implementation like the following in the same scope: - -```rust -// Visibility matches newtype or single field, whichever is more narrow. - -use unsafe impl Transparent for MyStr {} -use unsafe impl Transparent for str {} -// Could symmetry be implied instead? -``` - -(`Transparent` can and should be globally reflexive.) - -This would allow safe APIs with unlimited visibility like - -```rust -pub fn cast, U>(value: T) -> U { - unsafe { - // SAFETY: This operation is guaranteed-safe by `Transparent`. - std::mem::transmute(value) - } -} -``` - -and - -```rust -unsafe impl, U> Transparent> for Box {} -unsafe impl<'a, T: Transparent, U> Transparent<&'a U> for &'a T {} -unsafe impl<'a, T: Transparent, U> Transparent<&'a mut U> for &'a mut T {} -``` - -which due to their bound would only be usable where the respective `T: Transparent`-implementation is in scope, that is: where by-value unwrapping-and-then-wrapping would be a safe operation (for `Sized` types in that position). - -Overall, this would make unsized newtypes useful without `unsafe`, by providing a compiler-validated alternative to common reinterpret-casts in their implementation. The same likely also applies to certain optimisations for `Sized` that can't be done automatically for unwrap-then-wrap conversions as soon as a custom `Allocator` with possible side-effects is involved. - -If a module wants to publish this marker globally, it can do so with a separate global implementation of the trait, which won't cause breakage. (As noted in [efficient-compilation], the compiler should treat implementations of empty traits as identical early on, so that no code generation is unnecessarily duplicated.) - -> *Could* sharing pointers like `Arc` inherit this marker from their contents like `Box` could? I'm unsure. They probably *shouldn't* since doing this to exposed shared pointers could easily lead to hard-to-debug problems depending on drop order. -> -> A global -> -> ```rust -> unsafe impl, U> Transparent> for UnsafeCell {} -> ``` -> -> should be unproblematic, but a global -> -> ```rust -> unsafe impl Transparent for UnsafeCell {} -> ``` -> -> (or vice versa) **must not** exist to allow the likely more useful implementations on `&`-like types. +- Feature Name: `scoped_impl_trait_for_type` +- Start Date: (fill me in with today's date, 2024-05-12) +- RFC PR: [rust-lang/rfcs#3634](https://github.com/rust-lang/rfcs/pull/3634) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This proposal adds scoped `impl Trait for Type` items into the core language, as coherent but orphan-rule-free alternative to implementing traits globally. It also extends the syntax of `use`-declarations to allow importing these scoped implementations into other item scopes (including other crates), and differentiates type identity of most generics by which scoped trait implementations are available to each discretised generic type parameter (also adding syntax to specify differences to these captured *implementation environments* directly on generic type arguments). + +This (along with some details specified below) enables any crate to + +- locally, in item scopes, implement nearly any trait for any expressible type, +- publish these trivially composable implementations to other crates, +- import and use such implementations safely and seamlessly and +- completely ignore this feature when it's not needed\*. + +\* aside from one hopefully very obscure `TypeId` edge case that's easy to accurately lint for. + +This document uses "scoped implementation" and "scoped `impl Trait for Type`" interchangeably. As such, the former should always be interpreted to mean the latter below. + +# Motivation +[motivation]: #motivation + +While orphan rules regarding trait implementations are necessary to allow crates to add features freely without fear of breaking dependent crates, they limit the composability of third party types and traits, especially in the context of derive macros. + +For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between derive macro crates. + +Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions in order to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. + +Another pain point are sometimes missing `Into<>`-conversions when propagating errors with `?`, even though one external residual (payload) type may (sometimes *contextually*) be cleanly convertible into another. As-is, this usually requires a custom intermediary type, or explicit conversion using `.map_err(|e| …)` (or an equivalent function/extension trait). If an appropriate `From<>`-conversion can be provided *in scope*, then just `?` can be used. + +This RFC aims to address these pain points by creating a new path of least resistance that is easy to use and very easy to teach, intuitive to existing Rust-developers, readable without prior specific knowledge, discoverable as needed, has opportunity for rich tooling support in e.g. rust-analyzer and helpful error messages, is quasi-perfectly composable including decent re-use of composition, improves maintainability and (slightly) robustness to major-version dependency changes compared to newtype wrappers, and does not restrict crate API evolution, compromise existing coherence rules or interfere with future developments like specialisation. Additionally, it allows the implementation of more expressive (but no less explicit) extension APIs using syntax traits like in the `PartialEq<>`-example below, without complications should these traits be later implemented in the type-defining crate. + +For realistic examples of the difference this makes, please check the [rationale-and-alternatives] section. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Scoped `impl Trait for Type` can be introduced in The Book alongside global trait implementations and mentioned in the standard library documentation examples. + +For example, the following changes could be made: + +## **10.2.** Traits: Defining Shared Behavior + +The following sections are added after [Implementing a Trait on a Type]: + +[Implementing a Trait on a Type]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type + +### Scoped Implementation of a Trait on a Type + +Independently of implementing a trait on a type or set of types *globally*, it's possible to do so only for the current scope, by adding the `use` keyword: + +```rust +use impl Trait for Type { + // ... +} +``` + +With the exception of very few traits related to language features, you can implement any visible trait on any visible type this way, even if both are defined in other crates. + +In other words: The *orphan rule* does not apply to scoped implementations. Instead, item shadowing is used to determine which implementation to use. + +*Scoped implementations are intended mainly as compatibility feature*, to let third party crates provide glue code for other crate combinations. To change the behaviour of an instance or a set of instances from their default, consider using [the newtype pattern] instead. + +[`Hash`]: https://doc.rust-lang.org/stable/std/hash/trait.Hash.html +[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html +[`Eq`]: https://doc.rust-lang.org/stable/std/cmp/trait.Eq.html +[`PartialOrd`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialOrd.html +[`Ord`]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html + +[`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html +[`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html + +[the newtype pattern]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types + +### Publishing and Importing Scoped Implementations + +You can also publish a scoped implementation further by adding a visibility before `use` ...: + +```rust +pub use impl Trait for Type { + // ... +} + +pub use unsafe impl UnsafeTrait for Type { + // ... +} +``` + +... and import it into other scopes: + +```rust +use other_module::{ + impl Trait for Type, + impl UnsafeTrait for Type, +}; +``` + +Note that the scoped implementation of `UnsafeTrait` is imported without the `unsafe` keyword. **It is the implementing crate's responsibility to ensure the exported `unsafe` implementation is sound everywhere it is visible!** + +Generic parameters, bounds and `where`-clauses can be used as normal in each of these locations, though you usually have to brace `impl Trait for Type where /*...*/` individually in `use`-declarations. + +You can import a subset of a generic implementation, by narrowing bounds or replacing type parameters with concrete types in the `use`-declaration. + +Global implementations can be imported from the root namespace, for example to shadow a scoped implementation: + +```rust +use ::{impl Trait for Type}; +``` + +### Scoped implementations and generics +[scoped-implementations-and-generics]: #scoped-implementations-and-generics + +Scoped implementations are resolved on most generics' type arguments where those are specified, and become part of the (now less generic) host type's identity: + +```rust +#[derive(Default)] +struct Type(T); + +trait Trait { + fn trait_fn(); +} + +impl Type { + fn type_fn() { + T::trait_fn(); + } +} + +mod nested { + use super::{Trait, Type}; + + use impl Trait for () { + fn trait_fn() { + println!("nested"); + } + } + + pub type Alias = Type<()>; +} +use nested::Alias; + +fn main() { + Alias::type_fn(); // "nested" + + // Type::<()>::type_fn(); + // ^^^^^^^ error[E0599]: the function or associated item `type_fn` exists for struct `Type<()>`, but its trait bounds were not satisfied + + // let t: Type<()> = Alias::default(); + // ^^^^^^^^^ error[E0308]: mismatched types + + let t: Type<() as Trait in nested> = Alias::default(); +} +``` + +This works equally not just for type aliases but also fields, `let`-bindings and also where generic type parameters are inferred automatically from expressions (for example to call a constructor). + +Note that some utility types, like references, tuples, `Option`, `Result` and closure traits, do not bind implementations eagerly but only when used to specify another generic. You can find a list of these types in the reference. (← i.e. "insert link here".) + +## **19.2.** Advanced Traits + +The section [Using the Newtype Pattern to Implement External Traits on External Types] is updated to mention scoped implementations, to make them more discoverable when someone arrives from an existing community platform answer regarding orphan rule workarounds. It should also mention that newtypes are preferred over scoped implementations when use of the type is semantically different, to let the type checker distinguish it from others. + +[Using the Newtype Pattern to Implement External Traits on External Types]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types + +A new section is added: + +### Using Scoped Implementations to Implement External Traits on External Types +[using-scoped-implementations-to-implement-external-traits-on-external-types]: #using-scoped-implementations-to-implement-external-traits-on-external-types + +Since scoped implementations allow crates to reusably implement external traits on external types, they can be used to provide API extensions that make use of syntactic sugar. For example: + +Filename: fruit-comparer/src/lib.rs + +```rust +use apples::Apple; +use oranges::Orange; + +pub use impl PartialEq for Apple { + fn eq(&self, other: &Orange) -> bool { + todo!("Figure out how to compare apples and oranges.") + } +} + +pub use impl PartialEq for Orange { + fn eq(&self, other: &Orange) -> bool { + todo!("Figure out how to compare oranges and apples.") + } +} +``` + +Filename: src/main.rs + +```rust +use apples::Apple; +use oranges::Orange; + +use fruit_comparer::{ + impl PartialEq for Apple, + impl PartialEq for Orange, +}; + +fn main() { + let apple = Apple::new(); + let orange = Orange::new(); + + // Compiles: + dbg!(apple == orange); + dbg!(orange == apple); +} +``` + +If the type whose API was extended this way later gains the same trait inherently, that is not a problem as the consuming code continues to use `fruit_comparer`'s scoped implementation. However, a warning ([global-trait-implementation-available]) is shown by default to alert the maintainers of each crate of the covering global implementation. + +Be careful about literal coercion when using generic traits this way! For example, if a scoped implementation of `Index` is used and a global `Index` implementation is added later on the same type, the compiler will *not* automatically decide which to use for integer literal indices between these two. + +## Rustdoc documentation changes +[rustdoc-documentation-changes]: #rustdoc-documentation-changes + +### `use` and `impl` keywords + +The documentation pages [for the `use` keyword] and [for the `impl` keyword] are adjusted to (very) briefly demonstrate the respective scoped use of `impl Trait for Type`. + +[for the `use` keyword]: https://doc.rust-lang.org/stable/std/keyword.use.html +[for the `impl` keyword]: https://doc.rust-lang.org/stable/std/keyword.impl.html + +### `TypeId` + +The page for [`TypeId`] gains two sections with the following information: + +```markdown +# `TypeId` and scoped implementations + +To make sure that that are no mix-ups between, for example, `HashSet` and `HashSet`, any such difference implies distinct `TypeId`s between such discretised generics (and that the types are not mutually assignable). + +This also affects trait-bounded generic type parameters: If `T` is bounded on `Hash`, then `TypeId::of::()` results in distinct `TypeId`s in that context depending on the captured implementation. + +However, note that `TypeId::of::()` and `TypeId::of::()` are always equivalent for one definition of `T`, as `TypeId::of`'s implementation does **not** have a `T: Hash` bound! + +For convenience (so that their values are easily interchangeable across crates), the following types ignore scoped implementations *on* their generic arguments in terms of *their own* type identity: […] + +Despite this, differences in *type arguments'* discrete identities (for example from scoped implementations captured *in* them) distinguish the type identity of *all* discretised generics they appear in. + +# `TypeId::of::()` may change for values of generics + +To make type-erased collections sound and unsurprising by default, it's sound to transmute between instances of an external generic type that differ only in their captured scoped implementations, **iff and only iff** no inconsistency is ever observed by bounds (including across separate function calls). + +However, this poses a problem: `TypeId::of::()` (just like the written-out form of any type that doesn't ignore scoped implementations) takes *all* differences in captured implementation environments into account, not just those relevant to trait bounds. + +As such, prefer `TypeId::of::()` whenever possible in order to make only the distinctions you require. You can use tuples to combine multiple type parameters without over-distinguishing: `TypeId::of::<(S, T)>()` +``` + +> These rules and the reasons for them are explained in detail in the [reference-level-explanation] below, as well as in [logical-consistency] as part of [rationale-and-alternatives]. It may be a good idea to link to similar longer explanations from the standard library docs above, even if just as "See also:"-style references for further reading. + +> The `[…]`-placeholder stands for a list of links to each implementation-invariant generic's documentation. + +See also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a way to narrowly alert users of this when relevant, and to scan for the potential impact of these changes ahead of time. + +[`TypeId`]: https://doc.rust-lang.org/stable/std/any/struct.TypeId.html + +### Implementation-invariant generics + +The pages for [implementation-invariant-generics] gain a section similar to the following: + +```markdown +# Implementation-invariant generic + +This type does not by itself capture scoped implementation environments when discretised. See [`TypeId` and scoped implementations] for more information. +``` + +where ``[`TypeId` and scoped implementations]`` is a link to the section added to the `TypeId` page above. + +### `mem::transmute` + +The page for [`transmute`] gains a section with the following information: + +```markdown +# `transmute` and scoped implementations + +It is sound to transmute between discretised generic types that differ only in their captured scoped implementation environments, **but only iff** such differences are **never** observed by bounds on their implementation, including functions that imply such by being implemented for discrete instances of the generic. +``` + +> As far as I can tell, this is only practically relevant for certain kinds of type-erasing collections, like type-erasing hash maps and B-trees, of which I couldn't find any examples on crates.io. +> +> Any straightforward implementations of such collections should also at worst exhibit only unexpected behaviour when consumed in the presence of scoped implementations, rather than unsoundness. + +[`transmute`]: https://doc.rust-lang.org/stable/std/mem/fn.transmute.html + +## Changes to The Rustonomicon + +The page on [Transmutes] gains the following warning in addition to the existing ones: + +```markdown +- It is unsound to change [captured scoped implementations] via transmute for any external type if this change ever causes a contradiction observable by the transmuted value's implementation. + + This can happen due to bounds on called functions and/or because a called function is implemented for a specific type discretised from the generic. +``` + +`[captured scoped implementations]` should link to documentation introducing scoped `impl Trait for Type`. + +[Transmutes]: https://doc.rust-lang.org/stable/nomicon/transmutes.html + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Grammar changes +[grammar-changes]: #grammar-changes + +The core Rust language grammar is extended as follows: + +- [*TraitImpl*]'s definition is prepended with (*Visibility*? `use`)? and refactored for partial reuse to arrive at + + > *TraitImpl* : + >   **(*Visibility*? `use`)?** `unsafe`? ***TraitCoverage*** + >   `{` + >    *InnerAttribute*\* + >    *AssociatedItem*\* + >   `}` + > + > ***TraitCoverage*** : + >   ***TraitCoverageNoWhereClause*** + >   *WhereClause*? + > + > ***TraitCoverageNoWhereClause*** : + >   `impl` *GenericParams*? `!`? *TypePath* `for` *Type* + + where a trait implementation with that `use`-prefix provides the implementation *only* as item in the containing item scope. + + (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) + + **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [type-parameters-capture-their-implementation-environment]). + + [*TraitImpl*]: https://doc.rust-lang.org/reference/items/implementations.html?highlight=TraitImpl#implementations + +- [*UseTree*]'s definition is extended for importing scoped implementations by inserting the extracted *TraitCoverage* and *TraitCoverageNoWhereClause* rules as follows: + + > *UseTree* : + >   (*SimplePath*? `::`)? `*` + >   | (*SimplePath*? `::`)? `{` + >   ( + >    (**(**‍*UseTree* **| *TraitCoverageNoWhereClause*)** (`,` **(**‍*UseTree* **| *TraitCoverageNoWhereClause*)**)\* **(**`,` ***TraitCoverage*?)**?)? + >    **| *TraitCoverage*** + >   ) + >   `}` + >   | *SimplePath* (`as` (IDENTIFIER | `_`))? + + Allowing a trailing *TraitCoverage* with *WhereClause* in a braced list is intended for ergonomics, but rustfmt should brace it individually by default, then append a trailing comma where applicable as usual. A '`,`' in the *WhereClause* here is not truly ambiguous because *WhereClauseItem*s contain '`:`', but allowing that ahead of others would likely be visually confusing and tricky to implement (requiring an arbitrarily long look-ahead). Alternatively to allowing a trailing *TraitCoverage* in mixed lists, an error similar to [E0178] could be emitted. + + [E0178]: https://doc.rust-lang.org/error_codes/E0178.html + + > Allowing unbraced imports like `use some_crate::impl Trait for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy quite badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added for convenience, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced. + + Here, *TraitCoverage* imports the specified scoped `impl Trait for Type` for binding and conflict checks as if defined in the scope containing the `use`-declaration. The resulting visibility is taken from *UseDeclaration*, like with *SimplePath*-imported items. + + *TraitCoverage* must be fully covered by the scoped implementation visible in the source module. Otherwise, a compile-error occurs explaining the uncovered case (similarly to the current error(s) for missing trait implementations). + + ***TraitCoverage* may subset the source module's implementation** by having narrower bounds or using concrete types in place of one or more generic type parameters. This causes only the specified subset of the scoped implementation to be imported. + + Note that scoped implementations of `unsafe` traits are imported without `unsafe`. It is the exporting crate's responsibility to ensure a scoped implementation is sound everywhere it is visible. + + Other elements of the coverage must match the source module's implementation exactly, unless specified otherwise. + + [*UseTree*]: https://doc.rust-lang.org/reference/items/use-declarations.html?highlight=UseTree#use-declarations + +- [*TypeParam*], [*GenericArg*] and [*GenericArgsBinding*] are extended to accept *implementation environments* inline: + + > *TypeParam* : + >   IDENTIFIER ( `:` *TypeParamBounds*? )? ( `=` *Type* ***ImplEnvironment*?** )? + > + > *GenericArg* : + >   *Lifetime* | *Type* ***ImplEnvironment*?** | *GenericArgsConst* | *GenericArgsBinding* + > + > *GenericArgsBinding* : + >   IDENTIFIER `=` *Type* ***ImplEnvironment*?** + > + > ***ImplEnvironment* :** + > **  `as` ( *ImplEnvironmentEntry* ( `+` *ImplEnvironmentEntry* )\* `+`? )?** + > + > ***ImplEnvironmentEntry* :** + > **  (** + > **   *ForLifetimes*? *TypePath*** + > **   | ( *ForLifetimes*? *TypePath* )** + > **  )** + > **  `in` ( `::` | *SimplePath* )** + + When detecting conflicting implementations, the *ImplEnvironment* is treated as creating a distinct scope nested in its surrounding scope. Each resulting *implementation environment* must be conflict-free, but between them they *can* contain conflicting implementations. + + Even when an *ImplEnvironment* is added as above, the resulting *implementation environment* still captures scoped implementations from the surrounding scope for all traits that were not specified inline! A global implementation can be used explicitly by sourcing it from `::` instead of a module. + + For stability reasons (against relaxation of bounds) and because they matter for type identity, explicit inline *implementation environments* should be allowed where no matching bound is present, but should produce an [unused-scoped-implementation] warning iff neither published nor used in the same crate (including for type identity distinction). + + > Whether inline *implementation environments* would inherit from each other is intentionally left unspecified, as identical types can't be nested without indirection, which ensures such a situation isn't relevant. + + [*TypeParam*]: https://doc.rust-lang.org/reference/items/generics.html?highlight=TypeParam#generic-parameters + [*GenericArg*]: https://doc.rust-lang.org/reference/paths.html?highlight=GenericArg#paths-in-expressions + [*GenericArgsBinding*]: https://doc.rust-lang.org/reference/paths.html?highlight=GenericArgsBinding#paths-in-expressions + +- Further type specification syntax is extended as follows: + + > *ParenthesizedType* : + >   `(` *Type* ***ImplEnvironment*?** `)` + > + > *TupleType* : + >   `(` `)` + >   | `(` ( *Type* ***ImplEnvironment*?** `,` )+ **(** *Type* ***ImplEnvironment*? )**? `)` + > + > *ArrayType* : + >   `[` *Type* ***ImplEnvironment*?** `;` *Expression* `]` + > + > *SliceType* : + >   `[` *Type* ***ImplEnvironment*?** `]` + + > Closure types are not extended with *ImplEnvironment* because *implementation environments* annotated on their parameters would never be effective. + > + > Extending *ParenthesizedType* this way is necessary to specify *implementation environments* for pointer types' generic type parameters, e.g. `&(Type as Trait in module)`. + +- [*QualifiedPathType*] is also extended for this purpose, but can additionally act as *implementation environment* scope that also affects the *implementation environment* of nested types, using a clause starting with `where`: + + > *QualifiedPathType* : + >   `<` *Type* ( `as` *TypePath* **(`in` (`::` | *SimplePath* ) )?** )? **( `where` ( *Type* *ImplEnvironment* `,` )\* ( *Type* *ImplEnvironment* )? )?** `>` + + The form `` is syntactic sugar for `<(Type as Trait in module) as Trait>`, to avoid repetition of potentially long traits. + + Implementations imported after `where` must be valid, but don't necessarily have to be relevant. + + > I am **not** confident that `where` is the right keyword here, but it seems like this best option among the already-existing ones. `use`-syntax feels far too verbose here. Maybe the above but with `using` or `with` in place of `where`? + + [*QualifiedPathType*]: https://doc.rust-lang.org/reference/paths.html?highlight=QualifiedPathType#qualified-paths + +## No scoped `impl Trait for Type` of auto traits, `Copy` and `Drop` + +Implementations of auto traits state guarantees about private implementation details of the covered type(s), which an external implementation can almost never do soundly. + +`Copy` is not an auto trait, but implementing it on a smart pointer like `Box` would immediately be unsound. As such, this trait must be excluded from all external implementations. + +Shadowing `Drop` for types that are `!Unpin` is similarly unsound without cooperation of the original crate (in addition to likely causing memory leaks in this and more cases). + +## No scoped `impl !Trait for Type` + +Any negative scoped implementation like for example + +```rust +use impl !Sync for Type {} +``` + +is syntactically valid, but rejected by the compiler with a specific error. (See [negative-scoped-implementation].) + +This also applies to `impl Trait`s in `use`-declarations (even though the items they would import cannot be defined anyway. Having a specific error saying that this *isn't possible* would be much clearer than one saying that the imported item doesn't exist). + +## No external scoped implementations of sealed traits +[no-external-scoped-implementations-of-sealed-traits]: #no-external-scoped-implementations-of-sealed-traits + +Consider this library crate: + +```rust +pub struct Generic(T); + +mod private { + // Implemented only on traits that are also `Sealed`. + pub trait Sealing {} +} +use private::Sealing; + +pub trait Sealed: Sealing { + fn assumed { + // ❷ + } +} + +impl Generic { + fn assuming { + // ❶ + } +} +``` + +In this crate, any code at ❶ is currently allowed to make safety-critical assumptions about code at ❷ and other implementations of `assumed`. + +To ensure this stays sound, scoped `impl Trait for Type` where `Trait` is external requires that all supertraits of `Trait` are visible to the crate defining the scoped implementation or are defined not in `Trait`'s definition crate (meaning they must still be exported from a crate somewhere in the dependency tree). + +See also [scoped-implementation-of-external-sealed-trait]. + +## Type parameters capture their *implementation environment* +[type-parameters-capture-their-implementation-environment]: #type-parameters-capture-their-implementation-environment + +When a type parameter is specified, either explicitly or inferred from an expression, it captures a view of *all* implementations that are applicable to its type there. This is called the type parameter's *implementation environment*. + +(For trait objects, associated types are treated as type parameters for the purposes of this proposal.) + +When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type parameters are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type parameter is specified. + +When a generic type parameter is used to discretise another generic, the captured environment is the one captured in the former but overlaid with modifications applicable to that generic type parameter's opaque type. + +Note that type parameter defaults too capture their *implementation environment* where they are specified, so at the initial definition site of the generic. This environment is used whenever the type parameter default is used. + +In order to avoid too much friction, [implementation-invariant-generics] are exempt from acting as host for *implementation environments* on their own. + +## Type identity of discrete types +[type-identity-of-discrete-types]: #type-identity-of-discrete-types + +The type identity and `TypeId::of::<…>()` of discrete types, including discretised generics, are not affected by scoped implementations *on* them. + +## Type identity of generic types +[type-identity-of-generic-types]: #type-identity-of-generic-types + +### Implementation-aware generics +[implementation-aware-generics]: #implementation-aware-generics + +Generics that are not [implementation-invariant-generics] are implementation-aware generics. + +The type identity of implementation-aware generic types is derived from the types specified for their type parameters as well as the *full* *implementation environment* of each of their type parameters and their associated types: + +```rust +#[derive(Default)] +struct Type; +#[derive(Default)] +struct Generic(T); +trait Trait {} + +impl Generic { + fn identical(_: Self) {} + fn nested_convertible>(_: Generic) {} +} + +mod mod1 { + use crate::{Generic, Trait, Type}; + use impl Trait for Type {} // Private implementation, but indirectly published through `Alias1`. + pub type Alias1 = Generic; +} + +mod mod2 { + use crate::{Generic, Trait, Type}; + pub use impl Trait for Type {} // Public implementation. + pub type Alias2 = Generic; +} + +mod mod3 { + use crate::{Generic, Trait, Type}; + use crate::mod2::{impl Trait for Type}; // Reused implementation. + pub type Alias3 = Generic; +} + +mod mod4 { + use crate::{Generic, Trait, Type}; + use impl Trait for Generic {} // Irrelevant top-level implementation. + pub type Alias4 = Generic; +} + +mod mod5 { + use crate::{Generic, Type}; + // No implementation. + pub type Alias5 = Generic; +} + +use mod1::Alias1; +use mod2::Alias2; +use mod3::Alias3; +use mod4::Alias4; +use mod5::Alias5; + +fn main() { + use std::any::TypeId; + + use tap::Conv; + + // Distinct implementations produce distinct types. + assert_ne!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::(), TypeId::of::()); + + // Types with identical captured implementation environments are still the same type. + assert_eq!(TypeId::of::(), TypeId::of::()); + + // Top-level implementations are not part of type identity. + assert_eq!(TypeId::of::(), TypeId::of::()); + + // If the type is distinct, then values aren't assignable. + // Alias1::identical(Alias2::default()); + // ^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types + + // Fulfilled using the global reflexive `impl Into for T` on `Type`, + // as from its perspective, the binding is stripped due to being top-level. + Alias1::nested_convertible(Alias2::default()); + + // The reflexive `impl Into for T` does not apply between the aliases here, + // as the distinct capture in the type parameter affects its inherent identity. + // (It's unfortunately not possible to generically implement this conversion without specialisation.) + // Alias1::default().conv::(); + // ^^^^ error[E0277]: the trait bound `Generic: From>>` is not satisfied + + // Identical types are interchangeable. + Alias2::identical(Alias3::default()); + Alias4::identical(Alias5::default()); +} +``` + +As mentioned in [type-identity-of-discrete-types], implementations on the generic type *itself* do *not* affect its type identity, as can be seen with `Alias4` above. + +The `TypeId` of these generics varies alongside their identity. Note that due to the transmutation permission defined in [layout-compatibility], consumer code is effectively allowed to change the `TypeId` of instances of generics between calls to generic implementations in most cases. Due to this, implementations of generics that manage types at runtime should usually rely on the [typeid-of-generic-type-parameters-opaque-types] or `(…,)`-tuple-types combining them instead of on `TypeId::of::()`. (see also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]) + +(For a practical example, see [logical-consistency] [of-generic-collections].) + +### Implementation-invariant generics +[implementation-invariant-generics]: #implementation-invariant-generics + +The following generics that never rely on the consistency of trait implementations on their type parameters are implementation-invariant: + +- `&T`, `&mut T` (references), +- `*const T`, `*mut T` (pointers), +- `[T; N]`, `[T]` (arrays and slices), +- `(T,)`, `(T, U, ..)` (tuples), +- *superficially*\* `fn(T) -> U` and similar (function pointers), +- *superficially*\* `Fn(T) -> U`, `FnMut(T) -> U`, `FnOnce(T) -> U`, `Future`, `Iterator`, `std::ops::Coroutine` and similar (closures), +- `Pin

`, `NonNull`, `Box`, `Rc`, `Arc`, `Weak`, `Option`, `Result`\*\*. + +Implementation-invariant generics never capture *implementation environments* on their own. Instead, their effective *implementation environments* follow that of their host, acting as if they were captured in the same scope. + +The type identity of implementation-invariant generics seen on their own does not depend on the *implementation environment*. This also means that the `TypeId` of `Option` does not take into account differences of implementations *on* `T`. However, differences of implementations *in* `T` can still distinguish the types, in cases where the type identity (and possibly `TypeId`) of `T` *itself* are different. An example for this are generic type parameters' effective types that can have bounds-relevant implementations observably baked into them. + +Hosts are: + +- Type aliases (see [type-aliases-are-opaque-to-scoped-implementations]), +- [implementation-aware-generics], +- types written as *QualifiedPathType* (see [grammar-changes] to *QualifiedPathType*) and +- the *function operand* of [call expressions] (see [call-expressions-function-operand-captures-its-implementation-environment]). + +\* superficially: The underlying instance may well use a captured implementation internally, but this isn't surfaced in signatures. For example, a closure defined where `usize: PartialOrd in reverse + Ord in reverse` is just `FnOnce(usize)` but will use `usize: PartialOrd in reverse + Ord in reverse` privately when called. + +\*\* but see [which-structs-should-be-implementation-invariant]. + +See also [why-specific-implementation-invariant-generics]. + +[call expressions]: https://doc.rust-lang.org/reference/expressions/call-expr.html#call-expressions + +## Call expressions' *function operand* captures its *implementation environment* +[call-expressions-function-operand-captures-its-implementation-environment]: #call-expressions-function-operand-captures-its-implementation-environment + +Call expressions capture the *implementation environment* in their *function operand*, acting as host for [implementation-invariant-generics]. This enables call expressions such as + +```rust +Option::::fmt(…) +``` + +where `fmt` receives the specified scoped implementation by observing it through the `T: Debug` bound on its implementing `impl` block. + +If no observing bound exists, code of this form should produce a warning spanning the `Trait in module` tokens. (see [unused-scoped-implementation]) + +## Type aliases are opaque to scoped implementations +[type-aliases-are-opaque-to-scoped-implementations]: #type-aliases-are-opaque-to-scoped-implementations + +As scoped `impl Trait for Type` is a fully lexically-scoped feature, the *implementation environment* present in a scope does not affect types hidden behind a type alias, except for the top-level type directly: + +```rust +trait Trait { + fn method(&self) -> &str; +} + +impl Trait for Type { + fn method(&self) -> &str { + "global" + } +} + +mod m1 { + use super::Type; + + pub type Alias = [Type; 1]; +} + +mod m2 { + use super::{Type, Trait}; + + pub use impl Trait for Type { + fn method(&self) -> &str { + "scoped" + } + } + + pub use impl Trait for [T; 1] { + fn method(&self) -> &str { + self[0].method() + } + } +} + +fn main() { + use m1::Alias; + use m2::{ + impl Trait for Type, + impl Trait for [Type; 1], + }; + + assert_eq!([Type].method(), "scoped"); + assert_eq!(Alias::default().method(), "global"); +} +``` + +Scoped implementations may still be observed through bounded generic type parameters on the type alias itself. (see [binding-choice-by-implementations-bounds]) + +## `TypeId` of generic type parameters' opaque types +[typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types + +In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to the captured *implementation environment*, but *only according to implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: + +```rust +use std::any::TypeId; + +#[derive(Default)] +struct Type; +trait Trait {} +impl Trait for Type {} + +#[derive(Default)] +struct Generic(T); + +mod nested { + pub(super) use impl super::Trait for super::Type {} +} + +// `A` and `B` are distinct due to different captured implementation environments. +type A = Generic; +type B = Generic; + +fn no_bound(_: Generic, _: Generic) { + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::>(), TypeId::of::>()); + + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_eq!(TypeId::of::(), TypeId::of::()); +} + +fn yes_bound(_: Generic, _: Generic) { + assert_ne!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::>(), TypeId::of::>()); + + assert_eq!(TypeId::of::(), TypeId::of::()); + assert_ne!(TypeId::of::(), TypeId::of::()); +} + +fn main() { + no_bound(A::default(), B::default()); + yes_bound(A::default(), B::default()); +} +``` + +In particular: + +- If no bound-relevant scoped implementations are captured in a type parameter, then the `TypeId` of the opaque type of that type parameter is identical to that of the discrete type specified for that type parameter. +- Distinct sets of bound-relevant captured scoped implementations lead to distinct `TypeId`s of the opaque type of a type parameter. +- If the set of bound-relevant captured scoped implementations in two generic type parameters is the same, and the wrapped discrete type is identical, then the `TypeId` of the opaque types of these generic type parameters is identical. +- If a generic type parameter is distinguishable this way, it remains distinguishable in called implementations even if those have fewer bounds - the relevant distinction is 'baked' into the generic type parameter's opaque type. + +These rules (and the transmutation permission in [layout-compatibility]) allow the following collection to remain sound with minimal perhaps unexpected behaviour: + +```rust +use std::{ + any::TypeId, + collections::{ + hash_map::{HashMap, RandomState}, + HashSet, + }, + hash::{BuildHasher, Hash}, + mem::drop, +}; + +use ondrop::OnDrop; + +#[derive(Default)] +pub struct ErasedHashSet<'a, S: 'a + BuildHasher + Clone = RandomState> { + storage: HashMap, + droppers: Vec>>, +} + +impl ErasedHashSet<'_, RandomState> { + pub fn new() -> Self { + Self::default() + } +} + +impl<'a, S: BuildHasher + Clone> ErasedHashSet<'a, S> { + pub fn with_hasher(hasher: S) -> Self { + Self { + storage: HashMap::with_hasher(hasher), + droppers: vec![], + } + } + + // This is the important part. + pub fn insert(&mut self, value: T) -> bool + where + T: Hash + Eq + 'static, // <-- Bounds. + { + let type_id = TypeId::of::(); // <-- `TypeId` depends on implementations of bounds. + let storage: *mut () = if let Some(storage) = self.storage.get_mut(&type_id) { + *storage + } else { + let pointer = Box::into_raw(Box::new(HashSet::::with_hasher( + self.storage.hasher().clone(), + ))); + self.droppers.push(OnDrop::new(Box::new(move || unsafe { + // SAFETY: Only called once when the `ErasedHashSet` is dropped. + // The type is still correct since the pointer wasn't `.cast()` yet and + // both `S` and `T` are bounded on `'a`, so they are still alive at this point. + drop(Box::from_raw(pointer)); + }))); + self.storage + .insert(type_id, pointer.cast::<()>()) + .expect("always succeeds") + }; + + let storage: &mut HashSet = unsafe { + // SAFETY: Created with (close to) identical type above. + // Different `Hash` and `Eq` implementations are baked into `T`'s identity because of the bounds, so they result in distinct `TypeId`s above. + // It's allowed to transmute between types that differ in identity only by bound-irrelevant captured implementations. + // The borrowed reference isn't returned. + &mut *(storage.cast::>()) + }; + storage.insert(value) + } + + // ... +} +``` + +In particular, this code will ignore any scoped implementations on `T` that are not `Hash`, `Eq` or (implicitly) `PartialEq`, while any combination of distinct discrete type and *implementation environments* with distinct `Hash`, `Eq` or `PartialEq` implementations is cleanly separated. + +See also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for how to lint for an implementation of this collection that uses `TypeId::of::>()` as key, which *also* remains sound and deterministic but distinguishes too aggressively by irrelevant scoped implementations in consumer code, leading to unexpected behaviour. + +(For an example of `TypeId` behaviour, see [logical-consistency] [of-type-erased-collections].) + +## Layout-compatibility +[layout-compatibility]: #layout-compatibility + +Types whose identities are only distinct because of a difference in *implementation environments* remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. + +It is sound to transmute an instance between these types **if** no inconsistency is observed on that instance by the bounds of any external-to-the-`transmute` implementation or combination of implementations, including scoped implementations and implementations on discrete variants of the generic. As a consequence, the `Self`-observed `TypeId` of instances of generic types **may** change in some cases. + +For example, given a library + +```rust +#[derive(Debug)] +pub struct Type(T); + +impl Type { + pub fn method(&self) {} +} +``` + +then in another crate + +- if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again afterwards, and +- if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured *implementation environment*, because `method` has observed the *exact* type parameter through its constraints. + +(In short: Don't use external-to-your-code implementations with the instance in any combination that wouldn't have been possible without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) + +See [typeid-of-generic-type-parameters-opaque-types] for details on what this partial transmutation permission is for, and [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a future incompatibility lint that could be used to warn implementations where this is relevant. + +## No interception/no proxies + +That each scoped `impl Trait for Type { /*...*/ }` is in scope for itself makes the use of the implementation it shadows in the consumer scope *inexpressible*. There can be no scoped implementation constrained to always shadow another. + +This is intentional, as it makes the following code trivial to reason about: + +```rust +{ + use a::{impl TheTrait for TheType}; // <-- Clearly unused, no hidden interdependencies. + { + use b::{impl TheTrait for TheType}; + // ... + } +} +``` + +(The main importance here is to not allow non-obvious dependencies of imports. Implementations can still access associated items of a *specific* other implementation by bringing it into a nested scope or binding to its associated items elsewhere. See also [independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations].) + +## Binding choice by implementations' bounds +[binding-choice-by-implementations-bounds]: #binding-choice-by-implementations-bounds + +Implementations bind to other implementations as follows: + +| `where`-clause¹ on `impl`? | binding-site of used trait | monomorphised by used trait? | +|-|-|-| +| Yes. | Bound at each binding-site of `impl`. | Yes, like-with or as-part-of type parameter distinction. | +| No. | Bound once at definition-site of `impl`. | No. | + +¹ Or equivalent generic type parameter bound, where applicable. For all purposes, this RFC treats them as semantically interchangeable. + +A convenient way to think about this is that *`impl`-implementations with bounds are blanket implementations over `Self` in different implementation environments*. + +Note that `Self`-bounds on associated functions do **not** cause additional monomorphic variants to be emitted, as these continue to only filter the surrounding implementation. + +Consider the following code with attention to the where clauses: + +```rust +struct Type; + +// ❶ + +trait Trait { fn function(); } +impl Trait for Type { fn function() { println!("global"); } } + +trait Monomorphic { fn monomorphic(); } +impl Monomorphic for Type { + fn monomorphic() { Type::function() } +} + +trait MonomorphicSubtrait: Trait { + fn monomorphic_subtrait() { Self::function(); } +} +impl MonomorphicSubtrait for Type {} + +trait Bounded { fn bounded(); } +impl Bounded for Type where Type: Trait { + fn bounded() { Type::function(); } +} + +trait BoundedSubtrait: Trait { + fn bounded_subtrait() { Type::function(); } +} +impl BoundedSubtrait for Type where Type: Trait {} + +trait FnBoundedMonomorphic { + fn where_trait() where Self: Trait { Self::function(); } + fn where_monomorphic_subtrait() where Self: MonomorphicSubtrait { Self::monomorphic_subtrait(); } +} +impl FnBoundedMonomorphic for Type {} + +trait NestedMonomorphic { fn nested_monomorphic(); } + +trait BoundedOnOther { fn bounded_on_other(); } +impl BoundedOnOther for () where Type: Trait { + fn bounded_on_other() { Type::function(); } +} + +Type::function(); // "global" +Type::monomorphic(); // "global" +Type::monomorphic_subtrait(); // "global" +Type::bounded(); // "global" +Type::bounded_subtrait(); // "global" +Type::where_trait(); // "global" +Type::where_monomorphic_subtrait(); // "global" +Type::nested_monomorphic(); // "scoped" +()::bounded_on_other(); // "global" + +{ + // ❷ + use impl Trait for Type { + fn function() { + println!("scoped"); + } + } + + // use impl FnBoundedMonomorphic for Type {} + // error: the trait bound `Type: MonomorphicSubtrait` is not satisfied + + Type::function(); // "scoped" + Type::monomorphic(); // "global" + // Type::monomorphic_subtrait(); // error; shadowed by scoped implementation + Type::bounded(); // "scoped" + Type::bounded_subtrait(); // "scoped" + Type::where_trait(); // "global" + Type::where_monomorphic_subtrait(); // "global" + Type::nested_monomorphic(); // "scoped" + ()::bounded_on_other(); // "global" + + { + // ❸ + use impl MonomorphicSubtrait for Type {} + use impl FnBoundedMonomorphic for Type {} + + impl NestedMonomorphic for Type { + fn nested_monomorphic() { Type::function() } + } + + Type::function(); // "scoped" + Type::monomorphic(); // "global" + Type::monomorphic_subtrait(); // "scoped" + Type::bounded(); // "scoped" + Type::bounded_subtrait(); // "scoped" + Type::where_trait(); // "scoped" + Type::where_monomorphic_subtrait(); // "scoped" + Type::nested_monomorphic(); // "scoped" + ()::bounded_on_other(); // "global" + } +} +``` + +The numbers ❶, ❷ and ❸ mark relevant item scopes. + +Generic item functions outside `impl` blocks bind and behave the same way as generic `impl`s with regard to scoped `impl Trait for Type`. + +### `Trait` / `::function` + +This is a plain monomorphic implementation with no dependencies. As there is a scoped implementation at ❷, that one is used in scopes ❷ and ❸. + +### `Monomorphic` / `::monomorphic` + +Another plain monomorphic implementations. + +As there is no bound, an implementation of `Trait` is bound locally in ❶ to resolve the `Type::function()`-call. + +This means that even though a different `use impl Trait for Type …` is applied in ❷, the global implementation remains in use when this `Monomorphic` implementation is called into from there and ❸. + +Note that the use of `Self` vs. `Type` in the non-default function body does not matter at all! + +### `MonomorphicSubtrait` / `::monomorphic_subtrait` + +Due to the supertrait, there is an implied bound `Self: Trait` *on the trait definition, but not on the implementation*. + +This means that the implementation remains monomorphic, and as such depends on the specific (global) implementation of `Trait` in scope at the `impl MonomorphicSubtrait …` in ❶. + +As this `Trait` implementation is shadowed in ❷, the `MonomorphicSubtrait` implementation is shadowed for consistency of calls to generics bounded on both traits. + +In ❸ there is a scoped implementation of `MonomorphicSubtrait`. As the default implementation is monomorphised for this implementation, it binds to the scoped implementation of `Trait` that is in scope here. + +### `Bounded` / `::bounded` + +The `Type: Trait` bound (can be written as `Self: Trait` – they are equivalent.) selects the `Bounded`-binding-site's `Type: Trait` implementation to be used, rather than the `impl Bounded for …`-site's. + +In ❶, this resolves to the global implementation as expected. + +For the scopes ❷ and ❸ together, `Bounded` gains one additional monomorphisation, as here another `Type: Trait` is in scope. + +### `BoundedSubtrait` / `::bounded_subtrait` + +As with `MonomorphicSubtrait`, the monomorphisation of `impl BoundedSubtrait for Type …` that is used in ❶ is shadowed in ❷. + +However, due to the `where Type: Trait` bound *on the implementation*, that implementation is polymorphic over `Trait for Type` implementations. This means a second monomorphisation is available in ❷ and its nested scope ❸. + +### `FnBoundedMonomorphic` + +`FnBoundedMonomorphic`'s implementations are monomorphic from the get-go just like `Monomorphic`'s. + +Due to the narrower bounds on functions, their availability can vary between receivers but always matches that of the global *implementation environment*: + +#### `::where_trait` + +Available everywhere since `Type: Trait` is in scope for both implementations of `FnBoundedMonomorphic`. + +In ❶, this resolves to the global implementation. + +In ❷, this *still* calls the global `::function()` implementation since the global `FnBoundedMonomorphic` implementation is *not* polymorphic over `Type: Trait`. + +In ❸, `FnBoundedMonomorphic` is monomorphically reimplemented for `Type`, which means it "picks up" the scoped `Type: Trait` implementation that's in scope there from ❷. + +#### `::where_monomorphic_subtrait` + +In ❶, this resolves to the global implementation. + +In ❷, this *still* calls the global `::monomorphic_subtrait()` implementation since the global `FnBoundedMonomorphic` implementation is *not* polymorphic over `Type: Trait`. + +Note that `FnBoundedMonomorphic` *cannot* be reimplemented in ❷ since the bound `Type: MonomorphicSubtrait` on its associated function isn't available in that scope, which would cause a difference in the availability of associated functions (which would cause a mismatch when casting to `dyn FnBoundedMonomorphic`). + +> It may be better to allow `use impl FnBoundedMonomorphic for Type {}` without `where_monomorphic_subtrait` in ❷ and disallow incompatible unsizing instead. I'm not sure about the best approach here. + +In ❸, `FnBoundedMonomorphic` is monomorphically reimplemented for `Type`, which means it "picks up" the scoped `Type: Trait` implementation that's in scope there from ❷. + +### `NestedMonomorphic` / `::nested_monomorphic` + +The global implementation of `NestedMonomorphic` in ❸ the binds to the scoped implementation of `Trait` on `Type` from ❷ internally. This allows outside code to call into that function indirectly without exposing the scoped implementation itself. + +### `BoundedOnOther` / `::bounded_on_other` + +As this discrete implementation's bound isn't over the `Self` type (and does not involved generics), it continues to act only as assertion and remains monomorphic. + +## Binding and generics + +`where`-clauses without generics or `Self` type, like `where (): Debug`, **do not** affect binding of implementations within an `impl` or `fn`, as the non-type-parameter-type `()` is unable to receive an *implementation environment* from the discretisation site. + +However, `where (): From` **does** take scoped implementations into account because the blanket `impl From for U where T: Into {}` is sensitive to `T: Into<()>` which is part of the *implementation environment* captured in `T`! + +This sensitivity even extends to scoped `use impl From for ()` at the discretisation site, as the inverse blanket implementation of `Into` creates a scoped implementation of `Into` wherever a scoped implementation of `From` exists. +This way, existing symmetries are fully preserved in all contexts. + +## Implicit shadowing of subtrait implementations + +Take this code for example: + +```rust +use std::ops::{Deref, DerefMut}; + +struct Type1(Type2); +struct Type2; + +impl Deref for Type1 { + type Target = Type2; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Type1 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +fn function1(_x: impl Deref + DerefMut) {} +fn function2(x: impl DerefMut) { + x.deref(); +} + +{ + use impl Deref for Type1 { + type Target = (); + + fn deref(&self) -> &Self::Target { + &() + } + } + + // function1(Type1(Type2)); // <-- Clearly impossible. + // function2(Type1(Type2)); // <-- Unexpected behaviour if allowed. +} +``` + +Clearly, `function1` cannot be used here, as its generic bounds would have to bind to incompatible implementations. + +But what about `function2`? Here, the bound is implicit but `Deref::deref` could still be accessed if the function could be called. For type compatibility, this would have to be the shadowed global implementation, which is most likely unintended decoherence. + +As such, **shadowing a trait implementation also shadows all respective subtrait implementations**. Note that the subtrait *may* still be immediately available (again), if it is implemented with a generic target and all bounds can be satisfied in the relevant scope: + +```rust +trait Trait1 { + fn trait1(&self); +} +trait Trait2: Trait1 { // <-- Subtrait of Trait1. + fn uses_trait1(&self) { + self.trait1(); + } +} +impl Trait2 for T {} // <-- Blanket implementation with bounds satisfiable in scope. + +struct Type; +impl Trait1 for Type { + fn trait1(&self) { + print!("global"); + } +} + +{ + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped"); + } + } + + Type.uses_trait1(); // Works, prints "scoped". +} +``` + +If a subtrait implementation is brought into scope, it must be either an implementation with a generic target, or an implementation on a discrete type making use of the identical supertrait implementations in that scope. (This rule is automatically fulfilled by scoped implementation definitions, so it's only relevant for which scoped implementations can be imported via `use`-declaration.) + +## Independent trait implementations on discrete types may still call shadowed implementations +[independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations]: #independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations + +Going back to the previous example, but now implementing `Trait2` independently without `Trait1` in its supertraits: + +```rust +trait Trait1 { + fn trait1(&self); +} +trait Trait2 { // <-- Not a subtrait of `Trait1`. + fn uses_trait1(&self); +} +impl Trait2 for Type { // <-- Implementation on discrete type. + fn uses_trait1(&self) { + self.trait1(); + } +} + +struct Type; +impl Trait1 for Type { + fn trait1(&self) { + print!("global"); + } +} + +{ + use impl Trait1 for Type { + fn trait1(&self) { + print!("scoped"); + } + } + + Type.uses_trait1(); // Works, prints "global". +} +``` + +In this case, the implementation of `Trait2` is *not* shadowed at all. Additionally, since `self.trait1();` here binds `Trait` on `Type` directly, rather than on a bounded generic type parameter, it uses whichever `impl Trait1 for Type` is in scope *where it is written*. + +## Resolution on generic type parameters +[resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters + +Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `use impl Trait for T`. + +However, a blanket implementation can only be bound on a generic type parameter iff its bounds are fully covered by the generic type parameter's bounds and other available trait implementations on the generic type parameter, in the same way as this applies for global implementations. + +## Method resolution to scoped implementation without trait in scope + +[Method calls] can bind to scoped implementations even when the declaring trait is not separately imported. For example: + +```rust +struct Type; +struct Type2; + +mod nested { + trait Trait { + fn method(&self) {} + } +} + +use impl nested::Trait for Type {} +impl nested::Trait for Type2 {} + +Type.method(); // Compiles. +Type2.method(); // error[E0599]: no method named `method` found for struct `Type2` in the current scope +``` + +This also equally (importantly) applies to scoped implementations imported from elsewhere. + +[Method calls]: https://doc.rust-lang.org/book/ch05-03-method-syntax.html#method-syntax + +## Scoped implementations do not implicitly bring the trait into scope + +This so that no method calls on other types become ambiguous: + +```rust +struct Type; +struct Type2; + +mod nested { + trait Trait { + fn method(&self) {} + } + + trait Trait2 { + fn method(&self) {} + } +} + +use nested::Trait2; +impl Trait2 for Type {} +impl Trait2 for Type2 {} + +use impl nested::Trait for Type {} +impl nested::Trait for Type2 {} + +Type.method(); // Compiles, binds to scoped implementation of `Trait`. +Type2.method(); // Compiles, binds to global implementation of `Trait2`. +``` + +(If `Trait` was not yet globally implemented for `Type2`, and `Trait` and `Type2` were defined in other crates, then bringing `Trait` into scope here could introduce instability towards that implementation later being added in one of those crates.) + +## Shadowing with different bounds + +Scoped implementations may have different bounds compared to an implementation they (partially) shadow. The compiler will attempt to satisfy those bounds, but if they are not satisfied, then the other implementation is not shadowed for that set of generic type parameters and no additional warning or error is raised. + +(Warnings for e.g. unused scoped implementations and scoped implementations that only shadow a covering global implementation are still applied as normal. It's just that partial shadowing with different bounds is likely a common use-case in macros.) + +```rust +struct Type1; +struct Type2; + +trait Trait1 { + fn trait1() { + println!("1"); + } +} +impl Trait1 for T {} // <-- + +trait Trait2 { + fn trait2() { + println!("2"); + } +} +impl Trait2 for Type2 {} // <-- + +trait Say { + fn say(); +} +impl Say for T +where + T: Trait1, // <-- +{ + fn say() { + T::trait1(); + } +} + +{ + use impl Say for T + where + T: Trait2 // <-- + { + fn say() { + T::trait2(); + } + } + + Type1::say(); // 1 + Type2::say(); // 2 +} +``` + +## No priority over type-associated methods + +Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. + +## Coercion to trait objects + +Due to the coercion into a trait object in the following code, the scoped implementation becomes attached to the value through the pointer meta data. This means it can then be called from other scopes: + +```rust +use std::fmt::{self, Display, Formatter}; + +fn function() -> &'static dyn Display { + use impl Display for () { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "scoped") + } + } + + &() +} + +println!("{}", function()); // "scoped" +``` + +This behaves exactly as a global implementation would. + +Note that the [`DynMetadata`]s of the reference returned above and one that uses the global implementation would compare as distinct even if both are "`&()`". + +[`DynMetadata`]: https://doc.rust-lang.org/stable/core/ptr/struct.DynMetadata.html + +## Interaction with return-position `impl Trait` + +Consider the following functions: + +```rust +trait Trait {} + +fn function() -> impl Trait { + use impl Trait for () {} + + () // Binds on trailing `()`-expression. +} + +fn function2() -> impl Trait { + use impl Trait for () {} + + {} // Binds on trailing `{}`-block used as expression. +} +``` + +In this case, the returned opaque types use the respective inner scoped implementation, as it binds on the `()` expression. + +The following functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: + +```rust +trait Trait {} + +fn function() -> impl Trait { + ^^^^^^^^^^ + use impl Trait for () {} + --------------------- + + // Cannot bind on implicit `()` returned by function body without trailing *Expression*. +} + +fn function2() -> impl Trait { + ^^^^^^^^^^ + use impl Trait for () {} + --------------------- + + return; // Cannot bind on `return` without expression. + ------- +} +``` + +(The errors should ideally also point at the scoped implementations here with a secondary highlight, and suggest stating the return value explicitly.) + +The binding must be consistent: + +```rust +trait Trait {} + +fn function() -> impl Trait { + // error: Inconsistent implementation of opaque return type. + if true { + use impl Trait for () {} + return (); + ---------- + } else { + use impl Trait for () {} + return (); + ^^^^^^^^^^ + } +} +``` + +This function *does* compile, as the outer scoped `impl Trait for ()` is bound on the `if`-`else`-expression as a whole. + +```rust +trait Trait {} + +fn function() -> impl Trait { + use impl Trait for () {} + + if true { + use impl Trait for () {} // warning: unused scoped implementation + () + } else { + use impl Trait for () {} // warning: unused scoped implementation + () + } +} +``` + +This compiles because the end of the function is not reachable: + +```rust +trait Trait {} + +fn function() -> impl Trait { + { + use impl Trait for () {} + return (); // Explicit `return` is required to bind in the inner scope. + } +} +``` + +## Static interception of dynamic calls + +As a consequence of binding outside of generic contexts, it *is* possible to statically wrap *specific* trait implementations on *concrete* types. This includes the inherent implementations on trait objects: + +```rust +use std::fmt::{self, Display, Formatter}; + +{ + use impl Display for dyn Display { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Restore binding to inherent global implementation within this function. + use ::{impl Display for dyn Display}; + + write!(f, "Hello! ")?; + d.fmt(f)?; + write!(f, " See you!") + } + } + + let question = "What's up?"; // &str + println!("{question}"); // "What's up?" + + let question: &dyn Display = &question; + println!("{question}"); // Binds to the scoped implementation; "Hello! What's up? See you!" +} +``` + +## Warnings + +### Unused scoped implementation +[unused-scoped-implementation]: #unused-scoped-implementation + +Scoped implementations and `use`-declarations of such (including those written as *ImplEnvironmentEntry*) receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. + +(rust-analyzer should suggest removing any unused `use`-declarations as fix in either case.) + +An important counter-example: + +Filename: library/src/lib.rs + +```rust +pub struct Type; +pub struct Generic; + +pub trait Trait {} +use impl Trait for Type {} + +pub type Alias = Generic; +``` + +Filename: main.rs +```rust +use std::any::TypeId; + +use library::{Alias, Generic, Type}; + +assert_ne!(TypeId::of::(), TypeId::of::>()); +``` + +Here, the scoped implementation `use impl Trait for Type {}` **is** accounted for as it is captured into the type identity of `Alias`. + +Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. If it can ensure that that is the case, a (different!) warning could in theory still be shown here. + +### Global trait implementation available +[global-trait-implementation-available]: #global-trait-implementation-available + +Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that *at all* shadow another scoped implementation. + +(Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) + +It would make sense to let the definitions and also alternatively specific global implementations of traits with high implementation stability requirements like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. + +### Self-referential bound of scoped implementation + +```rust +trait Foo { } + +use impl Foo for T where T: Foo { } + --------- ^^^^^^ +``` + +A Rust developer may want to write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. + +The warning should explain that and why the bound is impossible to satisfy. + +### Private supertrait implementation required by public implementation +[private-supertrait-implementation-required-by-public-implementation]: #private-supertrait-implementation-required-by-public-implementation + +Consider the following code: + +```rust +pub struct Type; + +use impl PartialEq for Type { + // ... +} + +pub use impl Eq for Type {} +``` + +Here, the public implementation relies strictly on the private implementation to also be available. This means it effectively cannot be imported in `use`-declarations outside this module. + +See also the error [incompatible-or-missing-supertrait-implementation]. + +### Public implementation of private trait/on private type + +The code + +```rust +struct Type; +trait Trait {} + +pub use impl Trait for Type {} + ^^^^^ ^^^^ +``` + +should produce two distinct warnings similarly to those for private items in public signatures, as the limited visibilities of `Type` and `Trait` independently prevent the implementation from being imported in modules for which it is declared as visible. + +### Scoped implementation is less visible than item/field it is captured in +[scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in + +The code + +```rust +pub struct Type; +pub struct Generic(U, V); + +trait Trait {} // <-- Visibility of the trait doesn't matter for *this* warning. + +use impl Trait for Type {} +----------------------- + +pub type Alias = Generic; + ^^^^ ^^^^ + +pub fn function(value: Generic) -> Generic { + ^^^^ ^^^^ ^^^^ ^^^^ + value +} + +pub struct Struct { + private: Generic, // This is fine. + pub public: Generic, + ^^^^ ^^^^ +} +``` + +should produce eight warnings (or four/three warnings with multiple primary spans each, if possible). The warning should explain that the type can't be referred to by fully specified name outside the crate/module and that the implementation may be callable from code outside the crate/module. + +If the binding is specified via inline *implementation environment*, then the warning should show up on the `Trait in module` span instead. + +Note that as with other private-in-public warnings, replacing + +```rust +use impl Trait for Type {} +``` + +with + +```rust +mod nested { + use super::{Trait, Type}; + pub use impl Trait for Type {} +} +use nested::{impl Trait for Type}; +``` + +in the code sample above should silence the warning. + +In some cases, adding `as Trait in ::` to the generic type argument could be suggested as quick-fix, though generally it's better to fix this warning by moving the scoped implementation into a nested scope or moving it into a module and importing it into nested scopes as needed. + +> This warning can't be suppressed for private traits because the presence of their scoped implementation on a generic type parameter still affects the `TypeId` of the capturing generic, which here is visible outside of the discretising module. + +### Imported implementation is less visible than item/field it is captured in +[imported-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #imported-implementation-is-less-visible-than-itemfield-it-is-captured-in + +This occurs under the same circumstances as above, except that + +```rust +trait Trait {} +use impl Trait for Type {} +``` + +is replaced with + +```rust +use a_crate::{ + Trait, + impl Trait for Type, +}; +``` + +(where here the implementation import is subsetting a blanket import, but that technicality isn't relevant. What matters is that the implementation is from another crate). + +If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in that public signature or item?). + +## Errors + +### Global implementation of trait where global implementation of supertrait is shadowed + +A trait cannot be implemented globally for a discrete type in a scope where the global implementation of any of its supertraits is shadowed on that type. + +```rust +struct Type; + +trait Super {} +trait Sub: Super {} + +impl Super for Type {} + +{ + use impl Super for Type {} + ----------------------- // <-- Scoped implementation defined/imported here. + + impl Sub for Type {} + ^^^^^^^^^^^^^^^^^ //<-- error: global implementation of trait where global implementation of supertrait is shadowed +} +``` + +### Negative scoped implementation +[negative-scoped-implementation]: #negative-scoped-implementation + +This occurs on all negative scoped implementations. Negative scoped implementations can be parsed, but are rejected shortly after macros are applied. + +```rust +struct Type; +trait Trait {} + +impl Trait for Type {} + +{ + use impl !Trait for Type {} + ^^^^^^^^^^^^^^^^^^^^^^^^ error: negative scoped implementation +} +``` + +### Incompatible or missing supertrait implementation +[incompatible-or-missing-supertrait-implementation]: #incompatible-or-missing-supertrait-implementation + +Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those being in scope too (to avoid perceived and hard to reason-about inconsistencies). + +```rust +struct Type; +trait Super {} +trait Sub: Super {} + +impl Super for Type {} + +mod nested { + pub use impl Super for Type {} + pub use impl Sub for Type {} +} + +use nested::{impl Sub for Type}; + ^^^^^^^^^^^^^^^^^ error: incompatible supertrait implementation +``` + +Rustc should suggest to import the required scoped implementation, if possible. + +See also the warning [private-supertrait-implementation-required-by-public-implementation]. See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] for a potential way to improve the ergonomics here. + +### Scoped implementation of external sealed trait +[scoped-implementation-of-external-sealed-trait]: #scoped-implementation-of-external-sealed-trait + +Given crate `a`: + +```rust +mod private { + pub trait Sealing {} +} +use private::Sealing; + +pub trait Sealed: Sealing {} + +pub use impl Sealed for T {} // Ok. +``` + +And crate `b`: + +```rust +use a::{ + Sealed, + impl Sealed for usize, // Ok. +}; + +use impl Sealed for () {} // Error. + ^^^^^^ +``` + +Crate `b` cannot define scoped implementations of the external sealed trait `Sealed`, but can still import them. + +See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. + +## Behaviour change/Warning: `TypeId` of implementation-aware generic discretised using generic type parameters +[behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters + +As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for [implementation-aware-generics] they discretise using type parameters may be called on the same value instance. + +Notably, this affects `TypeId::of::()` in implementations with most generic targets, but not in unspecific blanket implementations on the type parameter itself. + +This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. + +In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show only the expected `TypeId` distinction. + +Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can be used, as tuples are [implementation-invariant-generics]. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +## First-party implementation assumptions in macros +[first-party-implementation-assumptions-in-macros]: #first-party-implementation-assumptions-in-macros + +If a macro outputs a call of the form `<$crate::Type as $crate::Trait>::method()`, it can currently make safety-critical assumptions about implementation details of the `method` that is called iff implemented in the same crate. + +(This should also be considered relevant for library/proc-macro crate pairs where the macro crate is considered an implementation detail of the library even where the macro doesn't require an `unsafe` token in its input, even though "crate privacy" currently isn't formally representable towards Cargo.) + +As such, **newly allowing the global trait implementation to be shadowed here can introduce soundness holes** iff `Trait` is not `unsafe` or exempt from scoped implementations. + +(I couldn't come up with a good example for this. There might be a slim chance that it's not actually a practical issue in the ecosystem. Unfortunately, this seems to be very difficult to lint for.) + +There are a few ways to mitigate this, but they all have significant drawbacks: + +- Opt-in scoped-`impl Trait` transparency for macros + + This would make scoped `impl Trait for Type`s much less useful, as they couldn't be used with for example some derive macros by default. It would also be necessary to teach the opt-in along with macros, which may not be realistic considering existing community-made macro primers. + + Implementation is likely complicated because many procedural macros emit tokens only with `Span::call_site()` hygiene, so information on the distinct binding site origin may not be readily available. + + This could be limited to existing kinds of macro definitions, so that future revised macro systems can be opted in by default. Future macros could use an `unsafe` trait instead to assume an implementation, or make use of scoped `impl Trait for Type` to enforce a specific implementation in their output. + + Drawback: Whether globally implemented behaviour can be changed by the consumer would depend on the macro. It would be good to surface a transparency opt-in in the documentation here. + +- Opt-in scoped-`impl Trait` *priority* for macros + + This would preserve practical usefulness of the proposed feature in most cases. + + This would add significant complexity to the feature, as resolution of scoped implementations wouldn't be exactly the same as for other items. (We should otherwise warn if a scoped `impl Trait for Type` outside a macro shadows binding a global implementation inside of it though, so at least the feature implementation complexity may be net zero in this regard.) + + This could be limited to existing kinds of macro definitions, with the same implications as for opt-in transparency above. + + Drawback: Whether globally implemented behaviour can be changed by the consumer would depend on the macro. It would be good to surface a priority opt-in in the documentation here. + +- Forbid scoped `impl Trait for Type` if `Trait` and `Type` are from the same crate + + This would at best be a partial fix and would block some interesting uses of [using-scoped-implementations-to-implement-external-traits-on-external-types]. + +## Unexpected behaviour of `TypeId::of::()` in implementations on generics in the consumer-side presence of scoped implementations and `transmute` + +As explained in [rustdoc-documentation-changes], [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s as e.g. for the `HashSet`s inside the type-erased value-keyed collection like the `ErasedHashSet` example in the [typeid-of-generic-type-parameters-opaque-types] section. + +This use case appears to be niche enough in Rust to not have an obvious example on crates.io, but see [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a lint that aims to mitigate issues in this regard and could be used to survey potential issues. + +## More `use`-declaration clutter, potential inconsistencies between files + +If many scoped implementations need to be imported, this could cause the list of `use`-declarations to become less readable. If there are multiple alternatives available, inconsistencies could sneak in between modules (especially if scoped `impl Trait for Type` is used in combination with [specialisation](https://rust-lang.github.io/rfcs/1210-impl-specialization.html)). + +This can largely be mitigated by centralising a crate's scoped trait imports and implementations in one module, then wildcard-importing its items: + +```rust +// lib.rs +mod scoped_impls; +use scoped_impls::*; +``` + +```rust +// scoped_impls.rs +use std::fmt::Debug; + +use a::{TypeA, TraitA}; +use b::{TypeB, TraitB}; + +pub use a_b_glue::{impl TraitA for TypeB, impl TraitB for TypeA}; +// ... + +pub use impl Debug for TypeA { + // ... +} +pub use impl Debug for TypeB { + // ... +} + +// ... +``` + +```rust +// other .rs files +use crate::scoped_impls::*; +``` + +## Type inference has to consider both scoped and global implementations + +Complexity aside, this could cause compiler performance issues since caching would be less helpful. + +Fortunately, at least checking whether scoped implementations exist at all for a given trait and item scope should be reasonably inexpensive, so this hopefully won't noticeably slow down compilation of existing code. + +That *implementation environment* binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. + +## Cost of additional monomorphised implementation instances + +The additional instantiations of implementations resulting from [binding-choice-by-implementations-bounds] could have a detrimental effect on compile times and .text size (depending on optimisations). + +This isn't unusual for anything involving *GenericParams*, but use of this feature could act as a multiplier to some extent. It's likely a good idea to evaluate relatively fine-grained caching in this regard, if that isn't in place already. + +## Split type identity may be unexpected +[split-type-identity-may-be-unexpected]: #split-type-identity-may-be-unexpected + +Consider crates like `inventory` or Bevy's systems and queries. + +There may be tricky to debug issues for their consumers if a `TypeId` doesn't match between uses of generics with superficially the same type parameters, especially without prior knowledge of distinction by captured *implementation environments*. + +A partial mitigation would be to have rustc include captured scoped implementations on generic type parameters when printing types, but that wouldn't solve the issue entirely. + +Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct but bound-irrelevant captured implementations directly, as long as only these top-level implementations differ and no nested captured *implementation environments* do. + +## Marking a generic as implementation-invariant is a breaking change + +This concerns the split of [implementation-aware-generics] and [implementation-invariant-generics]. + +"Implementation-aware" is the logic-safe default. + +"Implementation-invariant" has better ergonomics in some cases. + +It would be great to make moving from the default here only a feature addition. To do this, a new coherence rule would likely have to be introduced to make implementations conflict if any type becoming implementation-invariant would make them conflict, and additionally to make such implementations shadow each other (to avoid all-too-unexpected silent behaviour changes). + +However, even that would not mitigate the behaviour change of type-erasing collections that are keyed on such generics that become type-invariant later, so making this a breaking change is simpler and overall more flexible. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Avoid newtypes' pain points + +Alternative keywords: ergonomics and compatibility. + +### Recursively dependent `#[derive(…)]` + +Many derives, like `Clone`, `Debug`, partial comparisons, `serde::{Deserialize, Serialize}` and `bevy_reflect::{FromReflect, Reflect}` require the trait to be implemented for each field type. Even with the more common third-party traits like Serde's, there are many crates with useful data structures that do not implement these traits directly. + +As such, glue code is necessary. + +#### Current pattern + +Some crates go out of their way to provide a compatibility mechanism for their derives, but this is neither the default nor has it (if available) any sort of consistency between crates, which means finding and interacting with these mechanisms requires studying the crate's documentation in detail. + +For derives that do not provide such a mechanism, often only newtypes like `NewSerdeCompatible` and `NewNeitherCompatible` below can be used. However, these do not automatically forward all traits (and forwarding implementations may be considerably more painful than the `derive`s), so additional glue code between glue crates may be necessary. + +```rust +use bevy_reflect::Reflect; +use serde::{Deserialize, Serialize}; + +use bevy_compatible::BevyCompatible; +use neither_compatible::NeitherCompatible; +use serde_compatible::SerdeCompatible; + +// I could not actually find much information on how to implement the Bevy-glue. +// I assume it's possible to provide at least this API by creating a newtype and implementing the traits manually. +use bevy_compatible_serde_glue::BevyCompatibleDef; +use neither_compatible_bevy_glue::NewNeitherCompatible; // Assumed to have `From`, `Into` conversions. +use neither_compatible_serde_glue::NeitherCompatibleDef; +use serde_compatible_bevy_glue::NewSerdeCompatible; // Assumed to have `From`, `Into` conversions. + +/// A typical data transfer object as it may appear in a service API. +#[derive(Deserialize, Serialize, Reflect)] +#[non_exhaustive] // Just a reminder, since the fields aren't public anyway. +pub struct DataBundle { + // Serde provides a way to use external implementations on fields (but it has to be specified for each field separately). + // Bevy does not have such a mechanism so far, so newtypes are required. + // The newtypes should be an implementation detail, so the fields are (for consistency all) private. + #[serde(with = "NewSerdeCompatibleDef")] + serde: NewSerdeCompatible, + #[serde(with = "BevyCompatibleDef")] + bevy: BevyCompatible, + #[serde(with = "NewNeitherCompatibleDef")] + neither: NewNeitherCompatible, +} + +// Some of the newtypes don't implement `Default` (maybe it was added to the underlying types later and the glue crate doesn't want to bump the dependency), +// so this has to be implemented semi-manually instead of using the `derive`-macro. +impl Default for DataBundle { + fn default() -> Self { + DataBundleParts::default().into() + } +} + +// If the Bevy glue doesn't forward the Serde implementations, this is necessary. +#[derive(Deserialize, Serialize)] +#[serde(remote = "NewSerdeCompatible")] +#[serde(transparent)] +struct NewSerdeCompatibleDef(SerdeCompatible); + +// Same as above, but here the implementation is redirected to another glue crate. +#[derive(Deserialize, Serialize)] +#[serde(remote = "NewNeitherCompatible")] +#[serde(transparent)] +struct NewNeitherCompatibleDef(#[serde(with = "NeitherCompatibleDef")] NeitherCompatible); + +impl DataBundle { + // These conversions are associated functions for discoverability. + pub fn from_parts(parts: DataBundleParts) -> Self { + parts.into() + } + pub fn into_parts(self) -> DataBundleParts { + self.into() + } + + // Necessary to mutate multiple fields at once. + pub fn parts_mut(&mut self) -> DataBundlePartsMut<'_> { + DataBundlePartsMut { + serde: &mut self.serde.0, + bevy: &mut self.bevy, + neither: &mut self.neither.0, + } + } + + // Accessors to the actual instances with the public types. + pub fn serde(&self) -> &SerdeCompatible { + &self.serde.0 + } + pub fn serde_mut(&mut self) -> &mut SerdeCompatible { + &mut self.serde.0 + } + + // This also uses an accessor just for consistency. + pub fn bevy(&self) -> &BevyCompatible { + &self.bevy + } + pub fn bevy_mut(&mut self) -> &mut BevyCompatible { + &mut self.bevy + } + + // More accessors. + pub fn neither(&self) -> &NeitherCompatible { + &self.neither.0 + } + pub fn neither_mut(&mut self) -> &mut NeitherCompatible { + &mut self.neither.0 + } +} + +// Conversions for convenience +impl From for DataBundle { + fn from(value: DataBundleParts) -> Self { + Self { + serde: value.serde.into(), + bevy: value.bevy.into(), + neither: value.neither.into(), + } + } +} + +impl From for DataBundleParts { + fn from(value: DataBundle) -> Self { + Self { + serde: value.serde.into(), + bevy: value.bevy, + neither: value.neither.into(), + } + } +} + +/// Used to construct and destructure [`DataBundle`]. +#[derive(Default)] // Assume that all the actual field types have useful defaults. +#[non_exhaustive] +pub struct DataBundleParts { + pub serde: SerdeCompatible, + pub bevy: BevyCompatible, + pub neither: NeitherCompatible, +} + +/// Return type of [`DataBundle::parts_mut`]. +#[non_exhaustive] +pub struct DataBundlePartsMut<'a> { + pub serde: &'a mut SerdeCompatible, + pub bevy: &'a mut BevyCompatible, + pub neither: &'a mut NeitherCompatible, +} +``` + +If two traits that require newtype wrappers need to be added for the same type, the process can be even more painful than what's shown above, involving `unsafe` reinterpret casts to borrow a wrapped value correctly as each newtype and forwarding-implementing each trait manually if no transparent derive is available. + +#### With scoped `impl Trait for Type` + +Scoped `impl Trait for Type` eliminates these issues, in a standardised way that doesn't require any special consideration from the trait or derive crates: + +```rust +use bevy_reflect::Reflect; +use serde::{Deserialize, Serialize}; + +use bevy_compatible::BevyCompatible; +use neither_compatible::NeitherCompatible; +use serde_compatible::SerdeCompatible; + +// I could not actually find much information on how to implement Bevy-glue. +// It's about the same as manually implementing the traits for newtypes, though. +// Since many traits are required for `bevy_reflect`'s derives, those glue crates use the prelude pattern and provide one for each target type. +use bevy_compatible_serde_glue::{ + impl Deserialize<'_> for BevyCompatible, + impl Serialize for BevyCompatible, +}; +use neither_compatible_bevy_glue::preludes::neither_compatible::*; +use neither_compatible_serde_glue::{ + impl Deserialize<'_> for NeitherCompatible, + impl Serialize for NeitherCompatible, +}; +use serde_compatible_bevy_glue::preludes::serde_compatible::*; + +/// A typical data transfer object as it may appear in a service API. +#[derive(Default, Deserialize, Serialize, Reflect)] +#[non_exhaustive] +pub struct DataBundle { + // Everything just works. + pub serde: SerdeCompatible, + pub bevy: BevyCompatible, + pub neither: NeitherCompatible, +} + +// `Default` was derived normally. +// No glue for the glue is necessary. +// No conversions are needed to construct or destructure. +// `&mut`-splitting is provided seamlessly by Rust. +// No accessors are needed since the fields are public. +``` + +Even in cases where the glue API cannot be removed, it's still possible to switch to this simplified, easier to consume implementation and deprecate the original indirect API. + +Note that the imported scoped implementations are *not* visible in the public API here, since they do not appear on generic type parameters in public items. There may still be situations in which defining a type alias is necessary to keep some scoped implementations away from generic type parameters. In some cases, it could be enough to add `as Trait in ::` to generic type arguments to restore their *implementation environment* to contain global implementations only. + +> In some cases, where a field type is quoted in a derive macro directly, writing `(Type as Trait in module)` only there could *in theory* also work, but this would heavily depend on the macro's implementation details. See also [should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused]. + +Unlike with external newtypes, there are no potential conflicts beyond overlapping imports and definitions in the same scope. These conflicts can *always* be resolved both without editing code elsewhere and without adding an additional implementation: + +- either by narrowing a local blanket implementation, +- by narrowing a blanket implementation import to a subset of the external implementation, +- or at worst by moving a generic implementation into a submodule and importing it for discrete types. + +### Error handling and conversions +[error-handling-and-conversions]: #error-handling-and-conversions + +When implementing services, it's a common pattern to combine a framework that dictates function signatures with one or more unrelated middlewares that have their own return and error types. The example below is a very abridged example of this. + +Note that in either version, the glue code may be project-specific. Glue code is *very slightly* more concise when implemented with scoped `impl Trait for Type`, as intermediary `struct` definitions and the resulting field access can be avoided. + +#### Current pattern + +```rust +// crate `service` + +use framework::{Error, Returned}; +use middleware_a::{fallible_a, Error as ErrorA}; +use middleware_b::{fallible_b, Error as ErrorB}; + +use framework_middleware_a_glue::{IntoReturnedExt as _, NewErrorA}; +use framework_middleware_b_glue::{IntoReturnedExt as _, NewErrorB}; + +pub fn a() -> Result { + // A `try` block should work eventually, but it may be not much less verbose. + Ok((|| -> Result<_, NewErrorA> { + fallible_a()?; + Ok(fallible_a()?) + })()? + .into_returned()) +} + +pub fn b() -> Result { + // The same as above. + Ok((|| -> Result<_, NewErrorB> { + fallible_b()?; + Ok(fallible_b()?) + })()? + .into_returned()) +} + +pub fn mixed(condition: bool) -> Result { + // Neither 'NewError' type provided by third-party crates can be used directly here. + Ok((move || -> Result<_, NewError> { + Ok(if condition { + fallible_b()?; + fallible_a()?.into_returned() + } else { + fallible_a()?; + fallible_b()?.into_returned() + }) + })()?) +} + +// Custom glue to connect all three errors: +struct NewError(Error); +impl From for Error { + fn from(value: NewError) -> Self { + value.0 + } +} +impl From for NewError { + fn from(value: ErrorA) -> Self { + let intermediate: NewErrorA = value.into(); + Self(intermediate.into()) + } +} +impl From for NewError { + fn from(value: ErrorB) -> Self { + let intermediate: NewErrorB = value.into(); + Self(intermediate.into()) + } +} +``` + +```rust +use service::{a, b, mixed}; + +fn main() { + framework::setup() + .add_route("a", a) + .add_route("b", b) + .add_route("mixed", mixed) + .build() + .run(); +} +``` + +#### With scoped `impl Trait for Type` + +```rust +// crate `service` + +// More concise, since middleware errors are used only once in imports. +use framework::{Error, Returned}; +use middleware_a::fallible_a; +use middleware_b::fallible_b; + +// Note: It is often better to import `impl Into` here over `impl From`, +// since middleware types often don't appear in public signatures. +// +// If the target type of the import must appear as type parameter in a public signature, +// a module that is wildcard-imported into each function body can be used instead, +// which would amount to 6 additional and 2 modified lines here. +// +// This RFC includes a warning for unintentionally exposed scoped implementations. +use framework_middleware_a_glue::{ + impl Into for middleware_a::Returned, + impl Into for middleware_a::Error, +}; +use framework_middleware_b_glue::{ + impl Into for middleware_b::Returned, + impl Into for middleware_b::Error, +}; + +pub fn a() -> Result { + // It just works. + fallible_a()?; + Ok(fallible_a()?.into()) +} + +pub fn b() -> Result { + // Here too. + fallible_b()?; + Ok(fallible_b()?.into()) +} + +pub fn mixed(condition: bool) -> Result { + // This too just works, as conversions bind separately. + Ok(if condition { + fallible_b()?; + fallible_a()?.into() + } else { + fallible_a()?; + fallible_b()?.into() + }) +} + +// No custom glue is necessary at all. +``` + +```rust +// Unchanged. No change in the API of `service`, either. + +use service::{a, b, mixed}; + +fn main() { + framework::setup() + .add_route("a", a) + .add_route("b", b) + .add_route("mixed", mixed) + .build() + .run(); +} +``` + +Note that to export *discrete* scoped `impl Into` in addition to their scoped `impl From`, the glue crates can use the following pattern, which discretises the global implementation and as such binds to each scoped `impl From` in the respective exported scoped `impl Into`: + +```rust +pub use ::{ + impl Into for middleware_a::Returned, + impl Into for middleware_a::Error, +}; +``` + +## Preserve coherence + +### Cross-crate stability + +With this RFC, scopes are a 'mini version' of the environment that global implementations exist in. As this environment is sealed within one scope, and not composed from multiple crates that may update independently, the *orphan rule* is not necessary. + +*All other* coherence rules and (for exported implementations) rules for what is and is not a breaking change apply *within each scope exactly like for global implementations*. In particular: + +- Blanket implementations like + + ```rust + // (Does not compile!) + + use std::fmt::{Debug, LowerHex, Pointer}; + mod debug_by_lower_hex; + + use debug_by_lower_hex::{impl Debug for T}; // <-- + + use impl Debug for T { // <-- + // ... + } + ``` + + still conflict regardless of actual implementations of `LowerHex` and `Pointer` because they may overlap later and + +- because scoped implementation are *explicitly subset* where they are imported, *it is not a breaking change to widen an exported scoped implementation*. + + (This is part of the reason why scoped `impl Trait for Type`s are anonymous; names would make these imports more verbose rather than shorter, since the subsetting still needs to happen in every case.) + +### Logical consistency +[logical-consistency]: #logical-consistency + +Binding external top-level implementations to types is equivalent to using their public API in different ways, so no instance-associated consistency is expected here. Rather, values that are used in the same scope behave consistently with regard to that scope's visible implementations. + +#### of generic collections +[of-generic-collections]: #of-generic-collections + +Generics are trickier, as their instances often do expect trait implementations on generic type parameters that are consistent between uses but not necessarily declared as bounded on the struct definition itself. + +This problem is solved by making the `impl`s available to each type parameter part of the the type identity of the discretised host generic, including a difference in `TypeId` there as with existing monomorphisation. + +(See [type-parameters-capture-their-implementation-environment] and [type-identity-of-generic-types] in the [reference-level-explanation] above for more detailed information.) + +Here is an example of how captured *implementation environments* safely flow across module boundaries, often seamlessly due to type inference: + +```rust +pub mod a { + // ⓐ == ◯ + + use std::collections::HashSet; + + #[derive(PartialEq, Eq)] + pub struct A; + + pub type HashSetA = HashSet; + pub fn aliased(_: HashSetA) {} + pub fn discrete(_: HashSet) {} + pub fn generic(_: HashSet) {} +} + +pub mod b { + // ⓑ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + }; + + #[derive(PartialEq, Eq)] + pub struct B; + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + + pub type HashSetB = HashSet; // ⚠ + pub fn aliased(_: HashSetB) {} + pub fn discrete(_: HashSet) {} // ⚠ + pub fn generic(_: HashSet) {} +} + +pub mod c { + // ⓒ == ◯ + + use std::collections::HashSet; + + #[derive(PartialEq, Eq, Hash)] + pub struct C; + + pub type HashSetC = HashSet; + pub fn aliased(_: HashSetC) {} + pub fn discrete(_: HashSet) {} + pub fn generic(_: HashSet) {} +} + +pub mod d { + // ⓓ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + iter::once, + }; + + use super::{ + a::{self, A}, + b::{self, B}, + c::{self, C}, + }; + + use impl Hash for A { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for C { + fn hash(&self, _state: &mut H) {} + } + + fn call_functions() { + a::aliased(HashSet::new()); // ⓐ == ◯ + a::discrete(HashSet::new()); // ⓐ == ◯ + a::generic(HashSet::from_iter(once(A))); // ⊙ == ⓓ + + b::aliased(HashSet::from_iter(once(B))); // ⓑ + b::discrete(HashSet::from_iter(once(B))); // ⓑ + b::generic(HashSet::from_iter(once(B))); // ⊙ == ⓓ + + c::aliased(HashSet::from_iter(once(C))); // ⓒ == ◯ + c::discrete(HashSet::from_iter(once(C))); // ⓒ == ◯ + c::generic(HashSet::from_iter(once(C))); // ⊙ == ⓓ + } +} + +``` + +Note that the lines annotated with `// ⚠` produce a warning due to the lower visibility of the scoped implementation in `b`. + +Circles denote *implementation environments*: + +| | | +|-|-| +| ◯ | indistinct from global | +| ⓐ, ⓑ, ⓒ, ⓓ | respectively as in module `a`, `b`, `c`, `d` | +| ⊙ | caller-side | + +The calls infer discrete `HashSet`s with different `Hash` implementations as follows: + +| call in `call_functions` | `impl Hash` in | captured in/at | notes | +|-|-|-|-| +| `a::aliased` | - | `type` alias | The implementation cannot be 'inserted' into an already-specified type parameter, even if it is missing. | +| `a::discrete` | - | `fn` signature | See `a::aliased`. | +| `a::generic` | `d` | `once` call | | +| `b::aliased` | `b` | `type` alias | | +| `b::discrete` | `b` | `fn` signature | | +| `b::generic` | `d` | `once` call | `b`'s narrow implementation cannot bind to the opaque `T`. | +| `c::aliased` | `::` | `type` alias | Since the global implementation is visible in `c`. | +| `c::discrete` | `::` | `fn` signature | See `c::aliased`. +| `c::generic` | `d` | `once` call | The narrow global implementation cannot bind to the opaque `T`. | + +#### of type-erased collections +[of-type-erased-collections]: #of-type-erased-collections + +Type-erased collections such as the `ErasedHashSet` shown in [typeid-of-generic-type-parameters-opaque-types] require slightly looser behaviour, as they are expected to mix instances between environments where only irrelevant implementations differ (since they don't prevent this mixing statically like `std::collections::HashSet`, as their generic type parameters are transient on their methods). + +It is for this reason that the `TypeId` of generic type parameters disregards bounds-irrelevant implementations. + +The example is similar to the previous one, but `aliased` has been removed since it continues to behave the same as `discrete`. A new set of functions `bounded` is added: + +```rust +#![allow(unused_must_use)] // For the `TypeId::…` lines. + +trait Trait {} + +pub mod a { + // ⓐ == ◯ + + use std::{collections::HashSet, hash::Hash}; + + #[derive(PartialEq, Eq)] + pub struct A; + + pub fn discrete(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn generic(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn bounded(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } +} + +pub mod b { + // ⓑ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + }; + + use super::Trait; + + #[derive(PartialEq, Eq)] + pub struct B; + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + use impl Trait for B {} + + pub fn discrete(_: HashSet) { // ⚠⚠ + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn generic(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn bounded(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } +} + +pub mod c { + // ⓒ == ◯ + + use std::{collections::HashSet, hash::Hash}; + + use super::Trait; + + #[derive(PartialEq, Eq, Hash)] + pub struct C; + impl Trait for C {} + + pub fn discrete(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn generic(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } + pub fn bounded(_: HashSet) { + TypeId::of::>(); // ❶ + TypeId::of::(); // ❷ + } +} + +pub mod d { + // ⓓ + + use std::{ + collections::HashSet, + hash::{Hash, Hasher}, + iter::once, + }; + + use super::{ + a::{self, A}, + b::{self, B}, + c::{self, C}, + Trait, + }; + + use impl Hash for A { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for B { + fn hash(&self, _state: &mut H) {} + } + use impl Hash for C { + fn hash(&self, _state: &mut H) {} + } + + use impl Trait for A {} + use impl Trait for B {} + use impl Trait for C {} + + fn call_functions() { + a::discrete(HashSet::new()); // ⓐ == ◯ + a::generic(HashSet::from_iter(once(A))); // ⊙ == ⓓ + a::bounded(HashSet::from_iter(once(A))); // ⊙ == ⓓ + + b::discrete(HashSet::from_iter(once(B))); // ⓑ + b::generic(HashSet::from_iter(once(B))); // ⊙ == ⓓ + b::bounded(HashSet::from_iter(once(B))); // ⊙ == ⓓ + + c::discrete(HashSet::from_iter(once(C))); // ⓒ == ◯ + c::generic(HashSet::from_iter(once(C))); // ⊙ == ⓓ + c::bounded(HashSet::from_iter(once(C))); // ⊙ == ⓓ + } +} + +``` + +`// ⚠` and non-digit circles have the same meanings as above. + +The following table describes how the types are observed at runtime in the lines marked with ❶ and ❷. Types are denoted as if seen from the global *implementation environment* with differences written inline, which should resemble how they are formatted in compiler messages and tooling. + +| within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | +|-|-|-| +| `a::discrete` | `HashSet
` | `A` | +| `a::generic` | `HashSet` | `A` | +| `a::bounded` | `HashSet` | `A` ∘ `Hash in d` | +| `b::discrete` | `HashSet` | `B` | +| `b::generic` | `HashSet` | `B` | +| `b::bounded` | `HashSet` | `B` ∘ `Hash in d` | +| `c::discrete` | `HashSet` | `C` | +| `c::generic` | `HashSet` | `C` | +| `c::bounded` | `HashSet` | `C` ∘ `Hash in d` | + +The combination ∘ is not directly expressible in `TypeId::of::<>` calls (as even a direct top-level annotation would be ignored without bounds). Rather, it represents an observation like this: + +```rust +{ + use std::{any::TypeId, hash::Hash}; + + use a::A; + use d::{impl Hash for A}; + + fn observe() { + TypeId::of::(); // '`A` ∘ `Hash in d`' + } + + observe::(); +} +``` + +##### with multiple erased type parameters + +By replacing the lines + +```rust +TypeId::of::>(); // ❶ +TypeId::of::(); // ❷ +``` + +with + +```rust +TypeId::of::>(); // ❶ +TypeId::of::<(T)>(); // ❷ +``` + +(and analogous inside the discrete functions), the `TypeId` table above changes as follows: + +| within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | +|-|-|-| +| `a::discrete` | `HashSet<(A,)>` | `(A,)` | +| `a::generic` | `HashSet<(A as Hash in d + Trait in d,)>` | `(A,)` | +| `a::bounded` | `HashSet<(A as Hash in d + Trait in d,)>` | `(A` ∘ `Hash in d,)` | +| `b::discrete` | `HashSet<(B as Hash in `***`b`***` + Trait in`***` b`***`,)>` | `(B,)` | +| `b::generic` | `HashSet<(B as Hash in d + Trait in d,)>` | `(B,)` | +| `b::bounded` | `HashSet<(B as Hash in d + Trait in d,)>` | `(B` ∘ `Hash in d,)` | +| `c::discrete` | `HashSet<(C,)>` | `(C,)` | +| `c::generic` | `HashSet<(C as Hash in d + Trait in d,)>` | `(C,)` | +| `c::bounded` | `HashSet<(C as Hash in d + Trait in d,)>` | `(C` ∘ `Hash in d,)` | + +As you can see, the type identity of the tuples appears distinct when contributing to an implementation-aware generic's type identity but (along with the `TypeId`) remains appropriately fuzzy when used alone. + +This scales up to any number of type parameters used in implementation-invariant generics, which means an efficient `ErasedHashMap` can be constructed by keying storage on the `TypeId::of::<(K, V)>()` where `K: Hash + Eq` and `V` are the generic type parameters of its functions. + +### Logical stability + +- Non-breaking changes to external crates cannot change the meaning of the program. +- Breaking changes should result in compile-time errors rather than a behaviour change. + +This is another consequence of subsetting rather than named-model imports, as narrowing a scoped implementation can only make the `use`-declaration fail to compile, rather than changing which implementations are shadowed. + +Similarly, types of generics with different captured *implementation environments* are strictly distinct from each other, so that assigning them inconsistently does not compile. This is weighed somewhat against ease of refactoring, so in cases where a type parameter is inferred and the host is used in isolation, which are assumed to not care about implementation details like that, the code will continue to align with the definition instead of breaking. + +## Encourage readable code + +This RFC aims to further decrease the mental workload required for code review, by standardising glue code APIs to some degree and by clarifying their use in other modules. + +It also aims to create an import grammar that can be understood more intuitively than external newtypes when first encountered, which should improve the accessibility of Rust code somewhat. + +### Clear imports + +As scoped implementations bind implicitly like global ones, two aspects must be immediately clear at a glace: + +- *Which trait* is implemented? +- *Which type* is targeted? + +Restating this information in the `use`-declaration means that it is available without leaving the current file, in plaintext without any tooling assists. This is another improvement compared to newtypes or external definitions, where the relationship may not be immediately clear depending on their names. + +Spelling scoped implementation imports out with keywords rather than just symbols makes their purpose easy to guess for someone unfamiliar with the scoped `impl Trait for Type` feature, possibly even for most English-speaking developers unfamiliar with Rust. + +This is also true for blanket imports with `where`, which remain easy to parse visually due to the surrounding braces: + +```rust +use std::fmt::{Debug, Display, Pointer}; + +// `Debug` and `Display` all `Pointer`-likes as addresses. +// The `Display` import is different only to show the long form +// with `where`. It could be written like the `Debug` import. +use cross_formatting::by_pointer::{ + impl Debug for T, + {impl Display for T where T: Pointer}, +}; + +println!("{:?}", &()); // For example: 0x7ff75584c360 +println!("{}", &()); // For example: 0x7ff75584c360 +``` + +### Familiar grammar + +The grammar for scoped implementations differs from that for global implementations by only a prefixed `use` and an optional visibility. As such, it should be easy to parse for developers not yet familiar with scoped implementations specifically. + +The clear prefix (starting with at least two keywords instead of one) should still be enough to distinguish scoped implementations at a glance from global ones. + +The header (the part before the `{}` block) of global implementations is reused unchanged for scoped implementation imports, including all bounds specifications, so there is very little grammar to remember additionally in order to `use` scoped `impl Trait for Type`s. + +In each case, the meaning of identical grammar elements lines up exactly - only their context and perspective vary due to immediately surrounding tokens. + +(See [grammar-changes] for details.) + +### Stop tokens for humans + +When looking for the scoped implementation affecting a certain type, strict shadowing ensures that it is always the closest matching one that is effective. + +As such, readers can stop scanning once they encounter a match (or module boundary, whether surrounding or nested), instead of checking the entire file's length for another implementation that may be present in the outermost scope. + +Aside from *implementation environments* captured *inside* generics, scoped implementations cannot influence the behaviour of another file without being mentioned explicitly. + +## Unblock ecosystem evolution +[unblock-ecosystem-evolution]: #unblock-ecosystem-evolution + +As any number of scoped glue implementations can be applied directly to application code without additional compatibility shims, it becomes far easier to upgrade individual dependencies to their next major version. Compatibility with multiple versions of crates like Serde and `bevy_reflect` can be provided in parallel through officially supported glue crates. + +Additionally, scoped implementations are actually *more* robust than newtypes regarding certain breaking changes: + +A newtype that implements multiple traits could eventually gain a global blanket implementation of one of its traits for types that implement another of its traits, causing a conflict during the upgrade. + +In the presence of an overlapping scoped `impl Trait for Type`, the new blanket implementation is just unambiguously shadowed where it would conflict, which means no change is necessary to preserve the code's behaviour. A [global-trait-implementation-available] warning is still shown where applicable to alert maintainers of new options they have. + +(See also [glue-crate-suggestions] for possible future tooling related to this pattern.) + +### Side-effect: Parallelise build plans (somewhat) more + +Serde often takes a long time to build even without its macros. If another complex crate depends on it just to support its traits, this can significantly stretch the overall build time. + +If glue code for 'overlay' features like Serde traits is provided in a separate crate, that incidentally helps to reduce that effect somewhat: + +Since the glue forms a second dependency chain that normally only rejoins in application code, the often heavier core functionality of libraries can build in parallel to Serde and/or earlier glue. Since the glue chain is likely to be less code, it matters less for overall build time whether it has to wait for one or two large crates first. + +## Provide opportunities for rich tooling + +### Discovery of implementations + +As scoped implementations clearly declare the link between the trait and type(s) they connect, tools like rust-analyzer are able to index them and suggest imports where needed, just like for global traits. + +(At least when importing from another crate, the suggested import should be for a specific type or generic, even if the export in question is a blanket implementation. Other generics of the export can usually be preserved, though.) + +### Discovery of the feature itself + +In some cases (where a trait implementations cannot be found at all), tools can suggest creating a scoped implementation, unless adding it in that place would capture it as part of the *implementation environment* of a type parameter specified in an item definition visible outside the current crate. + +That said, it would be great if rust-analyzer could detect and suggest/enable feature-gated global implementations to some extent, with higher priority than creating a new scoped implementation. + +### Rich and familiar warnings and error messages + +Since scoped implementations work much like global ones, many of the existing errors and warnings can be reused with at most small changes. This means that, as developers become more familiar with either category of trait-related issues, they learn how to fix them for global and scoped implementations at the same time. + +The implementation of the errors and warnings in the compiler can also benefit from the existing work done for global implementations, or in some cases outright apply the same warning to both scoped and global implementations. + +Since available-but-not-imported scoped implementations are easily discoverable by the compiler, they can be used to improve existing errors like *error[E0277]: the trait bound `[…]` is not satisfied* and *error[E0599]: no method named `[…]` found for struct `[…]` in the current scope* with quick-fix suggestions also for using an existing scoped implementation in at least some cases. + +### Maintenance warnings for ecosystem evolution + +Scoped `impl Trait for Type`s lead to better maintenance lints: + +If a covering global implementation later becomes available through a dependency, a warning can be shown on the local trait implementation for review. (See [global-trait-implementation-available].) + +In the long run, this can lead to less near-duplicated functionality in the dependency graph, which can lead to smaller executable sizes. + +### Automatic documentation + +Scoped implementations can be documented and appear as separate item category in rustdoc-generated pages. + +Rustdoc should be able to detect and annotate captured scoped implementations in public signatures automatically. This, in addition to warnings, could be another tool to help avoid accidental exposure of scoped implementations. + +Implementation origin and documentation could be surfaced by rust-analyzer in relevant places. + +## Why specific [implementation-invariant-generics]? +[why-specific-implementation-invariant-generics]: #why-specific-implementation-invariant-generics + +This is a *not entirely clean* ergonomics/stability trade-off, as well as a clean resolution path for [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]. + +> It is also the roughest part of this proposal, in my eyes. If you have a better way of dealing with the aware/invariant distinction, please do suggest it! + +The main issue is that generics in the Rust ecosystem do not declare which trait implementations on their type parameters need to be consistent during their instances' lifetime, if any, and that traits like `PartialOrd` that do provide logical consistency guarantees over time are not marked as such in a compiler-readable way. + +Ignoring this and not having distinction of [implementation-aware-generics]' discretised variants would badly break logical consistency of generic collections like `BTreeSet`, which relies on `Ord`-consistency to function. + +On the other hand, certain types (e.g. references and (smart) pointers) that often wrap values in transit between modules *really* don't care about implementation consistency on these types. If these were distinct depending on available implementations on their values, it would create *considerable* friction while defining public APIs in the same scope as `struct` or `enum` definitions that require scoped implementations for `derive`s. + +Drawing a line manually here is an attempt to un-break this *by default* for the most common cases while maintaining full compatibility with existing code and keeping awareness of scoped `impl Trait for Type` entirely optional for writing correct and user-friendly APIs. + +As a concrete example, this ensures that `Box>>` is automatically interchangeable even if spelled out in the presence of scoped [error-handling-and-conversions] affecting `Error`, but that `BinaryHeap>` and `BinaryHeap>` don't mix. + +Functions pointers and closure trait( object)s should probably be fairly easy to pass around, with their internally-used bindings being an implementation detail. Fortunately, the Rust ecosystem already uses more specific traits for most configuration for better logical safety, so it's likely not too messy to make these implementation-invariant. + +Traits and trait objects cannot be implementation invariant by default (including for their associated types!) because it's already possible to define `OrderedExtend` and `OrderedIterator` traits with logical consistency requirement on `Ord` between them. + +## Efficient compilation +[efficient-compilation]: #efficient-compilation + +In theory, it should be possible to unify many instances of generic functions that may be polymorphic under this proposal cheaply before code generation. (Very few previously discrete implementations become polymorphic under scoped `impl Trait for Type`.) + +This is mainly an effect of [layout-compatibility] and [binding-choice-by-implementations-bounds], so that, where the differences are only bounds-irrelevant, generated implementations are easily identical in almost all cases. The exception here are [implementation-aware-generics]' `TypeId`s (see also [typeid-of-generic-type-parameters-opaque-types]). Checking for this exception should be cheap if done alongside checks for e.g. function non-constness if possible, which propagates identically from callee to caller. + +Given equal usage, compiling code that uses scoped implementations could as such be slightly more efficient compared to use of newtypes and the resulting text size may be slightly smaller in some cases where newtype implementations are inlined differently. + +The compiler should treat implementations of the same empty trait on the same type as identical early on, so that no code generation is unnecessarily duplicated. However, unrelated empty-trait implementations must still result in distinct `TypeId`s when captured in a generic type parameter and observed there by a `where`-clause or through nesting in an implementation-aware generic. + +## Alternatives + +### Named implementations + +Use of named implementations is not as obvious as stating the origin-trait-type triple in close proximity, so code that uses named implementations tends to be harder to read. + +Like named implementations, the scope-identified implementations proposed here can be written concisely in generic parameter lists (as `Type as Trait in module`), limiting the code-writing convenience advantage of named implementations. Where needed, the module name can be chosen to describe specific function, e.g. exporting reverse-ordering `Ord` and `PartialOrd` implementations from a module called `reverse`. + +If named implementations can't be brought into scope (see Genus in [lightweight-flexible-object-oriented-generics]), that limits their practical application to where they can be captured in [implementation-aware-generics]. Bringing named implementations into scope would be more verbose than for module-trait-type-identified as subsetting would still be required to preserve useful room for library crate evolution. + +### Weakening coherence rules + +There is likely still some leeway here before the Rust ecosystem becomes brittle, but at least the orphan rule specifically is essential for ensuring that global trait implementations do not lead to hard ecosystem splits due to strictly incompatible framework crates. + +If *other* coherence rules are relaxed, scoped `impl Trait for Type` also benefits immediately since it is subject to all of them. + +### Crate-private implementations as distinct feature + +There is a previous [RFC: Hidden trait implementations] from 2018-2021 where the result was general acceptance, but postponement for logistical reasons. + +Scoped `impl Trait for Type` together with its warnings [scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in] and [imported-implementation-is-less-visible-than-itemfield-it-is-captured-in] can mostly cover this use-case, though with slightly more boilerplate (`use`-declarations) and not as-strict a limitation. + +[RFC: Hidden trait implementations]: https://github.com/rust-lang/rfcs/pull/2529 + +### Required-explicit binding of scoped implementations inside generics + +This could avoid the distinction between [implementation-aware-generics] and [implementation-invariant-generics] to some extent, at the cost of likely overall worse ergonomics when working with scoped implementations. + +It's also likely to make `derive`-compatibility of scoped implementations inconsistent, because some macros may require explicit binding on field types while others would not. + +# Prior art +[prior-art]: #prior-art + +## Lightweight, Flexible Object-Oriented Generics +[lightweight-flexible-object-oriented-generics]: #lightweight-flexible-object-oriented-generics + +Yizhou Zhang, Matthew Loring, Guido Salvaneschi, Barbara Liskov and Andrew C. Myers, May 2015 + + + +There are some parallels between Genus's models and the scoped `impl Trait for Type`s proposed in this RFC, but for the most part they are quite distinct due to Rust's existing features: + +| Genus | scoped `impl Trait for Type` | reasoning | +|---|---|---| +| Proper-named models | Anonymous scoped implementations | Use of existing coherence constraints for validation. Forced subsetting in `use`-declarations improves stability. The `impl Trait for Type` syntax stands out in `use`-declarations and is intuitively readable. | +| Explicit bindings of non-default models | Mainly implicit bindings, but explicit bindings of scoped *and global* implementations are possible in some places. | Focus on simplicity and ergonomics of the most common use-case. More natural use with future specialisation. | +| Comparing containers inherently constrain type parameters in their type definition. | Available scoped implementations for discretised type parameters become part of the type identity. |

This is a tradeoff towards integration with Rust's ecosystem, as generics are generally not inherently bounded on collection types in Rust.

There is likely some friction here with APIs that make use of runtime type identity. See [split-type-identity-may-be-unexpected].

| + +Some features are largely equivalent: + +| Genus | Rust (*without* scoped `impl Trait for Type`) | notes / scoped `impl Trait for Type` | +|---|---|---| +| Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | +| Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | +| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |

Available implementations on type parameters of discretised implementation-aware generics are part of the type identity. Top-level bindings are not.

Genus's approach provides better remote-access ergonomics than 𝒢's and great robustness when moving instances through complex code, so it should be available. Fortunately, the existing style of generic implementations in Rust can simply be monomorphised accordingly, and existing reflexive blanket conversions and comparisons can bind regardless of unrelated parts of the top-level *implementation environment* of their type parameters.

However, typical Rust code also very heavily uses generics like references and closures to represent values passed through crate boundaries. To keep friction acceptably low by default, specific utility types are exempt from capturing *implementation environments* in their type parameters.

| + +## A Language for Generic Programming in the Large + +Jeremy G. Siek, Andrew Lumsdaine, 2007 + + + +𝒢 and scoped `impl Trait for Type` are conceptually very similar, though this RFC additionally solves logical consistency issues that arise from having multiple alternative ways to fulfill a constraint and develops some ideas further than the paper. Other differences are largely due to 𝒢 being more C++-like while scoped `impl Trait for Type` attempts smooth integration with all relevant Rust language features. + +A few notable similarities, in the paper's words: + +- equivalent retroactive modeling (where existing Rust's is limited by orphan rules), +- (retained) separate compilation (though *some* information can flow between items in this RFC, but only where such information flows already exist in Rust currently), +- lexically scoped models, +- seemingly the same binding rules on generic type parameters within constrained models/generic implementations, + +and key differences: + +| 𝒢 | Rust / scoped `impl Trait for Type` | notes | +|---|---|---| +| Only discrete model imports | Includes generic imports and re-exports | This is pointed out as '[left] for future work' in the paper. Here, it follows directly from the syntax combination of Rust's `use` and `impl Trait for Type` items. | +| - | (Rust) Global implementations | The automatic availability of global implementations between separately imported traits and types offers more convenience especially when working with common traits, like those backing operators in Rust. | +| Model overloading, mixed into nested scopes | Strict shadowing | Strict shadowing is easier to reason about for developers (especially when writing macros!), as the search stops at the nearest matching implementation or module boundary.
See Rust's trait method resolution behaviour and [interaction-with-specialisation] for how this is still practically compatible with a form of overload resolution.
See [scoped-fallback-implementations] for a possible future way to better enable adaptive behaviour in macro output. | +| - | (Rust) Trait objects | 𝒢 does not appear to support runtime polymorphism beyond function pointers. Scoped `impl Trait for Type` is seamlessly compatible with `dyn Trait` coercions (iff `Trait` is object-safe). | +| (unclear?) | Available implementations on discretised type parameters become part of the type identity of implementation-aware generics. | This allows code elsewhere to access scoped implementations that are already available at the definition site, and leads to overall more semantically consistent behaviour. | + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +## "global" implementations + +I'm not too sure about the "global" wording. *Technically* that implementation isn't available for method calls unless the trait is in scope... though it is available when resolving generics. Maybe "unscoped" is better? + +## Precise resolution location of *implementation environments* in function calls + +In macros, which function-call token should provide the resolution context from where to look for scoped `impl Trait for Type`s (in all possible cases)? + +This doesn't matter for `Span::call_site()` vs. `Span::mixed_site()` since scoped implementations would resolve transparently through both, but it does matter for `Span::def_site()` which should exclude them. + +It very much does matter if one of the opt-in mitigations for [first-party-implementation-assumptions-in-macros] is implemented. + +## Which `struct`s should be implementation-invariant? +[which-structs-should-be-implementation-invariant]: #which-structs-should-be-implementation-invariant + +This is a tough question because, runtime behaviour difference of [of-type-erased-collections] aside, the following makes shifting a type from [implementation-aware-generics] to [implementation-invariant-generics] a compilation-breaking change: + +```rust +struct Type; +struct Generic(T); +trait Trait {} + +mod a { + use super::{Type, Generic, Trait}; + pub use impl Trait for Type {} + pub type Alias = Generic; +} + +mod b { + use super::{Type, Generic, Trait}; + pub use impl Trait for Type {} + pub type Alias = Generic; +} + +use impl Trait for a::Alias {} +use impl Trait for b::Alias {} +``` + +(It is *theoretically* possible to do such a later adjustment as part of an edition, even considering `TypeId` behaviour I think, but it's certainly not pretty.) + +Splitting this along the line of "structs that use `<>` around type parameters" would feel cleaner, but the basic smart pointers, `Pin

`, `Option` and `Result` appear in crate API signatures enough that not including them would create considerable friction. + +Other candidates for consideration: + +- Other `DispatchFromDyn` types in the standard library like `Cell`, `SyncUnsafeCell`, `UnsafeCell` + +## Should it be an error to specify an *implementation environment* in places where it's guaranteed to be unused? +[should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused]: #should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused + +With the given [grammar-changes], it's possible to write `fn((Type as Trait in module))`, but, at least without a surrounding host, here the *implementation environment* written inline is completely ineffective because function pointer types are discretised [implementation-invariant-generics]. + +On the other hand, making it an error rather than a [unused-scoped-implementation] warning could easily cause problems for macros. + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Exporting a scoped implementation as global, `extern impl Trait` + +***This should never be used for IO/serialisation traits.*** + +Application crates may want to provide a specific implementation globally, disregarding orphan rules since there are no downstream crates that could be impacted by future incompatibilities (and crate-local issues are largely mitigated by *Cargo.lock*). + +This could later be allowed using a construct like + +```rust +// Use an external implementation as global: +#[core::unstable_use_as_global] +use impl_crate::{impl Trait for Type}; + +// Provide a local implementation globally: +#[core::unstable_use_as_global] +use impl Trait for Type { /*...*/ } +``` + +To use a global implementation not available through one of its dependencies, a library crate would have to declare it: + +```rust +extern impl Trait for Type; +``` + +This would result in a compile error or link-time error if the declaration is not fully covered by a global trait implementation. + +If the trait implementation is later made available plainly (that is: without `use`, subject to orphan rules) by a dependency, a warning should appear on the `extern impl` declaration, along with the suggestion to remove the `extern impl` item. + +(However, I assume binding to implementations not-from dependencies or the same crate in this way has a lot of implications for code generation.) + +There is previous discussion regarding a similar suggestion in a slightly different context: [[Pre-RFC] Forward impls](https://internals.rust-lang.org/t/pre-rfc-forward-impls/4628) +Perhaps the downsides here could be mitigated by allowing `#[unstable_use_as_global]` very strictly only in application crates compiled with the `cargo --locked` flag. + +## Scoped `impl Trait for Type` of auto traits, `Drop` and/or `Copy` with orphan rules + +The crate in which a type is defined could in theory safely provide scoped implementations for it also for these traits. + +- This is likely more complicated to implement than the scoped `impl Trait for Type`s proposed in this RFC, as these traits interact with more distinct systems. + +- What would be the binding site of `Drop` in `let`-statements? + +- This could interact with linear types, were those to be added later on. + + For example, database transactions could be opt-out linear by being `!Drop` globally but also having their crate provide a scoped `Drop` implementation that can be imported optionally to remove this restriction in a particular consumer scope. + +## Scoped proxy implementations + +In theory it *might* be possible to later add syntax to create an exported implementation that's *not in scope for itself*. + +I'm **very** hesitant about this since doing so would allow transparent overrides of traits (i.e. proxying), which could be abused for JavaScript-style layered overrides through copy-pasting source code together to some extent. + +## Analogous scoped `impl Type` + +This could be considered as more-robust alternative to non-object-safe extension traits defined in third party crates. + +A good example of this use case could be the [tap] crate, which provides generic extension methods applicable to *all* types, but where its use is *theoretically* vulnerable to instability regarding the addition of type-associated methods of the same name(s). + +If instead of (or in addition to!) …: + +```rust +// pipe.rs + +pub trait Pipe { + #[inline(always)] + fn pipe(self, func: impl FnOnce(Self) -> R) -> R + where + Self: Sized, + R: Sized, + { + func(self) + } + + // ... +} + +impl Pipe for T where T: ?Sized {} +``` + +…the extension could be defined as …: + +```rust +pub use impl T where T: ?Sized { + #[inline(always)] + fn pipe(self, func: impl FnOnce(Self) -> R) -> R + where + Self: Sized, + R: Sized, + { + func(self) + } + + // ... +} +``` + +…then: + +- The consumer crate could choose which types to import the extension for, weighing + + ```rust + use tap::pipe::{impl Type1, impl Type2}; + ``` + + against + + ```rust + use tap::pipe::{impl T where T: ?Sized}; + ``` + +- These *scoped extensions would shadow inherent type-associated items of the same name*, guaranteeing stability towards those being added. + + (This should come with some warning labels in the documentation for this feature, since *adding items to an existing public scoped extension* could be considered an easily-breaking change here.) + +This has fewer benefits compared to scoped `impl Trait for Type`, but would still allow the use of such third-party extension APIs in library crates with very high stability requirements. + +An open question here is whether (and how) to allow partially overlapping `use impl Type` in the same scope, in order to not shadow inherent associated items with ones that cannot be implemented for the given type. + +- That could in theory be more convenient to use, but + +- calls could be *subtly* inconsistent at the consumer side, i.e. accidentally calling an inherent method if a scoped extension method was expected and + +- widening a public implementation to overlap more of another exported in the same module could break dependent crates if a wide blanket import applied to narrower extensions. + +As such, *if* this feature was proposed and accepted at some point in the future, it would likely be a good idea to only allow non-overlapping implementations to be exported. + +[tap]: https://crates.io/crates/tap + +## Interaction with specialisation +[interaction-with-specialisation]: #interaction-with-specialisation + +- Scoped `impl Trait for Type` can be used for consumer-side specialisation of traits for binding sites that are in item scope, by partially shadowing an outer scope's implementation. + + Note that this would **not** work on generic type parameters, as the selected implementation is controlled strictly by their bounds (See [resolution-on-generic-type-parameters].), but it would work in macros for the most part. + + This does not interact with [specialisation proper](https://rust-lang.github.io/rfcs/1210-impl-specialization.html), but rather is a distinct, less powerful mechanism. As such, it would not supersede specialisation. + +- Scoped `impl Trait for Type` does not significantly interact with specialisation of global implementations. + + Any global specialisation would only be resolved once it's clear no scoped implementation applies. + +- Specialisation could disambiguate scoped implementations which are provided (implemented or imported) in the same scope. For example, + + ```rust + use dummy_debug::{impl Debug for T}; + use debug_by_display::{impl Debug for T}; + use impl Debug for str { + // ... + } + ``` + + would then compile, in scope resolving `` to the local implementation and otherwise binding `Debug` depending on whether `Display` is available at the binding site for each given type `T`. + + Local implementations do not necessarily have to be more specific compared to imported ones - in keeping with "this is the same as for global implementations", the way in which the scoped implementation is introduced to the scope should not matter to specialisation. + + **When importing scoped implementations from a module, specialisation should apply hierarchically.** First, the specificity of implementations is determined only by `use impl` implementations and `use`-declarations in the importing scope. If the trait bound binds to a `use`-declaration, then the actual implementation is chosen by specificity among those visible in the module they are imported from. If the chosen implementation there is an import, the process repeats for the next module. This ensures stability and coherence when published implementations are specialised in other modules. + + - I'm not sure how well this can be cached in the compiler for binding-sites in distinct scopes, unfortunately. Fortunately, specialisation of scoped `impl Trait for Type` does not seem like a blocker for specialisation of global trait implementations. + + - Should specialisation of scoped implementations require equal visibility? I think so, but this question also seems considerably out of scope for scoped `impl Trait as Type` as a feature itself. + +## Scoped `impl Trait for Type` as associated item + +Scoped `impl Trait for Type` could be allowed and used as associated non-object-safe item as follows: + +```rust +trait OuterTrait { + use impl Trait for Type; +} + +fn function() { + use T::{impl Trait for Type}; + // ...configured code... +} +``` +```rust +impl OuterTrait for OtherType { + // Or via `use`-declaration of scoped implementation(s) defined elsewhere! + // Or specify that the global implementation is used (somehow)! + use impl Trait for Type { + // ... + } +} + +function::(); +``` + +This would exactly supersede the following more verbose pattern enabled by this RFC: + +```rust +trait OuterTrait { + type Impl: ImplTraitFor; +} + +trait ImplTraitFor { + // Copy of trait's associated items, but using `T` instead of the `Self` type and + // e.g. a parameter named `this` in place of `self`-parameters. +} + +fn function() { + use impl Trait for Type { + // Implement using `T::Impl`, associated item by associated item. + } + + // ...configured code... +} +``` + +```rust +struct ImplTraitForType; +impl ImplTraitFor for ImplTraitForType { + // Implement item-by-item, as existing scoped `impl Trait for Type` cannot be used here. +} + +impl OuterTrait for OtherType { + type Impl: ImplTraitFor = ImplTraitForType; +} + +function::(); +``` + +- *In theory* this could be made object-safe if the associated implementation belongs to an object-safe trait, but this would introduce much-more-implicit call indirection into Rust. + +## Scoped fallback implementations +[scoped-fallback-implementations]: #scoped-fallback-implementations + +A scoped fallback implementation could be allowed, for example by negatively bounding it *on the same trait* in the definition or import: + +```rust +#[derive(Debug)] +struct Type1; + +struct Type2; + +{ + use debug_fallback::{impl Debug for T where T: !Debug}; + + dbg!(Type1); // Compiles, uses global implementation. + dbg!(Type2); // Compiles, uses fallback implementation. +} +``` + +This would be a considerably less messy alternative to [autoref-] or [autoderef-specialisation] for macro authors. + +Note that ideally, these fallback implementations would still be required to not potentially overlap with any other (plain or fallback) scoped implementation brought into that same scope. + +[autoref-]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md +[autoderef-specialisation]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html + +## Negative scoped implementations + +It's technically possible to allow negative scoped implementations that only shadow the respective implementation from an outer scope. For example: + +```rust +// signed-indexing/src/arrays/prelude.rs +use core::ops::Index; + +pub use impl !Index for [T; N] {} +pub use impl Index for [T; N] { + type Output = T; + + #[inline] + #[track_caller] + fn index(&self, index: isize) -> &T { + match index { + 0.. => self[index as usize], + ..=-1 => if let Some(index) = self.len().checked_add_signed(index) { + self[index] + } else { + #[inline(never)] + #[track_caller] + fn out_of_bounds(len: usize, index: isize) -> ! { + panic!("Tried to index slice of length {len} with index {index}, which is too negative to index backwards here."); + } + + out_of_bounds(self.len(), index); + }, + } + } +} +``` + +```rust +use signed_indexing::arrays::prelude::*; + +let array = [1, 2, 3]; + +// Unambiguous: +let first = array[0]; +let last = array[-1]; +``` + +This is likely a rather niche use-case. + +It could also be useful in the context of [scoped-fallback-implementations]. + +## Implicit import of supertrait implementations of scoped implementations defined on discrete types +[implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types]: #implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types + +As subtype implementations defined on discrete types always require specific supertrait implementations, the import of these supertrait implementations could be made implicit. + +This would also affect *implementation environments* modified in generic arguments, changing + +```rust +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +``` + +to + +```rust +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +``` + +and + +```rust +dbg!(::cmp(&1, &2)); // […] = Greater +``` + +to + +```rust +dbg!(::cmp(&1, &2)); // […] = Greater +``` + +The downside is that `use`-declarations would become less obvious. Implied supertrait implementation imports could be enabled only for *implementation environments* specified inline on generic type parameters as e.g. `Type as Ord in module` to avoid this. + +If this is added later than scoped `impl Trait for Type`, then private scoped implementations **must not** be implicitly exported through this mechanism. (It's likely a good idea to not allow that anyway, as it would be surprising.) Making previously crate-private implementations available that way could lead to unsoundness. + +### Alternatively + +It could be enough to allow inferring the module explicitly by writing `_` instead of its *SimplePath*, so that the snippets above become + +```rust +let min_heap: BinaryHeap = [1, 3, 2, 4].into(); +``` + +and + +```rust +dbg!(<(u32 as PartialOrd in _) as Ord in reverse>::cmp(&1, &2)); // […] = Greater +``` + +Here, too, the inference should only be of required supertrait implementations based on explicitly chosen implementations of their subtraits. + +## Conversions where a generic only cares about specific bounds' consistency + +With specialisation and more expressive bounds, an identity conversion like the following could be implemented: + +```rust +// In the standard library. + +use std::mem; + +impl From> for HashSet +where + T: ?Hash + ?Eq, // Observe implementations without requiring them. + U: ?Hash + ?Eq, + T == U, // Comparison in terms of innate type identity and observed implementations. +{ + fn from(value: HashSet) -> Self { + unsafe { + // SAFETY: This type requires only the `Hash` and `Eq` implementations to + // be consistent for correct function. All other implementations on + // generic type parameters may be exchanged freely. + // For the nested types this is an identity-transform, as guaranteed + // by `T == U` and the shared `S` which means the container is also + // guaranteed to be layout compatible. + mem::transmute(value) + } + } +} +``` + +This could also enable adjusted borrowing: + +```rust +// In the standard library. + +use std::mem; + +impl HashSet { + fn as_with_item_impl(&self) -> HashSet + where + T: ?Hash + ?Eq, // Observe implementations without requiring them. + U: ?Hash + ?Eq, + T == U, // Comparison in terms of innate type identity and observed implementations. + { + unsafe { + // SAFETY: This type requires only the `Hash` and `Eq` implementations to + // be consistent for correct function. All other implementations on + // generic type parameters may be exchanged freely. + // For the nested types this is an identity-transform, as guaranteed + // by `T == U` and the shared `S` which means the container is also + // guaranteed to be layout compatible. + &*(self as *const HashSet as *const HashSet) + } + } +} +``` + +(But at that point, it may be better to use something like an unsafe marker trait or unsafe trait with default implementations.) + +## Sealed trait bounds + +This is probably pretty strange, and may not be useful at all, but it likely doesn't hurt to mention this. + +Consider *ImplEnvironment* clauses in bounds like here: + +```rust +use another_crate::{Trait, Type1, Type2}; + +pub fn function() {} + +pub use impl Trait for Type1 {} +pub use impl Trait for Type2 {} +``` + +With this construct, `function` could privately rely on implementation details of `Trait` on `Type1` and `Type2` without defining a new sealed wrapper trait. It also becomes possible to easily define multiple sealed sets of implementations this way, by defining modules that export them. + +Overall this would act as a more-flexible but also more-explicit counterpart to sealed traits. + +Iff the caller is allowed to use this function without restating the binding, then removing the scope would be a breaking change (as it is already with bindings captured on type parameters in public signatures, so that would be consistent for this syntactical shape). + +> That convenience (automatically using the correct implementations even if not in scope) also really should exist only iff there already is robust, near-effortless tooling for importing existing scoped implementations where missing. Otherwise this feature here *would* get (ab)used for convenience, which would almost certainly lead to painful overly sealed APIs. + +Binding an implementation in a call as `function::()` while it is constrained as `fn function() { … }` MUST fail for distinct modules `a` and `b` even if the implementations are identical, as otherwise this would leak the implementation identity into the set of breaking changes. + +## Glue crate suggestions +[glue-crate-suggestions]: #glue-crate-suggestions + +If crates move some of their overlay features into glue crates, as explained in [unblock-ecosystem-evolution], it would be nice if they could suggest them if both they and e.g. Serde were `cargo add`ed as direct dependencies of a crate currently being worked on. + +An example of what this could look like: + +```toml +[package] +name = "my-crate" +version = "0.1.2" +edition = "2021" + +[dependencies] +# none + +[suggest-with.serde."1"] +my-crate_serde_glue = "0.1.0" + +[suggest-with.bevy_reflect."0.11"] +my-crate_bevy_reflect_glue = "0.1.2" + +[suggest-with.bevy_reflect."0.12"] +my-crate_bevy_reflect_glue = "0.2.1" +``` + +(This sketch doesn't take additional registries into account.) + +Ideally, crates.io should only accept existing crates here (but with non-existing version numbers) and Cargo should by default validate compatibility where possible during `cargo publish`. + +## Reusable limited-access APIs + +Given a newtype of an unsized type, like + +```rust +#[repr(transparent)] +pub struct MyStr(str); +``` + +for example, there is currently no safe-Rust way to convert between `&str` and `&MyStr` or `Box` and `Box`, even though *in the current module which can see the field* this is guaranteed to be a sound operation. + +One good reason for this is that there is no way to represent this relationship with a marker trait, since any global implementation of such a trait would give outside code to this conversion too. + +With scoped `impl Trait for Type`, the code above could safely imply a marker implementation like the following in the same scope: + +```rust +// Visibility matches newtype or single field, whichever is more narrow. + +use unsafe impl Transparent for MyStr {} +use unsafe impl Transparent for str {} +// Could symmetry be implied instead? +``` + +(`Transparent` can and should be globally reflexive.) + +This would allow safe APIs with unlimited visibility like + +```rust +pub fn cast, U>(value: T) -> U { + unsafe { + // SAFETY: This operation is guaranteed-safe by `Transparent`. + std::mem::transmute(value) + } +} +``` + +and + +```rust +unsafe impl, U> Transparent> for Box {} +unsafe impl<'a, T: Transparent, U> Transparent<&'a U> for &'a T {} +unsafe impl<'a, T: Transparent, U> Transparent<&'a mut U> for &'a mut T {} +``` + +which due to their bound would only be usable where the respective `T: Transparent`-implementation is in scope, that is: where by-value unwrapping-and-then-wrapping would be a safe operation (for `Sized` types in that position). + +Overall, this would make unsized newtypes useful without `unsafe`, by providing a compiler-validated alternative to common reinterpret-casts in their implementation. The same likely also applies to certain optimisations for `Sized` that can't be done automatically for unwrap-then-wrap conversions as soon as a custom `Allocator` with possible side-effects is involved. + +If a module wants to publish this marker globally, it can do so with a separate global implementation of the trait, which won't cause breakage. (As noted in [efficient-compilation], the compiler should treat implementations of empty traits as identical early on, so that no code generation is unnecessarily duplicated.) + +> *Could* sharing pointers like `Arc` inherit this marker from their contents like `Box` could? I'm unsure. They probably *shouldn't* since doing this to exposed shared pointers could easily lead to hard-to-debug problems depending on drop order. +> +> A global +> +> ```rust +> unsafe impl, U> Transparent> for UnsafeCell {} +> ``` +> +> should be unproblematic, but a global +> +> ```rust +> unsafe impl Transparent for UnsafeCell {} +> ``` +> +> (or vice versa) **must not** exist to allow the likely more useful implementations on `&`-like types. From f82162b3e539a9d986995c0e0425bb3f110dc21a Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 12 May 2024 17:38:54 +0200 Subject: [PATCH 14/27] Fixed Start Date header --- text/3634-scoped-impl-trait-for-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 2db03456864..9f9e0c54c02 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1,5 +1,5 @@ - Feature Name: `scoped_impl_trait_for_type` -- Start Date: (fill me in with today's date, 2024-05-12) +- Start Date: 2024-05-12 - RFC PR: [rust-lang/rfcs#3634](https://github.com/rust-lang/rfcs/pull/3634) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) From b7fb5e2183eb4a0f5aa21fb7835c85965f7ca9d8 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 12 May 2024 18:02:45 +0200 Subject: [PATCH 15/27] Manually wrapped markdown code blocks --- text/3634-scoped-impl-trait-for-type.md | 48 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 9f9e0c54c02..5d06583ce65 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -232,23 +232,40 @@ The page for [`TypeId`] gains two sections with the following information: ```markdown # `TypeId` and scoped implementations -To make sure that that are no mix-ups between, for example, `HashSet` and `HashSet`, any such difference implies distinct `TypeId`s between such discretised generics (and that the types are not mutually assignable). +To make sure that that are no mix-ups between, for example, `HashSet` and +`HashSet`, any such difference implies distinct `TypeId`s between +such discretised generics (and that the types are not mutually assignable). -This also affects trait-bounded generic type parameters: If `T` is bounded on `Hash`, then `TypeId::of::()` results in distinct `TypeId`s in that context depending on the captured implementation. +This also affects trait-bounded generic type parameters: If `T` is bounded on `Hash`, then +`TypeId::of::()` results in distinct `TypeId`s in that context depending on the +captured implementation. -However, note that `TypeId::of::()` and `TypeId::of::()` are always equivalent for one definition of `T`, as `TypeId::of`'s implementation does **not** have a `T: Hash` bound! +However, note that `TypeId::of::()` and `TypeId::of::()` are +always equivalent for one definition of `T`, as `TypeId::of`'s implementation does **not** +have a `T: Hash` bound! -For convenience (so that their values are easily interchangeable across crates), the following types ignore scoped implementations *on* their generic arguments in terms of *their own* type identity: […] +For convenience (so that their values are easily interchangeable across crates), the +following types ignore scoped implementations *on* their generic arguments in terms of +*their own* type identity: […] -Despite this, differences in *type arguments'* discrete identities (for example from scoped implementations captured *in* them) distinguish the type identity of *all* discretised generics they appear in. +Despite this, differences in *type arguments'* discrete identities (for example from +scoped implementations captured *in* them) distinguish the type identity of *all* +discretised generics they appear in. # `TypeId::of::()` may change for values of generics -To make type-erased collections sound and unsurprising by default, it's sound to transmute between instances of an external generic type that differ only in their captured scoped implementations, **iff and only iff** no inconsistency is ever observed by bounds (including across separate function calls). +To make type-erased collections sound and unsurprising by default, it's sound to transmute +between instances of an external generic type that differ only in their captured scoped +implementations, **iff and only iff** no inconsistency is ever observed by bounds +(including across separate function calls). -However, this poses a problem: `TypeId::of::()` (just like the written-out form of any type that doesn't ignore scoped implementations) takes *all* differences in captured implementation environments into account, not just those relevant to trait bounds. +However, this poses a problem: `TypeId::of::()` (just like the written-out form of +any type that doesn't ignore scoped implementations) takes *all* differences in captured +implementation environments into account, not just those relevant to trait bounds. -As such, prefer `TypeId::of::()` whenever possible in order to make only the distinctions you require. You can use tuples to combine multiple type parameters without over-distinguishing: `TypeId::of::<(S, T)>()` +As such, prefer `TypeId::of::()` whenever possible in order to make only the +distinctions you require. You can use tuples to combine multiple type parameters without +over-distinguishing: `TypeId::of::<(S, T)>()` ``` > These rules and the reasons for them are explained in detail in the [reference-level-explanation] below, as well as in [logical-consistency] as part of [rationale-and-alternatives]. It may be a good idea to link to similar longer explanations from the standard library docs above, even if just as "See also:"-style references for further reading. @@ -266,7 +283,8 @@ The pages for [implementation-invariant-generics] gain a section similar to the ```markdown # Implementation-invariant generic -This type does not by itself capture scoped implementation environments when discretised. See [`TypeId` and scoped implementations] for more information. +This type does not by itself capture scoped implementation environments when discretised. +See [`TypeId` and scoped implementations] for more information. ``` where ``[`TypeId` and scoped implementations]`` is a link to the section added to the `TypeId` page above. @@ -278,7 +296,10 @@ The page for [`transmute`] gains a section with the following information: ```markdown # `transmute` and scoped implementations -It is sound to transmute between discretised generic types that differ only in their captured scoped implementation environments, **but only iff** such differences are **never** observed by bounds on their implementation, including functions that imply such by being implemented for discrete instances of the generic. +It is sound to transmute between discretised generic types that differ only in their +captured scoped implementation environments, **but only iff** such differences are +**never** observed by bounds on their implementation, including functions that imply such +by being implemented for discrete instances of the generic. ``` > As far as I can tell, this is only practically relevant for certain kinds of type-erasing collections, like type-erasing hash maps and B-trees, of which I couldn't find any examples on crates.io. @@ -292,9 +313,12 @@ It is sound to transmute between discretised generic types that differ only in t The page on [Transmutes] gains the following warning in addition to the existing ones: ```markdown -- It is unsound to change [captured scoped implementations] via transmute for any external type if this change ever causes a contradiction observable by the transmuted value's implementation. +- It is unsound to change [captured scoped implementations] via transmute for any external + type if this change ever causes a contradiction observable by the transmuted value's + implementation. - This can happen due to bounds on called functions and/or because a called function is implemented for a specific type discretised from the generic. + This can happen due to bounds on called functions and/or because a called function is + implemented for a specific type discretised from the generic. ``` `[captured scoped implementations]` should link to documentation introducing scoped `impl Trait for Type`. From ef35245c61d5f9d459cd93593a7bebead2143221 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Mon, 13 May 2024 21:34:50 +0200 Subject: [PATCH 16/27] Changed "## Type parameters capture their *implementation environment*" to "## Type arguments capture their *implementation environment*" and clarified the role of type arguments vs. type parameters This was pointed out in the Rust Programming Language Community Discord. --- text/3634-scoped-impl-trait-for-type.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 5d06583ce65..9d2f13829fc 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -353,7 +353,7 @@ The core Rust language grammar is extended as follows: (This can be distinguished from `use`-declarations with a lookahead up to and including `impl` or `unsafe`, meaning at most four shallowly tested token trees with I believe no groups. No other lookaheads are introduced into the grammar by this RFC.) - **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [type-parameters-capture-their-implementation-environment]). + **The scoped implementation defined by this item is implicitly always in scope for its own definition.** This means that it's not possible to refer to any shadowed implementation inside of it (including generic parameters and where clauses), except by re-importing specific scoped implementations inside nested associated functions. Calls to generic functions cannot be used as backdoor either (see [type-arguments-capture-their-implementation-environment]). [*TraitImpl*]: https://doc.rust-lang.org/reference/items/implementations.html?highlight=TraitImpl#implementations @@ -505,16 +505,16 @@ To ensure this stays sound, scoped `impl Trait for Type` where `Trait` is extern See also [scoped-implementation-of-external-sealed-trait]. -## Type parameters capture their *implementation environment* -[type-parameters-capture-their-implementation-environment]: #type-parameters-capture-their-implementation-environment +## Type arguments capture their *implementation environment* +[type-arguments-capture-their-implementation-environment]: #type-arguments-capture-their-implementation-environment -When a type parameter is specified, either explicitly or inferred from an expression, it captures a view of *all* implementations that are applicable to its type there. This is called the type parameter's *implementation environment*. +When a type argument is specified, either explicitly or inferred from an expression, it captures a view of *all* implementations that are applicable to its type there. This is called the type argument's *implementation environment*. -(For trait objects, associated types are treated as type parameters for the purposes of this proposal.) +(For trait objects, associated types are treated as type arguments for the purposes of this proposal.) -When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type parameters are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type parameter is specified. +When implementations are resolved on the host type, bounds on the type parameter can only be satisfied according to this captured view. This means that implementations on generic type arguments are 'baked' into discretised generics and can be used even in other modules or crates where this discretised type parameter is accessible (possibly because a value of this type is accessible). Conversely, additional or changed implementations on a generic type parameter in an already-discretised type *cannot* be provided anywhere other than where the type argument is specified. -When a generic type parameter is used to discretise another generic, the captured environment is the one captured in the former but overlaid with modifications applicable to that generic type parameter's opaque type. +When a generic type parameter is used as type argument to discretise another generic, the captured environment is the one captured in the former but overlaid with modifications applicable to that generic type parameter's opaque type where it is used as type argument. Note that type parameter defaults too capture their *implementation environment* where they are specified, so at the initial definition site of the generic. This environment is used whenever the type parameter default is used. @@ -2293,7 +2293,7 @@ Generics are trickier, as their instances often do expect trait implementations This problem is solved by making the `impl`s available to each type parameter part of the the type identity of the discretised host generic, including a difference in `TypeId` there as with existing monomorphisation. -(See [type-parameters-capture-their-implementation-environment] and [type-identity-of-generic-types] in the [reference-level-explanation] above for more detailed information.) +(See [type-arguments-capture-their-implementation-environment] and [type-identity-of-generic-types] in the [reference-level-explanation] above for more detailed information.) Here is an example of how captured *implementation environments* safely flow across module boundaries, often seamlessly due to type inference: @@ -2770,7 +2770,7 @@ This is mainly an effect of [layout-compatibility] and [binding-choice-by-implem Given equal usage, compiling code that uses scoped implementations could as such be slightly more efficient compared to use of newtypes and the resulting text size may be slightly smaller in some cases where newtype implementations are inlined differently. -The compiler should treat implementations of the same empty trait on the same type as identical early on, so that no code generation is unnecessarily duplicated. However, unrelated empty-trait implementations must still result in distinct `TypeId`s when captured in a generic type parameter and observed there by a `where`-clause or through nesting in an implementation-aware generic. +The compiler should treat implementations of the same empty trait on the same type as identical early on, so that no code generation is unnecessarily duplicated. However, unrelated empty-trait implementations must still result in distinct `TypeId`s when captured into a generic type parameter and observed there by a `where`-clause or through nesting in an implementation-aware generic. ## Alternatives From 129e734ae1bc198becb49b2b4590232e0a746c5e Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Mon, 13 May 2024 21:42:31 +0200 Subject: [PATCH 17/27] Added missing `pub` visibility Fixes https://github.com/rust-lang/rfcs/pull/3634/files#r1598697056 . An inline implementation environment can only refer to implementations that it can actually see. --- text/3634-scoped-impl-trait-for-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 9d2f13829fc..06bf00df7c7 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -131,7 +131,7 @@ impl Type { mod nested { use super::{Trait, Type}; - use impl Trait for () { + pub use impl Trait for () { fn trait_fn() { println!("nested"); } From f3589adf495ceb828627817e234f4dffdaedc5b8 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Mon, 13 May 2024 23:34:30 +0200 Subject: [PATCH 18/27] Clarified opaque type of generic type parameter --- text/3634-scoped-impl-trait-for-type.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 06bf00df7c7..488dc7dd7bb 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -724,8 +724,7 @@ Scoped implementations may still be observed through bounded generic type parame ## `TypeId` of generic type parameters' opaque types [typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types -In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to the captured *implementation environment*, but *only according to implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: - +In addition to the type identity of the specified type, the `TypeId` of generic type parameter types varies according to the *implementation environment* that has been captured into them, but *only according to implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: ```rust use std::any::TypeId; From 9c1ef7a21dcc3ba93a4b55c5f2b2a36627a85097 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Mon, 13 May 2024 23:53:36 +0200 Subject: [PATCH 19/27] Clarified lines that produce warnings --- text/3634-scoped-impl-trait-for-type.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 488dc7dd7bb..e059b005cc8 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -746,7 +746,7 @@ type B = Generic; fn no_bound(_: Generic, _: Generic) { assert_eq!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::>(), TypeId::of::>()); + assert_ne!(TypeId::of::>(), TypeId::of::>()); // ⚠⚠ assert_eq!(TypeId::of::(), TypeId::of::()); assert_eq!(TypeId::of::(), TypeId::of::()); @@ -754,7 +754,7 @@ fn no_bound(_: Generic, _: Generic) { fn yes_bound(_: Generic, _: Generic) { assert_ne!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::>(), TypeId::of::>()); + assert_ne!(TypeId::of::>(), TypeId::of::>()); // ⚠⚠ assert_eq!(TypeId::of::(), TypeId::of::()); assert_ne!(TypeId::of::(), TypeId::of::()); @@ -766,6 +766,8 @@ fn main() { } ``` +(The lines marked with ` // ⚠⚠` produce two warnings each: [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]) + In particular: - If no bound-relevant scoped implementations are captured in a type parameter, then the `TypeId` of the opaque type of that type parameter is identical to that of the discrete type specified for that type parameter. From 6fc4bf9cb647a8df50d6f9a4e5426ff85258270f Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Tue, 14 May 2024 01:31:16 +0200 Subject: [PATCH 20/27] Changed "No priority over type-associated methods" into "Resolution priority" with more and more precise information --- text/3634-scoped-impl-trait-for-type.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index e059b005cc8..243a1f2ddfc 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1329,9 +1329,13 @@ where } ``` -## No priority over type-associated methods +## Resolution priority -Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. +Scoped `impl Trait for Type` has *the same* resolution priority as an equivalent global trait implementation would have if it was visible in that scope. This means that there may be ambiguity between scoped implementations introduced at different nesting levels, just as between equivalent global trait implementations. + +> Flagging a potentially unintended behaviour change with this error seems like the safer choice, and it's easy to resolve using a qualified path where needed. + +Inherent implementations still always shadow all trait implementations, including scoped ones. ## Coercion to trait objects From 99dc57251c6e03b8f333d1acf551fb6c0ab6936b Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sat, 25 May 2024 11:47:56 +0200 Subject: [PATCH 21/27] Replaced "Static interception of dynamic calls" with "Limitations on trait object types" --- text/3634-scoped-impl-trait-for-type.md | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 243a1f2ddfc..c6099c784fe 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1460,32 +1460,30 @@ fn function() -> impl Trait { } ``` -## Static interception of dynamic calls +## Limitations on trait object types -As a consequence of binding outside of generic contexts, it *is* possible to statically wrap *specific* trait implementations on *concrete* types. This includes the inherent implementations on trait objects: +Trait object types automatically implement their stated trait (and all its supertraits) and are already exempt from blanket implementations of these traits. + +To avoid confusion, these automatic implementations can't be shadowed by scoped implementations either, whether through blanket or direct implementations. The following code does not compile: ```rust -use std::fmt::{self, Display, Formatter}; +trait Super {} +trait Trait: Super {} -{ - use impl Display for dyn Display { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Restore binding to inherent global implementation within this function. - use ::{impl Display for dyn Display}; +use impl Trait for dyn Trait {} + // ^^^^^^^^^^^^^^^^^^^^^^^^ + // error[E0371]: the object type `…` automatically implements implements the trait `Trait` - write!(f, "Hello! ")?; - d.fmt(f)?; - write!(f, " See you!") - } - } +use impl Super for dyn Trait {} + // ^^^^^^^^^^^^^^^^^^^^^^^^ + // error[E0371]: the object type `…` automatically implements implements the trait `Super` +``` - let question = "What's up?"; // &str - println!("{question}"); // "What's up?" +### Interaction with [RFC 2515 `type_alias_impl_trait`] - let question: &dyn Display = &question; - println!("{question}"); // Binds to the scoped implementation; "Hello! What's up? See you!" -} -``` +The same limitations apply to `Alias` of `type Alias = impl Trait;` with regard to `Trait` and all of its supertraits. + +[RFC 2515 `type_alias_impl_trait`]: https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html ## Warnings From 308af53213df879a0ba26f12bd96602c305f9766 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sat, 25 May 2024 17:23:31 +0200 Subject: [PATCH 22/27] Added "Limitations on bounded opaque types and type variables" as subsection of "Limitations on trait object types" --- text/3634-scoped-impl-trait-for-type.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index c6099c784fe..40278b0f21b 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1479,9 +1479,13 @@ use impl Super for dyn Trait {} // error[E0371]: the object type `…` automatically implements implements the trait `Super` ``` +### Limitations on bounded opaque types and type variables + +Just like with trait object types, scoped implementations can't shadow the implementations of traits visible automatically on bounded opaque types (e.g. from return-position `impl Trait`) or bounded type variables like bounded type parameters and their bounded trait-associated types. Trying to do so directly is an error just as above. + ### Interaction with [RFC 2515 `type_alias_impl_trait`] -The same limitations apply to `Alias` of `type Alias = impl Trait;` with regard to `Trait` and all of its supertraits. +The same limitations as for trait object types apply to `Alias` of `type Alias = impl Trait;` with regard to `Trait` and all of its supertraits. This includes the error when trying to write `use impl Trait for Alias { … }`; [RFC 2515 `type_alias_impl_trait`]: https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html From 360a5944bd9d1d5732c5d794e282f51955879d8f Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sun, 26 May 2024 11:41:00 +0200 Subject: [PATCH 23/27] Added "Forbidden implementation combinations" and the error "potentially unsound combination of implementations" --- text/3634-scoped-impl-trait-for-type.md | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 40278b0f21b..882e92a747c 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1489,6 +1489,34 @@ The same limitations as for trait object types apply to `Alias` of `type Alias = [RFC 2515 `type_alias_impl_trait`]: https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html +## Forbidden implementation combinations +[forbidden-implementation-combinations]: #forbidden-implementation-combinations + +Due to uniqueness of global implementations for trait/type pairs, it has been possible for `unsafe` implementations to generically make safety-relevant assertions also about only loosely-related implementations. + +In order for existing code to remain sound in all cases, at least the following implementation combinations must be forbidden by default when fulfilling bounds: + +- `` +- `, U: Scoped>` +- `, U: Scoped>` +- combinations of these patterns, regardless of how the connecting bounds are fulfilled + +where + +- `UnsafeGlobal` is fulfilled by a global implementation of an `unsafe` trait (including through imports (direct or indirect)), +- `Scoped` is fulfilled by a scoped implementation and +- the type parameter definitions and bounds may be split between an `impl` and e.g. `fn` or `type` (as what matters is only that the relation is visible generically in some way). + +Running afoul of this restriction produces the error [potentially-unsound-combination-of-implementations]. + +Unsafe traits are opted-out of imposing these limits if their definition has the new attribute `#[asserts_non_supertrait_impls(false)]`. In particular, all auto-traits like `Send`, `Sync` and `Unpin` and to my knowledge all other `unsafe` traits defined in the standard library can do this without issue. + +> Depending on how much friction this rule causes, changing the default may eventually be a candidate for inclusion in an edition change, but personally I wouldn't do this before scoped implementations have become a well-established part of the language. +> +> The migration for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe` trait definitions without the attribute. + +These limits don't apply to `unsafe` implementations that are originally implemented as scoped. Instead, it is unsound to expose (to external safe code) an originally-scoped `unsafe` implementation that asserts non-supertrait implementations. + ## Warnings ### Unused scoped implementation @@ -1755,6 +1783,42 @@ Crate `b` cannot define scoped implementations of the external sealed trait `Sea See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. +### Potentially unsound combination of implementations +[potentially-unsound-combination-of-implementations]: #potentially-unsound-combination-of-implementations + +See [forbidden-implementation-combinations]. + +```rust +struct Type; + +/// # Safety +/// +/// Has requirements regarding the implementation of [`Scoped`] on `Self`. +unsafe trait Global {} +unsafe impl Global for Type {} + +trait Scoped {} +use impl Scoped for Type {} + +fn two_bounds() {} + +fn main() { + two_bounds::(); + // ^^^^ error: potentially unsound combination of implementations + // + // The global implementation of the unsafe trait `Global` by `Type` may assume + // the global implementation of `Scoped` by `Type` when `Type` implements `Scoped`. + // + // Hint: Add a scoped implementation of `Global` by `Type` to avoid this restriction. + // Hint: Add `#[asserts_non_supertrait_impls(false)]` to `Global`'s definition to allow this combination. +} +``` + +> Should the hints be conditional on whether doing so would be legal in the current scope/workspace? +> +> rust-analyzer and similar tooling could in theory offer to perform these actions as quick-fix. +> If a scoped implementation is generated that way, it should forward all items to the global one. + ## Behaviour change/Warning: `TypeId` of implementation-aware generic discretised using generic type parameters [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters From 4d746692f16d69a981bce7570822d6fa6dd690e0 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sat, 1 Jun 2024 18:50:44 +0200 Subject: [PATCH 24/27] Expanded and corrected "Forbidden implementation combinations" --- text/3634-scoped-impl-trait-for-type.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 882e92a747c..471974a5174 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1492,30 +1492,40 @@ The same limitations as for trait object types apply to `Alias` of `type Alias = ## Forbidden implementation combinations [forbidden-implementation-combinations]: #forbidden-implementation-combinations -Due to uniqueness of global implementations for trait/type pairs, it has been possible for `unsafe` implementations to generically make safety-relevant assertions also about only loosely-related implementations. +Due to uniqueness of global implementations for trait/type pairs, it has been possible for `unsafe` implementations and blocks to generically make safety-relevant assertions and assumptions also about only loosely-related implementations. In order for existing code to remain sound in all cases, at least the following implementation combinations must be forbidden by default when fulfilling bounds: - `` - `, U: Scoped>` - `, U: Scoped>` +- `` +- `, U: ExternalScoped>` +- `, U: ExternalScoped>` +- for patterns with `U`: also cases where `T` is replaced with a concrete type - combinations of these patterns, regardless of how the connecting bounds are fulfilled where - `UnsafeGlobal` is fulfilled by a global implementation of an `unsafe` trait (including through imports (direct or indirect)), -- `Scoped` is fulfilled by a scoped implementation and +- `Scoped` is fulfilled by a scoped implementation, +- `Sealed` is fulfilled by an implementation of a sealed trait, +- `ExternalScoped` is fulfilled by a scoped implementation defined in a different crate from where the bounded item is defined and - the type parameter definitions and bounds may be split between an `impl` and e.g. `fn` or `type` (as what matters is only that the relation is visible generically in some way). Running afoul of this restriction produces the error [potentially-unsound-combination-of-implementations]. -Unsafe traits are opted-out of imposing these limits if their definition has the new attribute `#[asserts_non_supertrait_impls(false)]`. In particular, all auto-traits like `Send`, `Sync` and `Unpin` and to my knowledge all other `unsafe` traits defined in the standard library can do this without issue. +Unsafe and sealed traits are opted-out of imposing these limits in place of `UnsafeGlobal` if their definition has the new attribute `#[asserts_non_supertrait_impls(false)]`. In particular, all auto-traits like `Send`, `Sync` and `Unpin` and to my knowledge all other `unsafe` traits defined in the standard library can do this without issue. -> Depending on how much friction this rule causes, changing the default may eventually be a candidate for inclusion in an edition change, but personally I wouldn't do this before scoped implementations have become a well-established part of the language. +Bounded items are opted out of imposing the limits containing `Sealed` on their uses if their definition has the new attribute `#[assumes_unique_impls(false)]`. Applying this attribute in turn triggers [potentially-unsound-combination-of-implementations] on nested uses of bounds combinations where the nested `ExternalScoped` bound is fulfilled through any outer bound (including indirectly through blanket implementations enabled by any outer bound). + +> Depending on how much friction these rules cause, changing the default(s) may eventually be a candidate for inclusion in an edition change, but personally I wouldn't do this before scoped implementations have become a well-established part of the language. > -> The migration for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe` trait definitions without the attribute. +> The migrations for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe`-or-sealed traits' definitions without the attribute, and `#[assumes_unique_impls(true)]` to all public items where a sealed-trait-bound is combined with another as above. + +These limits don't apply to `unsafe` implementations in place of `UnsafeGlobal` that are originally implemented as scoped. Instead, it is unsound to expose (to external safe code) an originally-scoped `unsafe` implementation that asserts non-supertrait implementations. -These limits don't apply to `unsafe` implementations that are originally implemented as scoped. Instead, it is unsound to expose (to external safe code) an originally-scoped `unsafe` implementation that asserts non-supertrait implementations. +By the same principle, scoped implementations defined by the same crate as the bounded item don't necessarily have to be forbidden when used in place of `ExternalScoped` for those bounds. ## Warnings From bf0379f1e86523c7407c652c2b14c16426acfa50 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sat, 1 Jun 2024 19:55:58 +0200 Subject: [PATCH 25/27] Further expanded and corrected "Forbidden implementation combinations" --- text/3634-scoped-impl-trait-for-type.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 471974a5174..691e6b45678 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1498,10 +1498,14 @@ In order for existing code to remain sound in all cases, at least the following - `` - `, U: Scoped>` -- `, U: Scoped>` +- `, U: Scoped>` - `` - `, U: ExternalScoped>` -- `, U: ExternalScoped>` +- `, U: ExternalScoped>` +- `` with `where Type: Trait` +- `` with `where Type: Trait` +- `` with `where Type: ExternalScoped` +- `` with `where Type: ExternalScoped` - for patterns with `U`: also cases where `T` is replaced with a concrete type - combinations of these patterns, regardless of how the connecting bounds are fulfilled @@ -1510,18 +1514,20 @@ where - `UnsafeGlobal` is fulfilled by a global implementation of an `unsafe` trait (including through imports (direct or indirect)), - `Scoped` is fulfilled by a scoped implementation, - `Sealed` is fulfilled by an implementation of a sealed trait, -- `ExternalScoped` is fulfilled by a scoped implementation defined in a different crate from where the bounded item is defined and +- `ExternalScoped` is fulfilled by a scoped implementation defined in a different crate from where the bounded item is defined, +- `Type` is **any** type (including generics) where global implementations in downstream crates can't fulfill the bound on it, +- `Trait` is fulfilled by **any** implementation and - the type parameter definitions and bounds may be split between an `impl` and e.g. `fn` or `type` (as what matters is only that the relation is visible generically in some way). Running afoul of this restriction produces the error [potentially-unsound-combination-of-implementations]. Unsafe and sealed traits are opted-out of imposing these limits in place of `UnsafeGlobal` if their definition has the new attribute `#[asserts_non_supertrait_impls(false)]`. In particular, all auto-traits like `Send`, `Sync` and `Unpin` and to my knowledge all other `unsafe` traits defined in the standard library can do this without issue. -Bounded items are opted out of imposing the limits containing `Sealed` on their uses if their definition has the new attribute `#[assumes_unique_impls(false)]`. Applying this attribute in turn triggers [potentially-unsound-combination-of-implementations] on nested uses of bounds combinations where the nested `ExternalScoped` bound is fulfilled through any outer bound (including indirectly through blanket implementations enabled by any outer bound). +Bounded items are opted out of imposing the limits containing `ExternalScoped` on their uses if their definition has the new attribute `#[assumes_unique_impls(false)]`. Applying this attribute in turn triggers [potentially-unsound-combination-of-implementations] on nested uses of bounds combinations where the nested `ExternalScoped` bound is fulfilled through any outer bound (including indirectly through blanket implementations enabled by any outer bound). > Depending on how much friction these rules cause, changing the default(s) may eventually be a candidate for inclusion in an edition change, but personally I wouldn't do this before scoped implementations have become a well-established part of the language. > -> The migrations for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe`-or-sealed traits' definitions without the attribute, and `#[assumes_unique_impls(true)]` to all public items where a sealed-trait-bound is combined with another as above. +> The migrations for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe`-or-sealed traits' definitions without *this* attribute, and `#[assumes_unique_impls(true)]` to all public items without *that* attribute wherever a sealed-trait-bound is combined with another as above. These limits don't apply to `unsafe` implementations in place of `UnsafeGlobal` that are originally implemented as scoped. Instead, it is unsound to expose (to external safe code) an originally-scoped `unsafe` implementation that asserts non-supertrait implementations. From 1d5ad3621212015b8be2006994cc10aaff55a6f1 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sat, 1 Jun 2024 19:58:38 +0200 Subject: [PATCH 26/27] Wording correction --- text/3634-scoped-impl-trait-for-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index 691e6b45678..a16a7bedf04 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1527,7 +1527,7 @@ Bounded items are opted out of imposing the limits containing `ExternalScoped` o > Depending on how much friction these rules cause, changing the default(s) may eventually be a candidate for inclusion in an edition change, but personally I wouldn't do this before scoped implementations have become a well-established part of the language. > -> The migrations for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe`-or-sealed traits' definitions without *this* attribute, and `#[assumes_unique_impls(true)]` to all public items without *that* attribute wherever a sealed-trait-bound is combined with another as above. +> The migrations for that would add `#[asserts_non_supertrait_impls(true)]` to all `unsafe`-or-sealed traits' definitions without *this* attribute, and `#[assumes_unique_impls(true)]` to all public items without *that* attribute wherever a sealed bound is combined with another as above. These limits don't apply to `unsafe` implementations in place of `UnsafeGlobal` that are originally implemented as scoped. Instead, it is unsound to expose (to external safe code) an originally-scoped `unsafe` implementation that asserts non-supertrait implementations. From 251e4c443757ebe7c079a8a3627e32f9b79d4d0e Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Sat, 1 Jun 2024 20:51:14 +0200 Subject: [PATCH 27/27] Further expanded and corrected "Forbidden implementation combinations" (again) --- text/3634-scoped-impl-trait-for-type.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/3634-scoped-impl-trait-for-type.md b/text/3634-scoped-impl-trait-for-type.md index a16a7bedf04..f2a3e4b190b 100644 --- a/text/3634-scoped-impl-trait-for-type.md +++ b/text/3634-scoped-impl-trait-for-type.md @@ -1502,6 +1502,8 @@ In order for existing code to remain sound in all cases, at least the following - `` - `, U: ExternalScoped>` - `, U: ExternalScoped>` +- `, U: ExternalScoped>` +- `, U: ExternalScoped>` - `` with `where Type: Trait` - `` with `where Type: Trait` - `` with `where Type: ExternalScoped` @@ -1515,10 +1517,15 @@ where - `Scoped` is fulfilled by a scoped implementation, - `Sealed` is fulfilled by an implementation of a sealed trait, - `ExternalScoped` is fulfilled by a scoped implementation defined in a different crate from where the bounded item is defined, +- `VisibleGlobal` is fulfilled by a global implementation potentially visible at the bounded item (i.e. not defined in the caller's crate), - `Type` is **any** type (including generics) where global implementations in downstream crates can't fulfill the bound on it, - `Trait` is fulfilled by **any** implementation and - the type parameter definitions and bounds may be split between an `impl` and e.g. `fn` or `type` (as what matters is only that the relation is visible generically in some way). +> For the patterns with `T: 'static + VisibleGlobal`, the implementation could check `TypeId::of::()` and then assume `U: ExternalScoped` to instead be the global implementation. +> +> It may be a good idea to slightly broaden some of these restrictions here for simplicity, but unfortunately adjusting them *at all* after scoped implementations are introduced could either be unsound or potentially break crates, and the migrations to mitigate that would still be somewhat messy. At the least, additional arguments to the attributes below would be needed. + Running afoul of this restriction produces the error [potentially-unsound-combination-of-implementations]. Unsafe and sealed traits are opted-out of imposing these limits in place of `UnsafeGlobal` if their definition has the new attribute `#[asserts_non_supertrait_impls(false)]`. In particular, all auto-traits like `Send`, `Sync` and `Unpin` and to my knowledge all other `unsafe` traits defined in the standard library can do this without issue.

`, `NonNull`, `Box`, `Rc`, `Arc`, `Weak`, `Option`, `Result`\*\*. - -Implementation-invariant generics never capture *implementation environments* on their own. Instead, their effective *implementation environments* follow that of their host, acting as if they were captured in the same scope. - -The type identity of implementation-invariant generics seen on their own does not depend on the *implementation environment*. This also means that the `TypeId` of `Option` does not take into account differences of implementations *on* `T`. However, differences of implementations *in* `T` can still distinguish the types, in cases where the type identity (and possibly `TypeId`) of `T` *itself* are different. An example for this are generic type parameters' effective types that can have bounds-relevant implementations observably baked into them. - -Hosts are: - -- Type aliases (see [type-aliases-are-opaque-to-scoped-implementations]), -- [implementation-aware-generics], -- types written as *QualifiedPathType* (see [grammar-changes] to *QualifiedPathType*) and -- the *function operand* of [call expressions] (see [call-expressions-function-operand-captures-its-implementation-environment]). - -\* superficially: The underlying instance may well use a captured implementation internally, but this isn't surfaced in signatures. For example, a closure defined where `usize: PartialOrd in reverse + Ord in reverse` is just `FnOnce(usize)` but will use `usize: PartialOrd in reverse + Ord in reverse` privately when called. - -\*\* but see [which-structs-should-be-implementation-invariant]. - -See also [why-specific-implementation-invariant-generics]. - -[call expressions]: https://doc.rust-lang.org/reference/expressions/call-expr.html#call-expressions - -## Call expressions' *function operand* captures its *implementation environment* -[call-expressions-function-operand-captures-its-implementation-environment]: #call-expressions-function-operand-captures-its-implementation-environment - -Call expressions capture the *implementation environment* in their *function operand*, acting as host for [implementation-invariant-generics]. This enables call expressions such as - -```rust -Option::::fmt(…) -``` - -where `fmt` receives the specified scoped implementation by observing it through the `T: Debug` bound on its implementing `impl` block. - -If no observing bound exists, code of this form should produce a warning spanning the `Trait in module` tokens. (see [unused-scoped-implementation]) - -## Type aliases are opaque to scoped implementations -[type-aliases-are-opaque-to-scoped-implementations]: #type-aliases-are-opaque-to-scoped-implementations - -As scoped `impl Trait for Type` is a fully lexically-scoped feature, the *implementation environment* present in a scope does not affect types hidden behind a type alias, except for the top-level type directly: - -```rust -trait Trait { - fn method(&self) -> &str; -} - -impl Trait for Type { - fn method(&self) -> &str { - "global" - } -} - -mod m1 { - use super::Type; - - pub type Alias = [Type; 1]; -} - -mod m2 { - use super::{Type, Trait}; - - pub use impl Trait for Type { - fn method(&self) -> &str { - "scoped" - } - } - - pub use impl Trait for [T; 1] { - fn method(&self) -> &str { - self[0].method() - } - } -} - -fn main() { - use m1::Alias; - use m2::{ - impl Trait for Type, - impl Trait for [Type; 1], - }; - - assert_eq!([Type].method(), "scoped"); - assert_eq!(Alias::default().method(), "global"); -} -``` - -Scoped implementations may still be observed through bounded generic type parameters on the type alias itself. (see [binding-choice-by-implementations-bounds]) - -## `TypeId` of generic type parameters' opaque types -[typeid-of-generic-type-parameters-opaque-types]: #typeid-of-generic-type-parameters-opaque-types - -In addition to the type identity of the specified type, the `TypeId` of opaque generic type parameter types varies according to the captured *implementation environment*, but *only according to implementations that are relevant to their bounds (including implicit bounds)*, so that the following program runs without panic: - -```rust -use std::any::TypeId; - -#[derive(Default)] -struct Type; -trait Trait {} -impl Trait for Type {} - -#[derive(Default)] -struct Generic(T); - -mod nested { - pub(super) use impl super::Trait for super::Type {} -} - -// `A` and `B` are distinct due to different captured implementation environments. -type A = Generic; -type B = Generic; - -fn no_bound(_: Generic, _: Generic) { - assert_eq!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::>(), TypeId::of::>()); - - assert_eq!(TypeId::of::(), TypeId::of::()); - assert_eq!(TypeId::of::(), TypeId::of::()); -} - -fn yes_bound(_: Generic, _: Generic) { - assert_ne!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::>(), TypeId::of::>()); - - assert_eq!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::(), TypeId::of::()); -} - -fn main() { - no_bound(A::default(), B::default()); - yes_bound(A::default(), B::default()); -} -``` - -In particular: - -- If no bound-relevant scoped implementations are captured in a type parameter, then the `TypeId` of the opaque type of that type parameter is identical to that of the discrete type specified for that type parameter. -- Distinct sets of bound-relevant captured scoped implementations lead to distinct `TypeId`s of the opaque type of a type parameter. -- If the set of bound-relevant captured scoped implementations in two generic type parameters is the same, and the wrapped discrete type is identical, then the `TypeId` of the opaque types of these generic type parameters is identical. -- If a generic type parameter is distinguishable this way, it remains distinguishable in called implementations even if those have fewer bounds - the relevant distinction is 'baked' into the generic type parameter's opaque type. - -These rules (and the transmutation permission in [layout-compatibility]) allow the following collection to remain sound with minimal perhaps unexpected behaviour: - -```rust -use std::{ - any::TypeId, - collections::{ - hash_map::{HashMap, RandomState}, - HashSet, - }, - hash::{BuildHasher, Hash}, - mem::drop, -}; - -use ondrop::OnDrop; - -#[derive(Default)] -pub struct ErasedHashSet<'a, S: 'a + BuildHasher + Clone = RandomState> { - storage: HashMap, - droppers: Vec>>, -} - -impl ErasedHashSet<'_, RandomState> { - pub fn new() -> Self { - Self::default() - } -} - -impl<'a, S: BuildHasher + Clone> ErasedHashSet<'a, S> { - pub fn with_hasher(hasher: S) -> Self { - Self { - storage: HashMap::with_hasher(hasher), - droppers: vec![], - } - } - - // This is the important part. - pub fn insert(&mut self, value: T) -> bool - where - T: Hash + Eq + 'static, // <-- Bounds. - { - let type_id = TypeId::of::(); // <-- `TypeId` depends on implementations of bounds. - let storage: *mut () = if let Some(storage) = self.storage.get_mut(&type_id) { - *storage - } else { - let pointer = Box::into_raw(Box::new(HashSet::::with_hasher( - self.storage.hasher().clone(), - ))); - self.droppers.push(OnDrop::new(Box::new(move || unsafe { - // SAFETY: Only called once when the `ErasedHashSet` is dropped. - // The type is still correct since the pointer wasn't `.cast()` yet and - // both `S` and `T` are bounded on `'a`, so they are still alive at this point. - drop(Box::from_raw(pointer)); - }))); - self.storage - .insert(type_id, pointer.cast::<()>()) - .expect("always succeeds") - }; - - let storage: &mut HashSet = unsafe { - // SAFETY: Created with (close to) identical type above. - // Different `Hash` and `Eq` implementations are baked into `T`'s identity because of the bounds, so they result in distinct `TypeId`s above. - // It's allowed to transmute between types that differ in identity only by bound-irrelevant captured implementations. - // The borrowed reference isn't returned. - &mut *(storage.cast::>()) - }; - storage.insert(value) - } - - // ... -} -``` - -In particular, this code will ignore any scoped implementations on `T` that are not `Hash`, `Eq` or (implicitly) `PartialEq`, while any combination of distinct discrete type and *implementation environments* with distinct `Hash`, `Eq` or `PartialEq` implementations is cleanly separated. - -See also [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for how to lint for an implementation of this collection that uses `TypeId::of::>()` as key, which *also* remains sound and deterministic but distinguishes too aggressively by irrelevant scoped implementations in consumer code, leading to unexpected behaviour. - -(For an example of `TypeId` behaviour, see [logical-consistency] [of-type-erased-collections].) - -## Layout-compatibility -[layout-compatibility]: #layout-compatibility - -Types whose identities are only distinct because of a difference in *implementation environments* remain layout-compatible as if one was a `#[repr(transparent)]` newtype of the other. - -It is sound to transmute an instance between these types **if** no inconsistency is observed on that instance by the bounds of any external-to-the-`transmute` implementation or combination of implementations, including scoped implementations and implementations on discrete variants of the generic. As a consequence, the `Self`-observed `TypeId` of instances of generic types **may** change in some cases. - -For example, given a library - -```rust -#[derive(Debug)] -pub struct Type(T); - -impl Type { - pub fn method(&self) {} -} -``` - -then in another crate - -- if `Debug` is used on an instance of `Type`, then this instance may *not* be transmuted to one where `T: Debug` uses a different implementation and have `Debug` used on it again afterwards, and -- if `Type::method()` is used on an instance of `Type`, then that instance may not be transmuted (and used) to or from any other variant, including ones that only differ by captured *implementation environment*, because `method` has observed the *exact* type parameter through its constraints. - -(In short: Don't use external-to-your-code implementations with the instance in any combination that wouldn't have been possible without transmuting the instance, pretending implementations can only observe the type identity according to their bounds.) - -See [typeid-of-generic-type-parameters-opaque-types] for details on what this partial transmutation permission is for, and [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a future incompatibility lint that could be used to warn implementations where this is relevant. - -## No interception/no proxies - -That each scoped `impl Trait for Type { /*...*/ }` is in scope for itself makes the use of the implementation it shadows in the consumer scope *inexpressible*. There can be no scoped implementation constrained to always shadow another. - -This is intentional, as it makes the following code trivial to reason about: - -```rust -{ - use a::{impl TheTrait for TheType}; // <-- Clearly unused, no hidden interdependencies. - { - use b::{impl TheTrait for TheType}; - // ... - } -} -``` - -(The main importance here is to not allow non-obvious dependencies of imports. Implementations can still access associated items of a *specific* other implementation by bringing it into a nested scope or binding to its associated items elsewhere. See also [independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations].) - -## Binding choice by implementations' bounds -[binding-choice-by-implementations-bounds]: #binding-choice-by-implementations-bounds - -Implementations bind to other implementations as follows: - -| `where`-clause¹ on `impl`? | binding-site of used trait | monomorphised by used trait? | -|-|-|-| -| Yes. | Bound at each binding-site of `impl`. | Yes, like-with or as-part-of type parameter distinction. | -| No. | Bound once at definition-site of `impl`. | No. | - -¹ Or equivalent generic type parameter bound, where applicable. For all purposes, this RFC treats them as semantically interchangeable. - -A convenient way to think about this is that *`impl`-implementations with bounds are blanket implementations over `Self` in different implementation environments*. - -Note that `Self`-bounds on associated functions do **not** cause additional monomorphic variants to be emitted, as these continue to only filter the surrounding implementation. - -Consider the following code with attention to the where clauses: - -```rust -struct Type; - -// ❶ - -trait Trait { fn function(); } -impl Trait for Type { fn function() { println!("global"); } } - -trait Monomorphic { fn monomorphic(); } -impl Monomorphic for Type { - fn monomorphic() { Type::function() } -} - -trait MonomorphicSubtrait: Trait { - fn monomorphic_subtrait() { Self::function(); } -} -impl MonomorphicSubtrait for Type {} - -trait Bounded { fn bounded(); } -impl Bounded for Type where Type: Trait { - fn bounded() { Type::function(); } -} - -trait BoundedSubtrait: Trait { - fn bounded_subtrait() { Type::function(); } -} -impl BoundedSubtrait for Type where Type: Trait {} - -trait FnBoundedMonomorphic { - fn where_trait() where Self: Trait { Self::function(); } - fn where_monomorphic_subtrait() where Self: MonomorphicSubtrait { Self::monomorphic_subtrait(); } -} -impl FnBoundedMonomorphic for Type {} - -trait NestedMonomorphic { fn nested_monomorphic(); } - -trait BoundedOnOther { fn bounded_on_other(); } -impl BoundedOnOther for () where Type: Trait { - fn bounded_on_other() { Type::function(); } -} - -Type::function(); // "global" -Type::monomorphic(); // "global" -Type::monomorphic_subtrait(); // "global" -Type::bounded(); // "global" -Type::bounded_subtrait(); // "global" -Type::where_trait(); // "global" -Type::where_monomorphic_subtrait(); // "global" -Type::nested_monomorphic(); // "scoped" -()::bounded_on_other(); // "global" - -{ - // ❷ - use impl Trait for Type { - fn function() { - println!("scoped"); - } - } - - // use impl FnBoundedMonomorphic for Type {} - // error: the trait bound `Type: MonomorphicSubtrait` is not satisfied - - Type::function(); // "scoped" - Type::monomorphic(); // "global" - // Type::monomorphic_subtrait(); // error; shadowed by scoped implementation - Type::bounded(); // "scoped" - Type::bounded_subtrait(); // "scoped" - Type::where_trait(); // "global" - Type::where_monomorphic_subtrait(); // "global" - Type::nested_monomorphic(); // "scoped" - ()::bounded_on_other(); // "global" - - { - // ❸ - use impl MonomorphicSubtrait for Type {} - use impl FnBoundedMonomorphic for Type {} - - impl NestedMonomorphic for Type { - fn nested_monomorphic() { Type::function() } - } - - Type::function(); // "scoped" - Type::monomorphic(); // "global" - Type::monomorphic_subtrait(); // "scoped" - Type::bounded(); // "scoped" - Type::bounded_subtrait(); // "scoped" - Type::where_trait(); // "scoped" - Type::where_monomorphic_subtrait(); // "scoped" - Type::nested_monomorphic(); // "scoped" - ()::bounded_on_other(); // "global" - } -} -``` - -The numbers ❶, ❷ and ❸ mark relevant item scopes. - -Generic item functions outside `impl` blocks bind and behave the same way as generic `impl`s with regard to scoped `impl Trait for Type`. - -### `Trait` / `::function` - -This is a plain monomorphic implementation with no dependencies. As there is a scoped implementation at ❷, that one is used in scopes ❷ and ❸. - -### `Monomorphic` / `::monomorphic` - -Another plain monomorphic implementations. - -As there is no bound, an implementation of `Trait` is bound locally in ❶ to resolve the `Type::function()`-call. - -This means that even though a different `use impl Trait for Type …` is applied in ❷, the global implementation remains in use when this `Monomorphic` implementation is called into from there and ❸. - -Note that the use of `Self` vs. `Type` in the non-default function body does not matter at all! - -### `MonomorphicSubtrait` / `::monomorphic_subtrait` - -Due to the supertrait, there is an implied bound `Self: Trait` *on the trait definition, but not on the implementation*. - -This means that the implementation remains monomorphic, and as such depends on the specific (global) implementation of `Trait` in scope at the `impl MonomorphicSubtrait …` in ❶. - -As this `Trait` implementation is shadowed in ❷, the `MonomorphicSubtrait` implementation is shadowed for consistency of calls to generics bounded on both traits. - -In ❸ there is a scoped implementation of `MonomorphicSubtrait`. As the default implementation is monomorphised for this implementation, it binds to the scoped implementation of `Trait` that is in scope here. - -### `Bounded` / `::bounded` - -The `Type: Trait` bound (can be written as `Self: Trait` – they are equivalent.) selects the `Bounded`-binding-site's `Type: Trait` implementation to be used, rather than the `impl Bounded for …`-site's. - -In ❶, this resolves to the global implementation as expected. - -For the scopes ❷ and ❸ together, `Bounded` gains one additional monomorphisation, as here another `Type: Trait` is in scope. - -### `BoundedSubtrait` / `::bounded_subtrait` - -As with `MonomorphicSubtrait`, the monomorphisation of `impl BoundedSubtrait for Type …` that is used in ❶ is shadowed in ❷. - -However, due to the `where Type: Trait` bound *on the implementation*, that implementation is polymorphic over `Trait for Type` implementations. This means a second monomorphisation is available in ❷ and its nested scope ❸. - -### `FnBoundedMonomorphic` - -`FnBoundedMonomorphic`'s implementations are monomorphic from the get-go just like `Monomorphic`'s. - -Due to the narrower bounds on functions, their availability can vary between receivers but always matches that of the global *implementation environment*: - -#### `::where_trait` - -Available everywhere since `Type: Trait` is in scope for both implementations of `FnBoundedMonomorphic`. - -In ❶, this resolves to the global implementation. - -In ❷, this *still* calls the global `::function()` implementation since the global `FnBoundedMonomorphic` implementation is *not* polymorphic over `Type: Trait`. - -In ❸, `FnBoundedMonomorphic` is monomorphically reimplemented for `Type`, which means it "picks up" the scoped `Type: Trait` implementation that's in scope there from ❷. - -#### `::where_monomorphic_subtrait` - -In ❶, this resolves to the global implementation. - -In ❷, this *still* calls the global `::monomorphic_subtrait()` implementation since the global `FnBoundedMonomorphic` implementation is *not* polymorphic over `Type: Trait`. - -Note that `FnBoundedMonomorphic` *cannot* be reimplemented in ❷ since the bound `Type: MonomorphicSubtrait` on its associated function isn't available in that scope, which would cause a difference in the availability of associated functions (which would cause a mismatch when casting to `dyn FnBoundedMonomorphic`). - -> It may be better to allow `use impl FnBoundedMonomorphic for Type {}` without `where_monomorphic_subtrait` in ❷ and disallow incompatible unsizing instead. I'm not sure about the best approach here. - -In ❸, `FnBoundedMonomorphic` is monomorphically reimplemented for `Type`, which means it "picks up" the scoped `Type: Trait` implementation that's in scope there from ❷. - -### `NestedMonomorphic` / `::nested_monomorphic` - -The global implementation of `NestedMonomorphic` in ❸ the binds to the scoped implementation of `Trait` on `Type` from ❷ internally. This allows outside code to call into that function indirectly without exposing the scoped implementation itself. - -### `BoundedOnOther` / `::bounded_on_other` - -As this discrete implementation's bound isn't over the `Self` type (and does not involved generics), it continues to act only as assertion and remains monomorphic. - -## Binding and generics - -`where`-clauses without generics or `Self` type, like `where (): Debug`, **do not** affect binding of implementations within an `impl` or `fn`, as the non-type-parameter-type `()` is unable to receive an *implementation environment* from the discretisation site. - -However, `where (): From` **does** take scoped implementations into account because the blanket `impl From for U where T: Into {}` is sensitive to `T: Into<()>` which is part of the *implementation environment* captured in `T`! - -This sensitivity even extends to scoped `use impl From for ()` at the discretisation site, as the inverse blanket implementation of `Into` creates a scoped implementation of `Into` wherever a scoped implementation of `From` exists. -This way, existing symmetries are fully preserved in all contexts. - -## Implicit shadowing of subtrait implementations - -Take this code for example: - -```rust -use std::ops::{Deref, DerefMut}; - -struct Type1(Type2); -struct Type2; - -impl Deref for Type1 { - type Target = Type2; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Type1 { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -fn function1(_x: impl Deref + DerefMut) {} -fn function2(x: impl DerefMut) { - x.deref(); -} - -{ - use impl Deref for Type1 { - type Target = (); - - fn deref(&self) -> &Self::Target { - &() - } - } - - // function1(Type1(Type2)); // <-- Clearly impossible. - // function2(Type1(Type2)); // <-- Unexpected behaviour if allowed. -} -``` - -Clearly, `function1` cannot be used here, as its generic bounds would have to bind to incompatible implementations. - -But what about `function2`? Here, the bound is implicit but `Deref::deref` could still be accessed if the function could be called. For type compatibility, this would have to be the shadowed global implementation, which is most likely unintended decoherence. - -As such, **shadowing a trait implementation also shadows all respective subtrait implementations**. Note that the subtrait *may* still be immediately available (again), if it is implemented with a generic target and all bounds can be satisfied in the relevant scope: - -```rust -trait Trait1 { - fn trait1(&self); -} -trait Trait2: Trait1 { // <-- Subtrait of Trait1. - fn uses_trait1(&self) { - self.trait1(); - } -} -impl Trait2 for T {} // <-- Blanket implementation with bounds satisfiable in scope. - -struct Type; -impl Trait1 for Type { - fn trait1(&self) { - print!("global"); - } -} - -{ - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped"); - } - } - - Type.uses_trait1(); // Works, prints "scoped". -} -``` - -If a subtrait implementation is brought into scope, it must be either an implementation with a generic target, or an implementation on a discrete type making use of the identical supertrait implementations in that scope. (This rule is automatically fulfilled by scoped implementation definitions, so it's only relevant for which scoped implementations can be imported via `use`-declaration.) - -## Independent trait implementations on discrete types may still call shadowed implementations -[independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations]: #independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations - -Going back to the previous example, but now implementing `Trait2` independently without `Trait1` in its supertraits: - -```rust -trait Trait1 { - fn trait1(&self); -} -trait Trait2 { // <-- Not a subtrait of `Trait1`. - fn uses_trait1(&self); -} -impl Trait2 for Type { // <-- Implementation on discrete type. - fn uses_trait1(&self) { - self.trait1(); - } -} - -struct Type; -impl Trait1 for Type { - fn trait1(&self) { - print!("global"); - } -} - -{ - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped"); - } - } - - Type.uses_trait1(); // Works, prints "global". -} -``` - -In this case, the implementation of `Trait2` is *not* shadowed at all. Additionally, since `self.trait1();` here binds `Trait` on `Type` directly, rather than on a bounded generic type parameter, it uses whichever `impl Trait1 for Type` is in scope *where it is written*. - -## Resolution on generic type parameters -[resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters - -Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `use impl Trait for T`. - -However, a blanket implementation can only be bound on a generic type parameter iff its bounds are fully covered by the generic type parameter's bounds and other available trait implementations on the generic type parameter, in the same way as this applies for global implementations. - -## Method resolution to scoped implementation without trait in scope - -[Method calls] can bind to scoped implementations even when the declaring trait is not separately imported. For example: - -```rust -struct Type; -struct Type2; - -mod nested { - trait Trait { - fn method(&self) {} - } -} - -use impl nested::Trait for Type {} -impl nested::Trait for Type2 {} - -Type.method(); // Compiles. -Type2.method(); // error[E0599]: no method named `method` found for struct `Type2` in the current scope -``` - -This also equally (importantly) applies to scoped implementations imported from elsewhere. - -[Method calls]: https://doc.rust-lang.org/book/ch05-03-method-syntax.html#method-syntax - -## Scoped implementations do not implicitly bring the trait into scope - -This so that no method calls on other types become ambiguous: - -```rust -struct Type; -struct Type2; - -mod nested { - trait Trait { - fn method(&self) {} - } - - trait Trait2 { - fn method(&self) {} - } -} - -use nested::Trait2; -impl Trait2 for Type {} -impl Trait2 for Type2 {} - -use impl nested::Trait for Type {} -impl nested::Trait for Type2 {} - -Type.method(); // Compiles, binds to scoped implementation of `Trait`. -Type2.method(); // Compiles, binds to global implementation of `Trait2`. -``` - -(If `Trait` was not yet globally implemented for `Type2`, and `Trait` and `Type2` were defined in other crates, then bringing `Trait` into scope here could introduce instability towards that implementation later being added in one of those crates.) - -## Shadowing with different bounds - -Scoped implementations may have different bounds compared to an implementation they (partially) shadow. The compiler will attempt to satisfy those bounds, but if they are not satisfied, then the other implementation is not shadowed for that set of generic type parameters and no additional warning or error is raised. - -(Warnings for e.g. unused scoped implementations and scoped implementations that only shadow a covering global implementation are still applied as normal. It's just that partial shadowing with different bounds is likely a common use-case in macros.) - -```rust -struct Type1; -struct Type2; - -trait Trait1 { - fn trait1() { - println!("1"); - } -} -impl Trait1 for T {} // <-- - -trait Trait2 { - fn trait2() { - println!("2"); - } -} -impl Trait2 for Type2 {} // <-- - -trait Say { - fn say(); -} -impl Say for T -where - T: Trait1, // <-- -{ - fn say() { - T::trait1(); - } -} - -{ - use impl Say for T - where - T: Trait2 // <-- - { - fn say() { - T::trait2(); - } - } - - Type1::say(); // 1 - Type2::say(); // 2 -} -``` - -## No priority over type-associated methods - -Scoped `impl Trait for Type` has *the same* method resolution priority as an equivalent global implementation would have if it was visible for method-binding in that scope. This means that directly type-associated functions still bind with higher priority than those available through scoped implementations. - -## Coercion to trait objects - -Due to the coercion into a trait object in the following code, the scoped implementation becomes attached to the value through the pointer meta data. This means it can then be called from other scopes: - -```rust -use std::fmt::{self, Display, Formatter}; - -fn function() -> &'static dyn Display { - use impl Display for () { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "scoped") - } - } - - &() -} - -println!("{}", function()); // "scoped" -``` - -This behaves exactly as a global implementation would. - -Note that the [`DynMetadata`]s of the reference returned above and one that uses the global implementation would compare as distinct even if both are "`&()`". - -[`DynMetadata`]: https://doc.rust-lang.org/stable/core/ptr/struct.DynMetadata.html - -## Interaction with return-position `impl Trait` - -Consider the following functions: - -```rust -trait Trait {} - -fn function() -> impl Trait { - use impl Trait for () {} - - () // Binds on trailing `()`-expression. -} - -fn function2() -> impl Trait { - use impl Trait for () {} - - {} // Binds on trailing `{}`-block used as expression. -} -``` - -In this case, the returned opaque types use the respective inner scoped implementation, as it binds on the `()` expression. - -The following functions do not compile, as the implicitly returned `()` is not stated *inside* the scope where the implementation is available: - -```rust -trait Trait {} - -fn function() -> impl Trait { - ^^^^^^^^^^ - use impl Trait for () {} - --------------------- - - // Cannot bind on implicit `()` returned by function body without trailing *Expression*. -} - -fn function2() -> impl Trait { - ^^^^^^^^^^ - use impl Trait for () {} - --------------------- - - return; // Cannot bind on `return` without expression. - ------- -} -``` - -(The errors should ideally also point at the scoped implementations here with a secondary highlight, and suggest stating the return value explicitly.) - -The binding must be consistent: - -```rust -trait Trait {} - -fn function() -> impl Trait { - // error: Inconsistent implementation of opaque return type. - if true { - use impl Trait for () {} - return (); - ---------- - } else { - use impl Trait for () {} - return (); - ^^^^^^^^^^ - } -} -``` - -This function *does* compile, as the outer scoped `impl Trait for ()` is bound on the `if`-`else`-expression as a whole. - -```rust -trait Trait {} - -fn function() -> impl Trait { - use impl Trait for () {} - - if true { - use impl Trait for () {} // warning: unused scoped implementation - () - } else { - use impl Trait for () {} // warning: unused scoped implementation - () - } -} -``` - -This compiles because the end of the function is not reachable: - -```rust -trait Trait {} - -fn function() -> impl Trait { - { - use impl Trait for () {} - return (); // Explicit `return` is required to bind in the inner scope. - } -} -``` - -## Static interception of dynamic calls - -As a consequence of binding outside of generic contexts, it *is* possible to statically wrap *specific* trait implementations on *concrete* types. This includes the inherent implementations on trait objects: - -```rust -use std::fmt::{self, Display, Formatter}; - -{ - use impl Display for dyn Display { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Restore binding to inherent global implementation within this function. - use ::{impl Display for dyn Display}; - - write!(f, "Hello! ")?; - d.fmt(f)?; - write!(f, " See you!") - } - } - - let question = "What's up?"; // &str - println!("{question}"); // "What's up?" - - let question: &dyn Display = &question; - println!("{question}"); // Binds to the scoped implementation; "Hello! What's up? See you!" -} -``` - -## Warnings - -### Unused scoped implementation -[unused-scoped-implementation]: #unused-scoped-implementation - -Scoped implementations and `use`-declarations of such (including those written as *ImplEnvironmentEntry*) receive a warning if unused. This can also happen if a `use`-declaration only reapplies a scoped implementation that is inherited from a surrounding item scope. - -(rust-analyzer should suggest removing any unused `use`-declarations as fix in either case.) - -An important counter-example: - -Filename: library/src/lib.rs - -```rust -pub struct Type; -pub struct Generic; - -pub trait Trait {} -use impl Trait for Type {} - -pub type Alias = Generic; -``` - -Filename: main.rs -```rust -use std::any::TypeId; - -use library::{Alias, Generic, Type}; - -assert_ne!(TypeId::of::(), TypeId::of::>()); -``` - -Here, the scoped implementation `use impl Trait for Type {}` **is** accounted for as it is captured into the type identity of `Alias`. - -Since `Alias` is exported, the compiler cannot determine within the library alone that the type identity is unobserved. If it can ensure that that is the case, a (different!) warning could in theory still be shown here. - -### Global trait implementation available -[global-trait-implementation-available]: #global-trait-implementation-available - -Scoped implementations and `use`-declarations of such receive a specific warning if only shadowing a global implementation that would fully cover them. This warning also informs about the origin of the global implementation, with a "defined here" marker if in the same workspace. This warning is not applied to scoped implementations that *at all* shadow another scoped implementation. - -(Partial overlap with a shadowed scoped implementation should be enough to suppress this because setting the import up to be a precise subset could get complex fairly quickly. In theory just copying `where`-clauses is enough, but in practice the amount required could overall scale with the square of scoped implementation shadowing depth and some imports may even have to be duplicated.) - -It would make sense to let the definitions and also alternatively specific global implementations of traits with high implementation stability requirements like `serde::{Deserialize, Serialize}` deactivate this warning too, so that the latter don't cause it on the respective covered scoped implementations. - -### Self-referential bound of scoped implementation - -```rust -trait Foo { } - -use impl Foo for T where T: Foo { } - --------- ^^^^^^ -``` - -A Rust developer may want to write the above to mean 'this scoped implementation can only be used on types that already implement this trait' or 'this scoped implementation uses functionality of the shadowed implementation'. However, since scoped `impl Trait for Type` uses item scope rules, any shadowed implementation is functionally absent in the entire scope. As such, this implementation, like the equivalent global implementation, cannot apply to any types at all. - -The warning should explain that and why the bound is impossible to satisfy. - -### Private supertrait implementation required by public implementation -[private-supertrait-implementation-required-by-public-implementation]: #private-supertrait-implementation-required-by-public-implementation - -Consider the following code: - -```rust -pub struct Type; - -use impl PartialEq for Type { - // ... -} - -pub use impl Eq for Type {} -``` - -Here, the public implementation relies strictly on the private implementation to also be available. This means it effectively cannot be imported in `use`-declarations outside this module. - -See also the error [incompatible-or-missing-supertrait-implementation]. - -### Public implementation of private trait/on private type - -The code - -```rust -struct Type; -trait Trait {} - -pub use impl Trait for Type {} - ^^^^^ ^^^^ -``` - -should produce two distinct warnings similarly to those for private items in public signatures, as the limited visibilities of `Type` and `Trait` independently prevent the implementation from being imported in modules for which it is declared as visible. - -### Scoped implementation is less visible than item/field it is captured in -[scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in - -The code - -```rust -pub struct Type; -pub struct Generic(U, V); - -trait Trait {} // <-- Visibility of the trait doesn't matter for *this* warning. - -use impl Trait for Type {} ------------------------ - -pub type Alias = Generic; - ^^^^ ^^^^ - -pub fn function(value: Generic) -> Generic { - ^^^^ ^^^^ ^^^^ ^^^^ - value -} - -pub struct Struct { - private: Generic, // This is fine. - pub public: Generic, - ^^^^ ^^^^ -} -``` - -should produce eight warnings (or four/three warnings with multiple primary spans each, if possible). The warning should explain that the type can't be referred to by fully specified name outside the crate/module and that the implementation may be callable from code outside the crate/module. - -If the binding is specified via inline *implementation environment*, then the warning should show up on the `Trait in module` span instead. - -Note that as with other private-in-public warnings, replacing - -```rust -use impl Trait for Type {} -``` - -with - -```rust -mod nested { - use super::{Trait, Type}; - pub use impl Trait for Type {} -} -use nested::{impl Trait for Type}; -``` - -in the code sample above should silence the warning. - -In some cases, adding `as Trait in ::` to the generic type argument could be suggested as quick-fix, though generally it's better to fix this warning by moving the scoped implementation into a nested scope or moving it into a module and importing it into nested scopes as needed. - -> This warning can't be suppressed for private traits because the presence of their scoped implementation on a generic type parameter still affects the `TypeId` of the capturing generic, which here is visible outside of the discretising module. - -### Imported implementation is less visible than item/field it is captured in -[imported-implementation-is-less-visible-than-itemfield-it-is-captured-in]: #imported-implementation-is-less-visible-than-itemfield-it-is-captured-in - -This occurs under the same circumstances as above, except that - -```rust -trait Trait {} -use impl Trait for Type {} -``` - -is replaced with - -```rust -use a_crate::{ - Trait, - impl Trait for Type, -}; -``` - -(where here the implementation import is subsetting a blanket import, but that technicality isn't relevant. What matters is that the implementation is from another crate). - -If the imported implementation is captured in a public item's signature, that can accidentally create a public dependency. As such this should be a warning too (unless something from that crate occurs explicitly in that public signature or item?). - -## Errors - -### Global implementation of trait where global implementation of supertrait is shadowed - -A trait cannot be implemented globally for a discrete type in a scope where the global implementation of any of its supertraits is shadowed on that type. - -```rust -struct Type; - -trait Super {} -trait Sub: Super {} - -impl Super for Type {} - -{ - use impl Super for Type {} - ----------------------- // <-- Scoped implementation defined/imported here. - - impl Sub for Type {} - ^^^^^^^^^^^^^^^^^ //<-- error: global implementation of trait where global implementation of supertrait is shadowed -} -``` - -### Negative scoped implementation -[negative-scoped-implementation]: #negative-scoped-implementation - -This occurs on all negative scoped implementations. Negative scoped implementations can be parsed, but are rejected shortly after macros are applied. - -```rust -struct Type; -trait Trait {} - -impl Trait for Type {} - -{ - use impl !Trait for Type {} - ^^^^^^^^^^^^^^^^^^^^^^^^ error: negative scoped implementation -} -``` - -### Incompatible or missing supertrait implementation -[incompatible-or-missing-supertrait-implementation]: #incompatible-or-missing-supertrait-implementation - -Implementations of traits on discrete types require a specific implementation of each of their supertraits, as they bind to them at their definition, so they cannot be used without those being in scope too (to avoid perceived and hard to reason-about inconsistencies). - -```rust -struct Type; -trait Super {} -trait Sub: Super {} - -impl Super for Type {} - -mod nested { - pub use impl Super for Type {} - pub use impl Sub for Type {} -} - -use nested::{impl Sub for Type}; - ^^^^^^^^^^^^^^^^^ error: incompatible supertrait implementation -``` - -Rustc should suggest to import the required scoped implementation, if possible. - -See also the warning [private-supertrait-implementation-required-by-public-implementation]. See also [implicit-import-of-supertrait-implementations-of-scoped-implementations-defined-on-discrete-types] for a potential way to improve the ergonomics here. - -### Scoped implementation of external sealed trait -[scoped-implementation-of-external-sealed-trait]: #scoped-implementation-of-external-sealed-trait - -Given crate `a`: - -```rust -mod private { - pub trait Sealing {} -} -use private::Sealing; - -pub trait Sealed: Sealing {} - -pub use impl Sealed for T {} // Ok. -``` - -And crate `b`: - -```rust -use a::{ - Sealed, - impl Sealed for usize, // Ok. -}; - -use impl Sealed for () {} // Error. - ^^^^^^ -``` - -Crate `b` cannot define scoped implementations of the external sealed trait `Sealed`, but can still import them. - -See [no-external-scoped-implementations-of-sealed-traits] for why this is necessary. - -## Behaviour change/Warning: `TypeId` of implementation-aware generic discretised using generic type parameters -[behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]: #behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters - -As a result of the transmutation permission given in [layout-compatibility], which is needed to let the `ErasedHashSet` example in [typeid-of-generic-type-parameters-opaque-types] *remain sound*, monomorphisations of a function that observe distinct `TypeId`s for [implementation-aware-generics] they discretise using type parameters may be called on the same value instance. - -Notably, this affects `TypeId::of::()` in implementations with most generic targets, but not in unspecific blanket implementations on the type parameter itself. - -This would have to become a future incompatibility lint ahead of time, and should also remain a warning after the feature is implemented since the behaviour of `TypeId::of::()` in generics is likely to be unexpected. - -In most cases, implementations should change this to `TypeId::of::()`, where `T` is the type parameter used for discretisation, since that should show only the expected `TypeId` distinction. - -Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can be used, as tuples are [implementation-invariant-generics]. - -# Drawbacks -[drawbacks]: #drawbacks - -Why should we *not* do this? - -## First-party implementation assumptions in macros -[first-party-implementation-assumptions-in-macros]: #first-party-implementation-assumptions-in-macros - -If a macro outputs a call of the form `<$crate::Type as $crate::Trait>::method()`, it can currently make safety-critical assumptions about implementation details of the `method` that is called iff implemented in the same crate. - -(This should also be considered relevant for library/proc-macro crate pairs where the macro crate is considered an implementation detail of the library even where the macro doesn't require an `unsafe` token in its input, even though "crate privacy" currently isn't formally representable towards Cargo.) - -As such, **newly allowing the global trait implementation to be shadowed here can introduce soundness holes** iff `Trait` is not `unsafe` or exempt from scoped implementations. - -(I couldn't come up with a good example for this. There might be a slim chance that it's not actually a practical issue in the ecosystem. Unfortunately, this seems to be very difficult to lint for.) - -There are a few ways to mitigate this, but they all have significant drawbacks: - -- Opt-in scoped-`impl Trait` transparency for macros - - This would make scoped `impl Trait for Type`s much less useful, as they couldn't be used with for example some derive macros by default. It would also be necessary to teach the opt-in along with macros, which may not be realistic considering existing community-made macro primers. - - Implementation is likely complicated because many procedural macros emit tokens only with `Span::call_site()` hygiene, so information on the distinct binding site origin may not be readily available. - - This could be limited to existing kinds of macro definitions, so that future revised macro systems can be opted in by default. Future macros could use an `unsafe` trait instead to assume an implementation, or make use of scoped `impl Trait for Type` to enforce a specific implementation in their output. - - Drawback: Whether globally implemented behaviour can be changed by the consumer would depend on the macro. It would be good to surface a transparency opt-in in the documentation here. - -- Opt-in scoped-`impl Trait` *priority* for macros - - This would preserve practical usefulness of the proposed feature in most cases. - - This would add significant complexity to the feature, as resolution of scoped implementations wouldn't be exactly the same as for other items. (We should otherwise warn if a scoped `impl Trait for Type` outside a macro shadows binding a global implementation inside of it though, so at least the feature implementation complexity may be net zero in this regard.) - - This could be limited to existing kinds of macro definitions, with the same implications as for opt-in transparency above. - - Drawback: Whether globally implemented behaviour can be changed by the consumer would depend on the macro. It would be good to surface a priority opt-in in the documentation here. - -- Forbid scoped `impl Trait for Type` if `Trait` and `Type` are from the same crate - - This would at best be a partial fix and would block some interesting uses of [using-scoped-implementations-to-implement-external-traits-on-external-types]. - -## Unexpected behaviour of `TypeId::of::()` in implementations on generics in the consumer-side presence of scoped implementations and `transmute` - -As explained in [rustdoc-documentation-changes], [layout-compatibility] and [type-identity-of-generic-types], an observed `TypeId` can change for an instance under specific circumstances that are previously-legal `transmute`s as e.g. for the `HashSet`s inside the type-erased value-keyed collection like the `ErasedHashSet` example in the [typeid-of-generic-type-parameters-opaque-types] section. - -This use case appears to be niche enough in Rust to not have an obvious example on crates.io, but see [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters] for a lint that aims to mitigate issues in this regard and could be used to survey potential issues. - -## More `use`-declaration clutter, potential inconsistencies between files - -If many scoped implementations need to be imported, this could cause the list of `use`-declarations to become less readable. If there are multiple alternatives available, inconsistencies could sneak in between modules (especially if scoped `impl Trait for Type` is used in combination with [specialisation](https://rust-lang.github.io/rfcs/1210-impl-specialization.html)). - -This can largely be mitigated by centralising a crate's scoped trait imports and implementations in one module, then wildcard-importing its items: - -```rust -// lib.rs -mod scoped_impls; -use scoped_impls::*; -``` - -```rust -// scoped_impls.rs -use std::fmt::Debug; - -use a::{TypeA, TraitA}; -use b::{TypeB, TraitB}; - -pub use a_b_glue::{impl TraitA for TypeB, impl TraitB for TypeA}; -// ... - -pub use impl Debug for TypeA { - // ... -} -pub use impl Debug for TypeB { - // ... -} - -// ... -``` - -```rust -// other .rs files -use crate::scoped_impls::*; -``` - -## Type inference has to consider both scoped and global implementations - -Complexity aside, this could cause compiler performance issues since caching would be less helpful. - -Fortunately, at least checking whether scoped implementations exist at all for a given trait and item scope should be reasonably inexpensive, so this hopefully won't noticeably slow down compilation of existing code. - -That *implementation environment* binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. - -## Cost of additional monomorphised implementation instances - -The additional instantiations of implementations resulting from [binding-choice-by-implementations-bounds] could have a detrimental effect on compile times and .text size (depending on optimisations). - -This isn't unusual for anything involving *GenericParams*, but use of this feature could act as a multiplier to some extent. It's likely a good idea to evaluate relatively fine-grained caching in this regard, if that isn't in place already. - -## Split type identity may be unexpected -[split-type-identity-may-be-unexpected]: #split-type-identity-may-be-unexpected - -Consider crates like `inventory` or Bevy's systems and queries. - -There may be tricky to debug issues for their consumers if a `TypeId` doesn't match between uses of generics with superficially the same type parameters, especially without prior knowledge of distinction by captured *implementation environments*. - -A partial mitigation would be to have rustc include captured scoped implementations on generic type parameters when printing types, but that wouldn't solve the issue entirely. - -Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct but bound-irrelevant captured implementations directly, as long as only these top-level implementations differ and no nested captured *implementation environments* do. - -## Marking a generic as implementation-invariant is a breaking change - -This concerns the split of [implementation-aware-generics] and [implementation-invariant-generics]. - -"Implementation-aware" is the logic-safe default. - -"Implementation-invariant" has better ergonomics in some cases. - -It would be great to make moving from the default here only a feature addition. To do this, a new coherence rule would likely have to be introduced to make implementations conflict if any type becoming implementation-invariant would make them conflict, and additionally to make such implementations shadow each other (to avoid all-too-unexpected silent behaviour changes). - -However, even that would not mitigate the behaviour change of type-erasing collections that are keyed on such generics that become type-invariant later, so making this a breaking change is simpler and overall more flexible. - -# Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives - -## Avoid newtypes' pain points - -Alternative keywords: ergonomics and compatibility. - -### Recursively dependent `#[derive(…)]` - -Many derives, like `Clone`, `Debug`, partial comparisons, `serde::{Deserialize, Serialize}` and `bevy_reflect::{FromReflect, Reflect}` require the trait to be implemented for each field type. Even with the more common third-party traits like Serde's, there are many crates with useful data structures that do not implement these traits directly. - -As such, glue code is necessary. - -#### Current pattern - -Some crates go out of their way to provide a compatibility mechanism for their derives, but this is neither the default nor has it (if available) any sort of consistency between crates, which means finding and interacting with these mechanisms requires studying the crate's documentation in detail. - -For derives that do not provide such a mechanism, often only newtypes like `NewSerdeCompatible` and `NewNeitherCompatible` below can be used. However, these do not automatically forward all traits (and forwarding implementations may be considerably more painful than the `derive`s), so additional glue code between glue crates may be necessary. - -```rust -use bevy_reflect::Reflect; -use serde::{Deserialize, Serialize}; - -use bevy_compatible::BevyCompatible; -use neither_compatible::NeitherCompatible; -use serde_compatible::SerdeCompatible; - -// I could not actually find much information on how to implement the Bevy-glue. -// I assume it's possible to provide at least this API by creating a newtype and implementing the traits manually. -use bevy_compatible_serde_glue::BevyCompatibleDef; -use neither_compatible_bevy_glue::NewNeitherCompatible; // Assumed to have `From`, `Into` conversions. -use neither_compatible_serde_glue::NeitherCompatibleDef; -use serde_compatible_bevy_glue::NewSerdeCompatible; // Assumed to have `From`, `Into` conversions. - -/// A typical data transfer object as it may appear in a service API. -#[derive(Deserialize, Serialize, Reflect)] -#[non_exhaustive] // Just a reminder, since the fields aren't public anyway. -pub struct DataBundle { - // Serde provides a way to use external implementations on fields (but it has to be specified for each field separately). - // Bevy does not have such a mechanism so far, so newtypes are required. - // The newtypes should be an implementation detail, so the fields are (for consistency all) private. - #[serde(with = "NewSerdeCompatibleDef")] - serde: NewSerdeCompatible, - #[serde(with = "BevyCompatibleDef")] - bevy: BevyCompatible, - #[serde(with = "NewNeitherCompatibleDef")] - neither: NewNeitherCompatible, -} - -// Some of the newtypes don't implement `Default` (maybe it was added to the underlying types later and the glue crate doesn't want to bump the dependency), -// so this has to be implemented semi-manually instead of using the `derive`-macro. -impl Default for DataBundle { - fn default() -> Self { - DataBundleParts::default().into() - } -} - -// If the Bevy glue doesn't forward the Serde implementations, this is necessary. -#[derive(Deserialize, Serialize)] -#[serde(remote = "NewSerdeCompatible")] -#[serde(transparent)] -struct NewSerdeCompatibleDef(SerdeCompatible); - -// Same as above, but here the implementation is redirected to another glue crate. -#[derive(Deserialize, Serialize)] -#[serde(remote = "NewNeitherCompatible")] -#[serde(transparent)] -struct NewNeitherCompatibleDef(#[serde(with = "NeitherCompatibleDef")] NeitherCompatible); - -impl DataBundle { - // These conversions are associated functions for discoverability. - pub fn from_parts(parts: DataBundleParts) -> Self { - parts.into() - } - pub fn into_parts(self) -> DataBundleParts { - self.into() - } - - // Necessary to mutate multiple fields at once. - pub fn parts_mut(&mut self) -> DataBundlePartsMut<'_> { - DataBundlePartsMut { - serde: &mut self.serde.0, - bevy: &mut self.bevy, - neither: &mut self.neither.0, - } - } - - // Accessors to the actual instances with the public types. - pub fn serde(&self) -> &SerdeCompatible { - &self.serde.0 - } - pub fn serde_mut(&mut self) -> &mut SerdeCompatible { - &mut self.serde.0 - } - - // This also uses an accessor just for consistency. - pub fn bevy(&self) -> &BevyCompatible { - &self.bevy - } - pub fn bevy_mut(&mut self) -> &mut BevyCompatible { - &mut self.bevy - } - - // More accessors. - pub fn neither(&self) -> &NeitherCompatible { - &self.neither.0 - } - pub fn neither_mut(&mut self) -> &mut NeitherCompatible { - &mut self.neither.0 - } -} - -// Conversions for convenience -impl From for DataBundle { - fn from(value: DataBundleParts) -> Self { - Self { - serde: value.serde.into(), - bevy: value.bevy.into(), - neither: value.neither.into(), - } - } -} - -impl From for DataBundleParts { - fn from(value: DataBundle) -> Self { - Self { - serde: value.serde.into(), - bevy: value.bevy, - neither: value.neither.into(), - } - } -} - -/// Used to construct and destructure [`DataBundle`]. -#[derive(Default)] // Assume that all the actual field types have useful defaults. -#[non_exhaustive] -pub struct DataBundleParts { - pub serde: SerdeCompatible, - pub bevy: BevyCompatible, - pub neither: NeitherCompatible, -} - -/// Return type of [`DataBundle::parts_mut`]. -#[non_exhaustive] -pub struct DataBundlePartsMut<'a> { - pub serde: &'a mut SerdeCompatible, - pub bevy: &'a mut BevyCompatible, - pub neither: &'a mut NeitherCompatible, -} -``` - -If two traits that require newtype wrappers need to be added for the same type, the process can be even more painful than what's shown above, involving `unsafe` reinterpret casts to borrow a wrapped value correctly as each newtype and forwarding-implementing each trait manually if no transparent derive is available. - -#### With scoped `impl Trait for Type` - -Scoped `impl Trait for Type` eliminates these issues, in a standardised way that doesn't require any special consideration from the trait or derive crates: - -```rust -use bevy_reflect::Reflect; -use serde::{Deserialize, Serialize}; - -use bevy_compatible::BevyCompatible; -use neither_compatible::NeitherCompatible; -use serde_compatible::SerdeCompatible; - -// I could not actually find much information on how to implement Bevy-glue. -// It's about the same as manually implementing the traits for newtypes, though. -// Since many traits are required for `bevy_reflect`'s derives, those glue crates use the prelude pattern and provide one for each target type. -use bevy_compatible_serde_glue::{ - impl Deserialize<'_> for BevyCompatible, - impl Serialize for BevyCompatible, -}; -use neither_compatible_bevy_glue::preludes::neither_compatible::*; -use neither_compatible_serde_glue::{ - impl Deserialize<'_> for NeitherCompatible, - impl Serialize for NeitherCompatible, -}; -use serde_compatible_bevy_glue::preludes::serde_compatible::*; - -/// A typical data transfer object as it may appear in a service API. -#[derive(Default, Deserialize, Serialize, Reflect)] -#[non_exhaustive] -pub struct DataBundle { - // Everything just works. - pub serde: SerdeCompatible, - pub bevy: BevyCompatible, - pub neither: NeitherCompatible, -} - -// `Default` was derived normally. -// No glue for the glue is necessary. -// No conversions are needed to construct or destructure. -// `&mut`-splitting is provided seamlessly by Rust. -// No accessors are needed since the fields are public. -``` - -Even in cases where the glue API cannot be removed, it's still possible to switch to this simplified, easier to consume implementation and deprecate the original indirect API. - -Note that the imported scoped implementations are *not* visible in the public API here, since they do not appear on generic type parameters in public items. There may still be situations in which defining a type alias is necessary to keep some scoped implementations away from generic type parameters. In some cases, it could be enough to add `as Trait in ::` to generic type arguments to restore their *implementation environment* to contain global implementations only. - -> In some cases, where a field type is quoted in a derive macro directly, writing `(Type as Trait in module)` only there could *in theory* also work, but this would heavily depend on the macro's implementation details. See also [should-it-be-an-error-to-specify-an-implementation-environment-in-places-where-its-guaranteed-to-be-unused]. - -Unlike with external newtypes, there are no potential conflicts beyond overlapping imports and definitions in the same scope. These conflicts can *always* be resolved both without editing code elsewhere and without adding an additional implementation: - -- either by narrowing a local blanket implementation, -- by narrowing a blanket implementation import to a subset of the external implementation, -- or at worst by moving a generic implementation into a submodule and importing it for discrete types. - -### Error handling and conversions -[error-handling-and-conversions]: #error-handling-and-conversions - -When implementing services, it's a common pattern to combine a framework that dictates function signatures with one or more unrelated middlewares that have their own return and error types. The example below is a very abridged example of this. - -Note that in either version, the glue code may be project-specific. Glue code is *very slightly* more concise when implemented with scoped `impl Trait for Type`, as intermediary `struct` definitions and the resulting field access can be avoided. - -#### Current pattern - -```rust -// crate `service` - -use framework::{Error, Returned}; -use middleware_a::{fallible_a, Error as ErrorA}; -use middleware_b::{fallible_b, Error as ErrorB}; - -use framework_middleware_a_glue::{IntoReturnedExt as _, NewErrorA}; -use framework_middleware_b_glue::{IntoReturnedExt as _, NewErrorB}; - -pub fn a() -> Result { - // A `try` block should work eventually, but it may be not much less verbose. - Ok((|| -> Result<_, NewErrorA> { - fallible_a()?; - Ok(fallible_a()?) - })()? - .into_returned()) -} - -pub fn b() -> Result { - // The same as above. - Ok((|| -> Result<_, NewErrorB> { - fallible_b()?; - Ok(fallible_b()?) - })()? - .into_returned()) -} - -pub fn mixed(condition: bool) -> Result { - // Neither 'NewError' type provided by third-party crates can be used directly here. - Ok((move || -> Result<_, NewError> { - Ok(if condition { - fallible_b()?; - fallible_a()?.into_returned() - } else { - fallible_a()?; - fallible_b()?.into_returned() - }) - })()?) -} - -// Custom glue to connect all three errors: -struct NewError(Error); -impl From for Error { - fn from(value: NewError) -> Self { - value.0 - } -} -impl From for NewError { - fn from(value: ErrorA) -> Self { - let intermediate: NewErrorA = value.into(); - Self(intermediate.into()) - } -} -impl From for NewError { - fn from(value: ErrorB) -> Self { - let intermediate: NewErrorB = value.into(); - Self(intermediate.into()) - } -} -``` - -```rust -use service::{a, b, mixed}; - -fn main() { - framework::setup() - .add_route("a", a) - .add_route("b", b) - .add_route("mixed", mixed) - .build() - .run(); -} -``` - -#### With scoped `impl Trait for Type` - -```rust -// crate `service` - -// More concise, since middleware errors are used only once in imports. -use framework::{Error, Returned}; -use middleware_a::fallible_a; -use middleware_b::fallible_b; - -// Note: It is often better to import `impl Into` here over `impl From`, -// since middleware types often don't appear in public signatures. -// -// If the target type of the import must appear as type parameter in a public signature, -// a module that is wildcard-imported into each function body can be used instead, -// which would amount to 6 additional and 2 modified lines here. -// -// This RFC includes a warning for unintentionally exposed scoped implementations. -use framework_middleware_a_glue::{ - impl Into for middleware_a::Returned, - impl Into for middleware_a::Error, -}; -use framework_middleware_b_glue::{ - impl Into for middleware_b::Returned, - impl Into for middleware_b::Error, -}; - -pub fn a() -> Result { - // It just works. - fallible_a()?; - Ok(fallible_a()?.into()) -} - -pub fn b() -> Result { - // Here too. - fallible_b()?; - Ok(fallible_b()?.into()) -} - -pub fn mixed(condition: bool) -> Result { - // This too just works, as conversions bind separately. - Ok(if condition { - fallible_b()?; - fallible_a()?.into() - } else { - fallible_a()?; - fallible_b()?.into() - }) -} - -// No custom glue is necessary at all. -``` - -```rust -// Unchanged. No change in the API of `service`, either. - -use service::{a, b, mixed}; - -fn main() { - framework::setup() - .add_route("a", a) - .add_route("b", b) - .add_route("mixed", mixed) - .build() - .run(); -} -``` - -Note that to export *discrete* scoped `impl Into` in addition to their scoped `impl From`, the glue crates can use the following pattern, which discretises the global implementation and as such binds to each scoped `impl From` in the respective exported scoped `impl Into`: - -```rust -pub use ::{ - impl Into for middleware_a::Returned, - impl Into for middleware_a::Error, -}; -``` - -## Preserve coherence - -### Cross-crate stability - -With this RFC, scopes are a 'mini version' of the environment that global implementations exist in. As this environment is sealed within one scope, and not composed from multiple crates that may update independently, the *orphan rule* is not necessary. - -*All other* coherence rules and (for exported implementations) rules for what is and is not a breaking change apply *within each scope exactly like for global implementations*. In particular: - -- Blanket implementations like - - ```rust - // (Does not compile!) - - use std::fmt::{Debug, LowerHex, Pointer}; - mod debug_by_lower_hex; - - use debug_by_lower_hex::{impl Debug for T}; // <-- - - use impl Debug for T { // <-- - // ... - } - ``` - - still conflict regardless of actual implementations of `LowerHex` and `Pointer` because they may overlap later and - -- because scoped implementation are *explicitly subset* where they are imported, *it is not a breaking change to widen an exported scoped implementation*. - - (This is part of the reason why scoped `impl Trait for Type`s are anonymous; names would make these imports more verbose rather than shorter, since the subsetting still needs to happen in every case.) - -### Logical consistency -[logical-consistency]: #logical-consistency - -Binding external top-level implementations to types is equivalent to using their public API in different ways, so no instance-associated consistency is expected here. Rather, values that are used in the same scope behave consistently with regard to that scope's visible implementations. - -#### of generic collections -[of-generic-collections]: #of-generic-collections - -Generics are trickier, as their instances often do expect trait implementations on generic type parameters that are consistent between uses but not necessarily declared as bounded on the struct definition itself. - -This problem is solved by making the `impl`s available to each type parameter part of the the type identity of the discretised host generic, including a difference in `TypeId` there as with existing monomorphisation. - -(See [type-parameters-capture-their-implementation-environment] and [type-identity-of-generic-types] in the [reference-level-explanation] above for more detailed information.) - -Here is an example of how captured *implementation environments* safely flow across module boundaries, often seamlessly due to type inference: - -```rust -pub mod a { - // ⓐ == ◯ - - use std::collections::HashSet; - - #[derive(PartialEq, Eq)] - pub struct A; - - pub type HashSetA = HashSet; - pub fn aliased(_: HashSetA) {} - pub fn discrete(_: HashSet) {} - pub fn generic(_: HashSet) {} -} - -pub mod b { - // ⓑ - - use std::{ - collections::HashSet, - hash::{Hash, Hasher}, - }; - - #[derive(PartialEq, Eq)] - pub struct B; - use impl Hash for B { - fn hash(&self, _state: &mut H) {} - } - - pub type HashSetB = HashSet; // ⚠ - pub fn aliased(_: HashSetB) {} - pub fn discrete(_: HashSet) {} // ⚠ - pub fn generic(_: HashSet) {} -} - -pub mod c { - // ⓒ == ◯ - - use std::collections::HashSet; - - #[derive(PartialEq, Eq, Hash)] - pub struct C; - - pub type HashSetC = HashSet; - pub fn aliased(_: HashSetC) {} - pub fn discrete(_: HashSet) {} - pub fn generic(_: HashSet) {} -} - -pub mod d { - // ⓓ - - use std::{ - collections::HashSet, - hash::{Hash, Hasher}, - iter::once, - }; - - use super::{ - a::{self, A}, - b::{self, B}, - c::{self, C}, - }; - - use impl Hash for A { - fn hash(&self, _state: &mut H) {} - } - use impl Hash for B { - fn hash(&self, _state: &mut H) {} - } - use impl Hash for C { - fn hash(&self, _state: &mut H) {} - } - - fn call_functions() { - a::aliased(HashSet::new()); // ⓐ == ◯ - a::discrete(HashSet::new()); // ⓐ == ◯ - a::generic(HashSet::from_iter(once(A))); // ⊙ == ⓓ - - b::aliased(HashSet::from_iter(once(B))); // ⓑ - b::discrete(HashSet::from_iter(once(B))); // ⓑ - b::generic(HashSet::from_iter(once(B))); // ⊙ == ⓓ - - c::aliased(HashSet::from_iter(once(C))); // ⓒ == ◯ - c::discrete(HashSet::from_iter(once(C))); // ⓒ == ◯ - c::generic(HashSet::from_iter(once(C))); // ⊙ == ⓓ - } -} - -``` - -Note that the lines annotated with `// ⚠` produce a warning due to the lower visibility of the scoped implementation in `b`. - -Circles denote *implementation environments*: - -| | | -|-|-| -| ◯ | indistinct from global | -| ⓐ, ⓑ, ⓒ, ⓓ | respectively as in module `a`, `b`, `c`, `d` | -| ⊙ | caller-side | - -The calls infer discrete `HashSet`s with different `Hash` implementations as follows: - -| call in `call_functions` | `impl Hash` in | captured in/at | notes | -|-|-|-|-| -| `a::aliased` | - | `type` alias | The implementation cannot be 'inserted' into an already-specified type parameter, even if it is missing. | -| `a::discrete` | - | `fn` signature | See `a::aliased`. | -| `a::generic` | `d` | `once` call | | -| `b::aliased` | `b` | `type` alias | | -| `b::discrete` | `b` | `fn` signature | | -| `b::generic` | `d` | `once` call | `b`'s narrow implementation cannot bind to the opaque `T`. | -| `c::aliased` | `::` | `type` alias | Since the global implementation is visible in `c`. | -| `c::discrete` | `::` | `fn` signature | See `c::aliased`. -| `c::generic` | `d` | `once` call | The narrow global implementation cannot bind to the opaque `T`. | - -#### of type-erased collections -[of-type-erased-collections]: #of-type-erased-collections - -Type-erased collections such as the `ErasedHashSet` shown in [typeid-of-generic-type-parameters-opaque-types] require slightly looser behaviour, as they are expected to mix instances between environments where only irrelevant implementations differ (since they don't prevent this mixing statically like `std::collections::HashSet`, as their generic type parameters are transient on their methods). - -It is for this reason that the `TypeId` of generic type parameters disregards bounds-irrelevant implementations. - -The example is similar to the previous one, but `aliased` has been removed since it continues to behave the same as `discrete`. A new set of functions `bounded` is added: - -```rust -#![allow(unused_must_use)] // For the `TypeId::…` lines. - -trait Trait {} - -pub mod a { - // ⓐ == ◯ - - use std::{collections::HashSet, hash::Hash}; - - #[derive(PartialEq, Eq)] - pub struct A; - - pub fn discrete(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } - pub fn generic(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } - pub fn bounded(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } -} - -pub mod b { - // ⓑ - - use std::{ - collections::HashSet, - hash::{Hash, Hasher}, - }; - - use super::Trait; - - #[derive(PartialEq, Eq)] - pub struct B; - use impl Hash for B { - fn hash(&self, _state: &mut H) {} - } - use impl Trait for B {} - - pub fn discrete(_: HashSet) { // ⚠⚠ - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } - pub fn generic(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } - pub fn bounded(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } -} - -pub mod c { - // ⓒ == ◯ - - use std::{collections::HashSet, hash::Hash}; - - use super::Trait; - - #[derive(PartialEq, Eq, Hash)] - pub struct C; - impl Trait for C {} - - pub fn discrete(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } - pub fn generic(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } - pub fn bounded(_: HashSet) { - TypeId::of::>(); // ❶ - TypeId::of::(); // ❷ - } -} - -pub mod d { - // ⓓ - - use std::{ - collections::HashSet, - hash::{Hash, Hasher}, - iter::once, - }; - - use super::{ - a::{self, A}, - b::{self, B}, - c::{self, C}, - Trait, - }; - - use impl Hash for A { - fn hash(&self, _state: &mut H) {} - } - use impl Hash for B { - fn hash(&self, _state: &mut H) {} - } - use impl Hash for C { - fn hash(&self, _state: &mut H) {} - } - - use impl Trait for A {} - use impl Trait for B {} - use impl Trait for C {} - - fn call_functions() { - a::discrete(HashSet::new()); // ⓐ == ◯ - a::generic(HashSet::from_iter(once(A))); // ⊙ == ⓓ - a::bounded(HashSet::from_iter(once(A))); // ⊙ == ⓓ - - b::discrete(HashSet::from_iter(once(B))); // ⓑ - b::generic(HashSet::from_iter(once(B))); // ⊙ == ⓓ - b::bounded(HashSet::from_iter(once(B))); // ⊙ == ⓓ - - c::discrete(HashSet::from_iter(once(C))); // ⓒ == ◯ - c::generic(HashSet::from_iter(once(C))); // ⊙ == ⓓ - c::bounded(HashSet::from_iter(once(C))); // ⊙ == ⓓ - } -} - -``` - -`// ⚠` and non-digit circles have the same meanings as above. - -The following table describes how the types are observed at runtime in the lines marked with ❶ and ❷. Types are denoted as if seen from the global *implementation environment* with differences written inline, which should resemble how they are formatted in compiler messages and tooling. - -| within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | -|-|-|-| -| `a::discrete` | `HashSet
` | `A` | -| `a::generic` | `HashSet` | `A` | -| `a::bounded` | `HashSet` | `A` ∘ `Hash in d` | -| `b::discrete` | `HashSet` | `B` | -| `b::generic` | `HashSet` | `B` | -| `b::bounded` | `HashSet` | `B` ∘ `Hash in d` | -| `c::discrete` | `HashSet` | `C` | -| `c::generic` | `HashSet` | `C` | -| `c::bounded` | `HashSet` | `C` ∘ `Hash in d` | - -The combination ∘ is not directly expressible in `TypeId::of::<>` calls (as even a direct top-level annotation would be ignored without bounds). Rather, it represents an observation like this: - -```rust -{ - use std::{any::TypeId, hash::Hash}; - - use a::A; - use d::{impl Hash for A}; - - fn observe() { - TypeId::of::(); // '`A` ∘ `Hash in d`' - } - - observe::(); -} -``` - -##### with multiple erased type parameters - -By replacing the lines - -```rust -TypeId::of::>(); // ❶ -TypeId::of::(); // ❷ -``` - -with - -```rust -TypeId::of::>(); // ❶ -TypeId::of::<(T)>(); // ❷ -``` - -(and analogous inside the discrete functions), the `TypeId` table above changes as follows: - -| within function
(called by `call_functions`) | ❶ (collection) | ❷ (item) | -|-|-|-| -| `a::discrete` | `HashSet<(A,)>` | `(A,)` | -| `a::generic` | `HashSet<(A as Hash in d + Trait in d,)>` | `(A,)` | -| `a::bounded` | `HashSet<(A as Hash in d + Trait in d,)>` | `(A` ∘ `Hash in d,)` | -| `b::discrete` | `HashSet<(B as Hash in `***`b`***` + Trait in`***` b`***`,)>` | `(B,)` | -| `b::generic` | `HashSet<(B as Hash in d + Trait in d,)>` | `(B,)` | -| `b::bounded` | `HashSet<(B as Hash in d + Trait in d,)>` | `(B` ∘ `Hash in d,)` | -| `c::discrete` | `HashSet<(C,)>` | `(C,)` | -| `c::generic` | `HashSet<(C as Hash in d + Trait in d,)>` | `(C,)` | -| `c::bounded` | `HashSet<(C as Hash in d + Trait in d,)>` | `(C` ∘ `Hash in d,)` | - -As you can see, the type identity of the tuples appears distinct when contributing to an implementation-aware generic's type identity but (along with the `TypeId`) remains appropriately fuzzy when used alone. - -This scales up to any number of type parameters used in implementation-invariant generics, which means an efficient `ErasedHashMap` can be constructed by keying storage on the `TypeId::of::<(K, V)>()` where `K: Hash + Eq` and `V` are the generic type parameters of its functions. - -### Logical stability - -- Non-breaking changes to external crates cannot change the meaning of the program. -- Breaking changes should result in compile-time errors rather than a behaviour change. - -This is another consequence of subsetting rather than named-model imports, as narrowing a scoped implementation can only make the `use`-declaration fail to compile, rather than changing which implementations are shadowed. - -Similarly, types of generics with different captured *implementation environments* are strictly distinct from each other, so that assigning them inconsistently does not compile. This is weighed somewhat against ease of refactoring, so in cases where a type parameter is inferred and the host is used in isolation, which are assumed to not care about implementation details like that, the code will continue to align with the definition instead of breaking. - -## Encourage readable code - -This RFC aims to further decrease the mental workload required for code review, by standardising glue code APIs to some degree and by clarifying their use in other modules. - -It also aims to create an import grammar that can be understood more intuitively than external newtypes when first encountered, which should improve the accessibility of Rust code somewhat. - -### Clear imports - -As scoped implementations bind implicitly like global ones, two aspects must be immediately clear at a glace: - -- *Which trait* is implemented? -- *Which type* is targeted? - -Restating this information in the `use`-declaration means that it is available without leaving the current file, in plaintext without any tooling assists. This is another improvement compared to newtypes or external definitions, where the relationship may not be immediately clear depending on their names. - -Spelling scoped implementation imports out with keywords rather than just symbols makes their purpose easy to guess for someone unfamiliar with the scoped `impl Trait for Type` feature, possibly even for most English-speaking developers unfamiliar with Rust. - -This is also true for blanket imports with `where`, which remain easy to parse visually due to the surrounding braces: - -```rust -use std::fmt::{Debug, Display, Pointer}; - -// `Debug` and `Display` all `Pointer`-likes as addresses. -// The `Display` import is different only to show the long form -// with `where`. It could be written like the `Debug` import. -use cross_formatting::by_pointer::{ - impl Debug for T, - {impl Display for T where T: Pointer}, -}; - -println!("{:?}", &()); // For example: 0x7ff75584c360 -println!("{}", &()); // For example: 0x7ff75584c360 -``` - -### Familiar grammar - -The grammar for scoped implementations differs from that for global implementations by only a prefixed `use` and an optional visibility. As such, it should be easy to parse for developers not yet familiar with scoped implementations specifically. - -The clear prefix (starting with at least two keywords instead of one) should still be enough to distinguish scoped implementations at a glance from global ones. - -The header (the part before the `{}` block) of global implementations is reused unchanged for scoped implementation imports, including all bounds specifications, so there is very little grammar to remember additionally in order to `use` scoped `impl Trait for Type`s. - -In each case, the meaning of identical grammar elements lines up exactly - only their context and perspective vary due to immediately surrounding tokens. - -(See [grammar-changes] for details.) - -### Stop tokens for humans - -When looking for the scoped implementation affecting a certain type, strict shadowing ensures that it is always the closest matching one that is effective. - -As such, readers can stop scanning once they encounter a match (or module boundary, whether surrounding or nested), instead of checking the entire file's length for another implementation that may be present in the outermost scope. - -Aside from *implementation environments* captured *inside* generics, scoped implementations cannot influence the behaviour of another file without being mentioned explicitly. - -## Unblock ecosystem evolution -[unblock-ecosystem-evolution]: #unblock-ecosystem-evolution - -As any number of scoped glue implementations can be applied directly to application code without additional compatibility shims, it becomes far easier to upgrade individual dependencies to their next major version. Compatibility with multiple versions of crates like Serde and `bevy_reflect` can be provided in parallel through officially supported glue crates. - -Additionally, scoped implementations are actually *more* robust than newtypes regarding certain breaking changes: - -A newtype that implements multiple traits could eventually gain a global blanket implementation of one of its traits for types that implement another of its traits, causing a conflict during the upgrade. - -In the presence of an overlapping scoped `impl Trait for Type`, the new blanket implementation is just unambiguously shadowed where it would conflict, which means no change is necessary to preserve the code's behaviour. A [global-trait-implementation-available] warning is still shown where applicable to alert maintainers of new options they have. - -(See also [glue-crate-suggestions] for possible future tooling related to this pattern.) - -### Side-effect: Parallelise build plans (somewhat) more - -Serde often takes a long time to build even without its macros. If another complex crate depends on it just to support its traits, this can significantly stretch the overall build time. - -If glue code for 'overlay' features like Serde traits is provided in a separate crate, that incidentally helps to reduce that effect somewhat: - -Since the glue forms a second dependency chain that normally only rejoins in application code, the often heavier core functionality of libraries can build in parallel to Serde and/or earlier glue. Since the glue chain is likely to be less code, it matters less for overall build time whether it has to wait for one or two large crates first. - -## Provide opportunities for rich tooling - -### Discovery of implementations - -As scoped implementations clearly declare the link between the trait and type(s) they connect, tools like rust-analyzer are able to index them and suggest imports where needed, just like for global traits. - -(At least when importing from another crate, the suggested import should be for a specific type or generic, even if the export in question is a blanket implementation. Other generics of the export can usually be preserved, though.) - -### Discovery of the feature itself - -In some cases (where a trait implementations cannot be found at all), tools can suggest creating a scoped implementation, unless adding it in that place would capture it as part of the *implementation environment* of a type parameter specified in an item definition visible outside the current crate. - -That said, it would be great if rust-analyzer could detect and suggest/enable feature-gated global implementations to some extent, with higher priority than creating a new scoped implementation. - -### Rich and familiar warnings and error messages - -Since scoped implementations work much like global ones, many of the existing errors and warnings can be reused with at most small changes. This means that, as developers become more familiar with either category of trait-related issues, they learn how to fix them for global and scoped implementations at the same time. - -The implementation of the errors and warnings in the compiler can also benefit from the existing work done for global implementations, or in some cases outright apply the same warning to both scoped and global implementations. - -Since available-but-not-imported scoped implementations are easily discoverable by the compiler, they can be used to improve existing errors like *error[E0277]: the trait bound `[…]` is not satisfied* and *error[E0599]: no method named `[…]` found for struct `[…]` in the current scope* with quick-fix suggestions also for using an existing scoped implementation in at least some cases. - -### Maintenance warnings for ecosystem evolution - -Scoped `impl Trait for Type`s lead to better maintenance lints: - -If a covering global implementation later becomes available through a dependency, a warning can be shown on the local trait implementation for review. (See [global-trait-implementation-available].) - -In the long run, this can lead to less near-duplicated functionality in the dependency graph, which can lead to smaller executable sizes. - -### Automatic documentation - -Scoped implementations can be documented and appear as separate item category in rustdoc-generated pages. - -Rustdoc should be able to detect and annotate captured scoped implementations in public signatures automatically. This, in addition to warnings, could be another tool to help avoid accidental exposure of scoped implementations. - -Implementation origin and documentation could be surfaced by rust-analyzer in relevant places. - -## Why specific [implementation-invariant-generics]? -[why-specific-implementation-invariant-generics]: #why-specific-implementation-invariant-generics - -This is a *not entirely clean* ergonomics/stability trade-off, as well as a clean resolution path for [behaviour-changewarning-typeid-of-implementation-aware-generic-discretised-using-generic-type-parameters]. - -> It is also the roughest part of this proposal, in my eyes. If you have a better way of dealing with the aware/invariant distinction, please do suggest it! - -The main issue is that generics in the Rust ecosystem do not declare which trait implementations on their type parameters need to be consistent during their instances' lifetime, if any, and that traits like `PartialOrd` that do provide logical consistency guarantees over time are not marked as such in a compiler-readable way. - -Ignoring this and not having distinction of [implementation-aware-generics]' discretised variants would badly break logical consistency of generic collections like `BTreeSet`, which relies on `Ord`-consistency to function. - -On the other hand, certain types (e.g. references and (smart) pointers) that often wrap values in transit between modules *really* don't care about implementation consistency on these types. If these were distinct depending on available implementations on their values, it would create *considerable* friction while defining public APIs in the same scope as `struct` or `enum` definitions that require scoped implementations for `derive`s. - -Drawing a line manually here is an attempt to un-break this *by default* for the most common cases while maintaining full compatibility with existing code and keeping awareness of scoped `impl Trait for Type` entirely optional for writing correct and user-friendly APIs. - -As a concrete example, this ensures that `Box>>` is automatically interchangeable even if spelled out in the presence of scoped [error-handling-and-conversions] affecting `Error`, but that `BinaryHeap>` and `BinaryHeap>` don't mix. - -Functions pointers and closure trait( object)s should probably be fairly easy to pass around, with their internally-used bindings being an implementation detail. Fortunately, the Rust ecosystem already uses more specific traits for most configuration for better logical safety, so it's likely not too messy to make these implementation-invariant. - -Traits and trait objects cannot be implementation invariant by default (including for their associated types!) because it's already possible to define `OrderedExtend` and `OrderedIterator` traits with logical consistency requirement on `Ord` between them. - -## Efficient compilation -[efficient-compilation]: #efficient-compilation - -In theory, it should be possible to unify many instances of generic functions that may be polymorphic under this proposal cheaply before code generation. (Very few previously discrete implementations become polymorphic under scoped `impl Trait for Type`.) - -This is mainly an effect of [layout-compatibility] and [binding-choice-by-implementations-bounds], so that, where the differences are only bounds-irrelevant, generated implementations are easily identical in almost all cases. The exception here are [implementation-aware-generics]' `TypeId`s (see also [typeid-of-generic-type-parameters-opaque-types]). Checking for this exception should be cheap if done alongside checks for e.g. function non-constness if possible, which propagates identically from callee to caller. - -Given equal usage, compiling code that uses scoped implementations could as such be slightly more efficient compared to use of newtypes and the resulting text size may be slightly smaller in some cases where newtype implementations are inlined differently. - -The compiler should treat implementations of the same empty trait on the same type as identical early on, so that no code generation is unnecessarily duplicated. However, unrelated empty-trait implementations must still result in distinct `TypeId`s when captured in a generic type parameter and observed there by a `where`-clause or through nesting in an implementation-aware generic. - -## Alternatives - -### Named implementations - -Use of named implementations is not as obvious as stating the origin-trait-type triple in close proximity, so code that uses named implementations tends to be harder to read. - -Like named implementations, the scope-identified implementations proposed here can be written concisely in generic parameter lists (as `Type as Trait in module`), limiting the code-writing convenience advantage of named implementations. Where needed, the module name can be chosen to describe specific function, e.g. exporting reverse-ordering `Ord` and `PartialOrd` implementations from a module called `reverse`. - -If named implementations can't be brought into scope (see Genus in [lightweight-flexible-object-oriented-generics]), that limits their practical application to where they can be captured in [implementation-aware-generics]. Bringing named implementations into scope would be more verbose than for module-trait-type-identified as subsetting would still be required to preserve useful room for library crate evolution. - -### Weakening coherence rules - -There is likely still some leeway here before the Rust ecosystem becomes brittle, but at least the orphan rule specifically is essential for ensuring that global trait implementations do not lead to hard ecosystem splits due to strictly incompatible framework crates. - -If *other* coherence rules are relaxed, scoped `impl Trait for Type` also benefits immediately since it is subject to all of them. - -### Crate-private implementations as distinct feature - -There is a previous [RFC: Hidden trait implementations] from 2018-2021 where the result was general acceptance, but postponement for logistical reasons. - -Scoped `impl Trait for Type` together with its warnings [scoped-implementation-is-less-visible-than-itemfield-it-is-captured-in] and [imported-implementation-is-less-visible-than-itemfield-it-is-captured-in] can mostly cover this use-case, though with slightly more boilerplate (`use`-declarations) and not as-strict a limitation. - -[RFC: Hidden trait implementations]: https://github.com/rust-lang/rfcs/pull/2529 - -### Required-explicit binding of scoped implementations inside generics - -This could avoid the distinction between [implementation-aware-generics] and [implementation-invariant-generics] to some extent, at the cost of likely overall worse ergonomics when working with scoped implementations. - -It's also likely to make `derive`-compatibility of scoped implementations inconsistent, because some macros may require explicit binding on field types while others would not. - -# Prior art -[prior-art]: #prior-art - -## Lightweight, Flexible Object-Oriented Generics -[lightweight-flexible-object-oriented-generics]: #lightweight-flexible-object-oriented-generics - -Yizhou Zhang, Matthew Loring, Guido Salvaneschi, Barbara Liskov and Andrew C. Myers, May 2015 - - - -There are some parallels between Genus's models and the scoped `impl Trait for Type`s proposed in this RFC, but for the most part they are quite distinct due to Rust's existing features: - -| Genus | scoped `impl Trait for Type` | reasoning | -|---|---|---| -| Proper-named models | Anonymous scoped implementations | Use of existing coherence constraints for validation. Forced subsetting in `use`-declarations improves stability. The `impl Trait for Type` syntax stands out in `use`-declarations and is intuitively readable. | -| Explicit bindings of non-default models | Mainly implicit bindings, but explicit bindings of scoped *and global* implementations are possible in some places. | Focus on simplicity and ergonomics of the most common use-case. More natural use with future specialisation. | -| Comparing containers inherently constrain type parameters in their type definition. | Available scoped implementations for discretised type parameters become part of the type identity. |

`, `NonNull`, `Box`, `Rc`, `Arc`, `Weak`, `Option`, `Result`\*\*. -Implementation-invariant generics never capture implementation environments on their own. Instead, their effective implementation environments follow that of their host, acting as if they were captured in the same scope. +Implementation-invariant generics never capture *implementation environments* on their own. Instead, their effective *implementation environments* follow that of their host, acting as if they were captured in the same scope. The type identity of implementation-invariant generics seen on their own does not depend on the implementation environment. -\* superficially: The underlying instance may well use a captured implementation, but this isn't surfaced in signatures. For example, a closure defined where `usize: PartialOrd in reverse + Ord in reverse` is just `FnOnce(usize)` but will use `usize: PartialOrd in reverse + Ord in reverse` privately when called. +\* superficially: The underlying instance may well use a captured implementation internally, but this isn't surfaced in signatures. For example, a closure defined where `usize: PartialOrd in reverse + Ord in reverse` is just `FnOnce(usize)` but will use `usize: PartialOrd in reverse + Ord in reverse` privately when called. \*\* but see [which-structs-should-be-implementation-invariant]. From 483f22a34b5adaecef8715a09f1cd630320ea051 Mon Sep 17 00:00:00 2001 From: Tamme Schichler Date: Thu, 7 Mar 2024 08:47:26 +0100 Subject: [PATCH 10/27] Pre-RFC v5 - Added section "(Pending changes to this draft)". - Revised "`TypeId` of generic type parameters' opaque types" as tuples are implementation-invariant. - Replaced section "Contextual monomorphisation of generic implementations and generic functions" with sections "Binding choice by implementations' bounds" and "Binding and generics". - Added sections "Marking a generic as implementation-invariant is a breaking change" and "Efficient compilation". - Renamed future possibilities section "Scoped bounds as contextual alternative to sealed traits" to "Sealed trait bounds". - Added future possibilities section "Reusable limited-access APIs". - a range of smaller adjustments to wording and formatting --- text/0000-scoped-impl-trait-for-type.md | 382 +++++++++++++++++++----- 1 file changed, 303 insertions(+), 79 deletions(-) diff --git a/text/0000-scoped-impl-trait-for-type.md b/text/0000-scoped-impl-trait-for-type.md index c27d1db98bf..9950c71a3dd 100644 --- a/text/0000-scoped-impl-trait-for-type.md +++ b/text/0000-scoped-impl-trait-for-type.md @@ -26,7 +26,7 @@ While orphan rules regarding trait implementations are necessary to allow crates For example, while many crates support `serde::{Deserialize, Serialize}` directly, implementations of the similarly-derived `bevy_reflect::{FromReflect, Reflect}` traits are less common. Sometimes, a `Debug`, `Clone` or (maybe only contextually sensible) `Default` implementation for a field is missing to derive those traits. While crates like Serde often do provide ways to supply custom implementations for fields, this usually has to be restated on each such field. Additionally, the syntax for doing so tends to differ between crates. -Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. +Wrapper types, commonly used as workaround, add clutter to call sites or field types, and introduce mental overhead for developers as they have to manage distinct types without associated state transitions in order to work around the issues laid out in this section. They also require a distinct implementation for each combination of traits and lack discoverability through tools like rust-analyzer. Another pain point are sometimes missing `Into<>`-conversions when propagating errors with `?`, even though one external residual (payload) type may (sometimes *contextually*) be cleanly convertible into another. As-is, this usually requires a custom intermediary type, or explicit conversion using `.map_err(|e| …)` (or an equivalent function/extension trait). If an appropriate `From<>`-conversion can be provided *in scope*, then just `?` can be used. @@ -34,6 +34,14 @@ This RFC aims to address these pain points by creating a new path of least resis For realistic examples of the difference this makes, please check the [rationale-and-alternatives] section. +# (Pending changes to this draft) + +It should be possible to specify differences in the implementation environment directly where it is captured, e.g. as `BTreeSet`, without bringing these implementations into scope. + +As this requires additional grammar changes and overall more adjustments to this document, I plan to tackle that a bit later. + +For now, see [explicit-binding] in *Future possibilities* below for more, but less rigorous, text about one possibility. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -167,8 +175,6 @@ Since scoped implementations allow crates to reusably implement external traits Filename: fruit-comparer/src/lib.rs ```rust -#![no_std] - use apples::Apple; use oranges::Orange; @@ -267,7 +273,7 @@ The core Rust language grammar is extended as follows: [E0178]: https://doc.rust-lang.org/error_codes/E0178.html - (Allowing unbraced imports like `use some_crate::impl Trait for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy quite badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added for convenience, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced.) + > Allowing unbraced imports like `use some_crate::impl Trait for Type where A: Debug, B: Debug;` would break the source code's visual hierarchy quite badly, so I won't suggest it here, but it is possible without ambiguity too. If that is added for convenience, then I'm strongly in favour of rustfmt bracing the *TraitCoverage* by default and rust-analyzer suggesting it only braced. Here, *TraitCoverage* imports the specified scoped `impl Trait for Type` for binding and conflict checks as if defined in the scope containing the `use`-declaration. The resulting visibility is taken from *UseDeclaration*, like with *SimplePath*-imported items. @@ -489,16 +495,22 @@ struct Type; trait Trait {} impl Trait for Type {} +#[derive(Default)] +struct Generic(T); + mod nested { - use super::{Trait, Type}; + use super::{Trait, Type, Generic}; use impl Trait for Type {}; - pub type Distinct = (Type,); + pub type B = Generic; } -use nested::Distinct; + +// `A` and `B` are distinct due to different captured implementation environments. +type A = Generic; +use nested::B; fn no_bound(_: (T,), _: (U,)) { assert_eq!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::<(T,)>(), TypeId::of::<(U,)>()); + assert_ne!(TypeId::of::>(), TypeId::of::>()); assert_eq!(TypeId::of::(), TypeId::of::()); assert_eq!(TypeId::of::(), TypeId::of::()); @@ -506,17 +518,16 @@ fn no_bound(_: (T,), _: (U,)) { fn yes_bound(_: (T,), _: (U,)) { assert_ne!(TypeId::of::(), TypeId::of::()); - assert_ne!(TypeId::of::<(T,)>(), TypeId::of::<(U,)>()); + assert_ne!(TypeId::of::>(), TypeId::of::>()); assert_eq!(TypeId::of::(), TypeId::of::()); assert_ne!(TypeId::of::(), TypeId::of::()); } fn main() { - no_bound((Type,), Distinct::default()); - yes_bound((Type,), Distinct::default()); + no_bound(A::default(), B::default()); + yes_bound(A::default(), B::default()); } - ``` In particular: @@ -649,80 +660,203 @@ This is intentional, as it makes the following code trivial to reason about: (The main importance here is to not allow non-obvious dependencies of imports. Implementations can still access associated items of a *specific* other implementation by bringing it into a nested scope or binding to its associated items elsewhere. See also [independent-trait-implementations-on-discrete-types-may-still-call-shadowed-implementations].) -## Contextual monomorphisation of generic implementations and generic functions -[contextual-monomorphisation-of-generic-implementations-and-generic-functions]: #contextual-monomorphisation-of-generic-implementations-and-generic-functions +## Binding choice by implementations' bounds +[binding-choice-by-implementations-bounds]: #binding-choice-by-implementations-bounds + +Implementations bind to other implementations as follows: + +| `where`-clause on `impl`? | binding-site of used trait | monomorphised by used trait? | +|-|-|-| +| Yes. | Bound at each binding-site of `impl`. | Yes, like-with or as-part-of type parameter distinction. | +| No. | Bound once at definition-site of `impl`. | No. | + +A convenient way to think about this is that *`impl`-implementations are blanket implementations over `Self` in different implementation environments*. -Traits of generic type parameters (and of their associated types, recursively) are resolved to implementations according to the binding site of the generically implemented trait or generic function, which is in the code consuming that implementation. +Note that `Self`-bounds on associated functions do **not** cause additional monomorphic variants to be emitted, as these continue to only filter the surrounding implementation. -This means generic implementations are monomorphised in different ways for the same type(s) when there is a difference in relevant scoped implementations for these types. +Consider the following code with attention to the where clauses: ```rust struct Type; -struct Type2(T); -trait Trait1 { - fn trait1(); +// ❶ + +trait Trait { fn function(); } +impl Trait for Type { fn function() { println!("global"); } } + +trait Monomorphic { fn monomorphic(); } +impl Monomorphic for Type { + fn monomorphic() { Type::function() } } -impl Trait1 for Type { - fn trait1() { - print!("global"); - } + +trait MonomorphicSubtrait: Trait { + fn monomorphic_subtrait() { Self::function(); } } +impl MonomorphicSubtrait for Type {} -trait Trait2 { - fn trait2(&self); +trait Bounded { fn bounded(); } +impl Bounded for Type where Type: Trait { + fn bounded() { Type::function(); } } -impl Trait2 for T { - fn trait2(&self) { - Self::trait1(); - } + +trait BoundedSubtrait: Trait { + fn bounded_subtrait() { Type::function(); } } +impl BoundedSubtrait for Type where Type: Trait {} -trait Trait3 { - fn trait3(&self); +trait FnBoundedMonomorphic { + fn where_trait() where Self: Trait { Self::function(); } + fn where_monomorphic_subtrait() where Self: MonomorphicSubtrait { Self::monomorphic_subtrait(); } } -impl Trait3 for Type2 -where - T: Trait2, -{ - fn trait3(&self) { - self.0.trait2(); - } +impl FnBoundedMonomorphic for Type {} + +trait NestedMonomorphic { fn nested_monomorphic(); } + +trait BoundedOnOther { fn bounded_on_other(); } +impl BoundedOnOther for () where Type: Trait { + fn bounded_on_other() { Type::function(); } } -Type2(Type).trait3(); // "global" +Type::function(); // "global" +Type::monomorphic(); // "global" +Type::monomorphic_subtrait(); // "global" +Type::bounded(); // "global" +Type::bounded_subtrait(); // "global" +Type::where_trait(); // "global" +Type::where_monomorphic_subtrait(); // "global" +Type::nested_monomorphic(); // "scoped" +()::bounded_on_other(); // "global" { - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped1"); + // ❷ + use impl Trait for Type { + fn function() { + println!("scoped"); } } - Type2(Type).trait3(); // "scoped1" - - { - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped2"); - } - } + // use impl FnBoundedMonomorphic for Type {} + // error: the trait bound `Type: MonomorphicSubtrait` is not satisfied - Type2(Type).trait3(); // "scoped2" - } + Type::function(); // "scoped" + Type::monomorphic(); // "global" + // Type::monomorphic_subtrait(); // error; shadowed by scoped implementation + Type::bounded(); // "scoped" + Type::bounded_subtrait(); // "scoped" + Type::where_trait(); // "global" + Type::where_monomorphic_subtrait(); // "global" + Type::nested_monomorphic(); // "scoped" + ()::bounded_on_other(); // "global" { - use impl Trait1 for Type { - fn trait1(&self) { - print!("scoped3"); - } + // ❸ + use impl MonomorphicSubtrait for Type {} + use impl FnBoundedMonomorphic for Type {} + + impl NestedMonomorphic for Type { + fn nested_monomorphic() { Type::function() } } - Type2(Type).trait3(); // "scoped3" + Type::function(); // "scoped" + Type::monomorphic(); // "global" + Type::monomorphic_subtrait(); // "scoped" + Type::bounded(); // "scoped" + Type::bounded_subtrait(); // "scoped" + Type::where_trait(); // "scoped" + Type::where_monomorphic_subtrait(); // "scoped" + Type::nested_monomorphic(); // "scoped" + ()::bounded_on_other(); // "global" } } ``` +The numbers ❶, ❷ and ❸ mark relevant item scopes. + +Generic item functions outside `impl` blocks bind and behave the same way as generic `impl`s with regard to scoped `impl Trait for Type`. + +### `Trait` / `::function` + +This is a plain monomorphic implementation with no dependencies. As there is a scoped implementation at ❷, that one is used in scopes ❷ and ❸. + +### `Monomorphic` / `::monomorphic` + +Another plain monomorphic implementations. + +As there is no bound, an implementation of `Trait` is bound locally in ❶ to resolve the `Type::function()`-call. + +This means that even though a different `use impl Trait for Type …` is applied in ❷, the global implementation remains in use when this `Monomorphic` implementation is called into from there and ❸. + +Note that the use of `Self` vs. `Type` in the non-default function body does not matter at all! + +### `MonomorphicSubtrait` / `::monomorphic_subtrait` + +Due to the supertrait, there is an implied bound `Self: Trait` *on the trait definition, but not on the implementation*. + +This means that the implementation remains monomorphic, and as such depends on the specific (global) implementation of `Trait` in scope at the `impl MonomorphicSubtrait …` in ❶. + +As this `Trait` implementation is shadowed in ❷, the `MonomorphicSubtrait` implementation is shadowed for consistency of calls to generics bounded on both traits. + +In ❸ there is a scoped implementation of `MonomorphicSubtrait`. As the default implementation is monomorphised for this implementation, it binds to the scoped implementation of `Trait` that is in scope here. + +### `Bounded` / `::bounded` + +The `Type: Trait` bound (can be written as `Self: Trait` – they are equivalent.) selects the `Bounded`-binding-site's `Type: Trait` implementation to be used, rather than the `impl Bounded for …`-site's. + +In ❶, this resolves to the global implementation as expected. + +For the scopes ❷ and ❸ together, `Bounded` gains one additional monomorphisation, as here another `Type: Trait` is in scope. + +### `BoundedSubtrait` / `::bounded_subtrait` + +As with `MonomorphicSubtrait`, the monomorphisation of `impl BoundedSubtrait for Type …` that is used in ❶ is shadowed in ❷. + +However, due to the `where Type: Trait` bound *on the implementation*, that implementation is polymorphic over `Trait for Type` implementations. This means a second monomorphisation is available in ❷ and its nested scope ❸. + +### `FnBoundedMonomorphic` + +`FnBoundedMonomorphic`'s implementations are monomorphic from the get-go just like `Monomorphic`'s. + +Due to the narrower bounds on functions, their availability can vary between receivers but always matches that of the global implementation environment: + +#### `::where_trait` + +Available everywhere since `Type: Trait` is in scope for both implementations of `FnBoundedMonomorphic`. + +In ❶, this resolves to the global implementation. + +In ❷, this *still* calls the global `::function()` implementation since the global `FnBoundedMonomorphic` implementation is *not* polymorphic over `Type: Trait`. + +In ❸, `FnBoundedMonomorphic` is monomorphically reimplemented for `Type`, which means it "picks up" the scoped `Type: Trait` implementation that's in scope there from ❷. + +#### `::where_monomorphic_subtrait` + +In ❶, this resolves to the global implementation. + +In ❷, this *still* calls the global `::monomorphic_subtrait()` implementation since the global `FnBoundedMonomorphic` implementation is *not* polymorphic over `Type: Trait`. + +Note that `FnBoundedMonomorphic` *cannot* be reimplemented in ❷ since the bound `Type: MonomorphicSubtrait` on its associated function isn't available in that scope, which would cause a difference in the availability of associated functions (which would cause a mismatch when casting to `dyn FnBoundedMonomorphic`). + +> It may be better to allow `use impl FnBoundedMonomorphic for Type {}` without `where_monomorphic_subtrait` in ❷ and disallow incompatible unsizing instead. I'm not sure about the best approach here. + +In ❸, `FnBoundedMonomorphic` is monomorphically reimplemented for `Type`, which means it "picks up" the scoped `Type: Trait` implementation that's in scope there from ❷. + +### `NestedMonomorphic` / `::nested_monomorphic` + +The global implementation of `NestedMonomorphic` in ❸ the binds to the scoped implementation of `Trait` on `Type` from ❷ internally. This allows outside code to call into that function indirectly without exposing the scoped implementation itself. + +### `BoundedOnOther` / `::bounded_on_other` + +As this discrete implementation's bound isn't over the `Self` type (and does not involved generics), it continues to act only as assertion and remains monomorphic. + +## Binding and generics + +`where`-clauses without generics or `Self` type, like `where (): Debug`, **do not** affect binding of implementations within an `impl` or `fn`, as the non-type-parameter-type `()` is unable to receive an implementation environment from the discretisation site. + +However, `where (): From` **does** take scoped implementations into account because the blanket `impl From for U where T: Into {}` is sensitive to `T: Into<()>` which is part of the implementation environment captured in `T`! + +This sensitivity even extends to scoped `use impl From for ()` at the discretisation site, as the inverse blanket implementation of `Into` creates a scoped implementation of `Into` wherever a scoped implementation of `From` exists. +This way, existing symmetries are fully preserved in all contexts. + ## Implicit shadowing of subtrait implementations Take this code for example: @@ -1119,7 +1253,7 @@ Instead of `TypeId::of::>()`, `TypeId::of::<(U, V, W)>()` can b ## Resolution on generic type parameters [resolution-on-generic-type-parameters]: #resolution-on-generic-type-parameters -Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `impl Trait for T`. +Scoped `impl Trait for Type`s (including `use`-declarations) can be applied to outer generic type parameters *at least* (see [unresolved-questions]) via scoped blanket `use impl Trait for T`. However, a blanket implementation can only be bound on a generic type parameter iff its bounds are fully covered by the generic type parameter's bounds and other available trait implementations on the generic type parameter, in the same way as this applies for global implementations. @@ -1364,15 +1498,14 @@ As a consequence of binding outside of generic contexts, it *is* possible to sta ```rust use std::fmt::{self, Display, Formatter}; -fn outside_scope(d: &dyn Display, f: &mut Formatter<'_>) -> fmt::Result { - d.fmt(f) // Binds to global/inherent implementation. -} - { use impl Display for dyn Display { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Restore binding to inherent global implementation within this function. + use ::{impl Display for dyn Display}; + write!(f, "Hello! ")?; - outside_scope(self, f)?; + d.fmt(f)?; write!(f, " See you!") } } @@ -1476,11 +1609,11 @@ Complexity aside, this could cause compiler performance issues since caching wou Fortunately, at least checking whether scoped implementations exist at all for a given trait and item scope should be reasonably inexpensive, so this hopefully won't noticeably slow down compilation of existing code. -That implementation binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. +That implementation environment binding on generic type parameters is centralised to the type discretisation site(s) may also help a little in this regard. ## Cost of additional monomorphised implementation instances -The [contextual-monomorphisation-of-generic-implementations-and-generic-functions] and the resulting additional instantiations of these implementations could have a detrimental effect on compile times and .text size (depending on optimisations). +The additional instantiations of implementations resulting from [binding-choice-by-implementations-bounds] could have a detrimental effect on compile times and .text size (depending on optimisations). This isn't unusual for anything involving *GenericParams*, but use of this feature could act as a multiplier to some extent. It's likely a good idea to evaluate relatively fine-grained caching in this regard, if that isn't in place already. @@ -1495,6 +1628,18 @@ A partial mitigation would be to have rustc include captured scoped implementati Note that with this RFC implemented, `TypeId` would still report the same value iff evaluated on generic type parameters with distinct but bound-irrelevant captured implementations directly, as long as only these top-level implementations differ and no nested captured *implementation environments* do. +## Marking a generic as implementation-invariant is a breaking change + +This concerns the split of [implementation-aware-generics] and [implementation-invariant-generics]. + +"Implementation-aware" is the logic-safe default. + +"Implementation-invariant" has better ergonomics in some cases. + +It would be great to make moving from the default here only a feature addition. To do this, a new coherence rule would likely have to be introduced to make implementations conflict if any type becoming implementation-invariant would make them conflict, and additionally to make such implementations shadow each other (to avoid all-too-unexpected silent behaviour changes). + +However, even that would not mitigate the behaviour change of type-erasing collections that are keyed on such generics that become type-invariant later, so making this a breaking change is simpler and overall more flexible. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -1853,7 +1998,7 @@ fn main() { } ``` -Note that to export *discrete* scoped `impl Into` in addition to their scoped `impl From`, the glue crates can use the following pattern, which discretises the global implementation and as such binds each scoped `impl From`: +Note that to export *discrete* scoped `impl Into` in addition to their scoped `impl From`, the glue crates can use the following pattern, which discretises the global implementation and as such binds to each scoped `impl From` in the respective exported scoped `impl Into`: ```rust pub use ::{ @@ -2239,7 +2384,7 @@ Similarly, types of generics with different captured *implementation environment ## Encourage readable code -This RFC aims to further decreases the mental workload required for code review, by standardising glue code APIs to some degree and by clarifying their use in other modules. +This RFC aims to further decrease the mental workload required for code review, by standardising glue code APIs to some degree and by clarifying their use in other modules. It also aims to create an import grammar that can be understood more intuitively than external newtypes when first encountered, which should improve the accessibility of Rust code somewhat. @@ -2332,7 +2477,7 @@ Since scoped implementations work much like global ones, many of the existing er The implementation of the errors and warnings in the compiler can also benefit from the existing work done for global implementations, or in some cases outright apply the same warning to both scoped and global implementations. -Since available-but-not-imported scoped implementations are easily discoverable by the compiler, they can be used to improve existing errors like *error[E0277]: the trait bound `[…]` is not satisfied* and *error[E0599]: no method named `[…]` found for struct `[…]` in the current scope* with quick-fix suggestions in at least some cases. +Since available-but-not-imported scoped implementations are easily discoverable by the compiler, they can be used to improve existing errors like *error[E0277]: the trait bound `[…]` is not satisfied* and *error[E0599]: no method named `[…]` found for struct `[…]` in the current scope* with quick-fix suggestions also for using an existing scoped implementation in at least some cases. ### Maintenance warnings for ecosystem evolution @@ -2359,7 +2504,7 @@ This is a *not entirely clean* ergonomics/stability trade-off, as well as a clea The main issue is that generics in the Rust ecosystem do not declare which trait implementations on their type parameters need to be consistent during their instances' lifetime, if any, and that traits like `PartialOrd` that do provide logical consistency guarantees over time are not marked as such in a compiler-readable way. -Ignoring this and not having distinction of [implementation-aware-generics]' discretised variants would badly break logical consistency of generic collections like `BTreeSet`, which relies on `Ord` to function. +Ignoring this and not having distinction of [implementation-aware-generics]' discretised variants would badly break logical consistency of generic collections like `BTreeSet`, which relies on `Ord`-consistency to function. On the other hand, certain types (e.g. references and (smart) pointers) that often wrap values in transit between modules *really* don't care about implementation consistency on these types. If these were distinct depending on available implementations on their values, it would create *considerable* friction while defining public APIs in the same scope as `struct` or `enum` definitions that require scoped implementations for `derive`s. @@ -2371,6 +2516,17 @@ Functions pointers and closure trait( object)s should probably be fairly easy to Traits and trait objects cannot be implementation invariant (including for their associated types!) because it's possible to define `OrderedExtend` and `OrderedIterator` traits with logical consistency requirement on `Ord` between them. +## Efficient compilation +[efficient-compilation]: #efficient-compilation + +In theory, it should be possible to unify many instances of generic functions that may be polymorphic under this proposal cheaply before code generation. (Very few previously discrete implementations become polymorphic under scoped `impl Trait for Type`.) + +This is mainly an effect of [layout-compatibility] and [binding-choice-by-implementations-bounds], so that, where the differences are only bounds-irrelevant, generated implementations are easily identical in almost all cases. The exception here are [implementation-aware-generics]' `TypeId`s (see also [typeid-of-generic-type-parameters-opaque-types]). Checking for this exception should be cheap if done alongside checks for e.g. function non-constness if possible, which propagates identically from callee to caller. + +Given equal usage, compiling code that uses scoped implementations could as such be slightly more efficient compared to use of newtypes and the resulting text size may be slightly smaller in some cases where newtype implementations are inlined differently. + +The compiler should treat implementations of the same empty trait on the same type as identical early on, so that no code generation is unnecessarily duplicated. However, unrelated empty-trait implementations must still result in distinct `TypeId`s when captured in a generic type parameter and observed there by a `where`-clause or through nesting in an implementation-aware generic. + ## Alternatives ### Named implementations @@ -2423,11 +2579,11 @@ There are some parallels between Genus's models and the scoped `impl Trait for T Some features are largely equivalent: -| Genus | Rust | notes / scoped `impl Trait for Type` | +| Genus | Rust (*without* scoped `impl Trait for Type`) | notes / scoped `impl Trait for Type` | |---|---|---| | Implicitly created default models | Explicit global trait implementations | Duck-typed implementation of unknown external traits is unnecessary since third party crates' implementations are as conveniently usable in scope as if global. | | Runtime model information / Wildcard models | Trait objects | Scoped implementations can be captured in trait objects, and the `TypeId` of generic type parameters can be examined. This does not allow for invisible runtime specialisation in all cases. | -| Bindings [only for inherent constraints on generic type parameters?] are part of type identity | not applicable |