- Feature Name:
scoped_impl_trait_for_type
[β¦]
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
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 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.
[β¦]
Rendered on GitHub: 3634-scoped-impl-trait-for-type.md
Use the rightmost icon in the file header to show the table of contents. The current section highlight doesn't sync automatically for me there though, unfortunately.
Change History: Commits Β· Tamschi/rust-rfcs Β· GitHub
Apologies in advance that it's a bit long. I had the initial idea earlier this year but only saw that the 2024 lang roadmap asks for ideas on coherence about two weeks ago and decided to give it a go. Then it turned out more complicated than I thought and kind of escalated.
There are a few rough parts where I think I need help from someone more familiar with how implementations are currently bound to call sites and on generic type parameters. If something doesn't add up regarding the feature implementation then that's likely because I never really looked at the compiler code so far, so please read those bits in terms of 'behaves as if' (and please tell me about those issues so I can fix them).
In any case, I hope there's nothing blatantly obvious that I missed, and thanks for your consideration.
Shout-out
- to teliosdev for a piece of very helpful early syntax criticism and
- to cofinite for pointing out how this can be used for sugary API extensions with syntax traits.
- to tfpdev, and to @SkiFire13 below, for suggestions on how to make the draft more approachable and easier to understand.