Skip to content

Commit 6dee359

Browse files
committed
Reduce the number of constraints repeated when writing T: SomeTrait
1 parent d8d2de4 commit 6dee359

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

text/0000-visible-where-clause.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
- Feature Name: visible_trait_where_clause
2+
- Start Date: (2017-02-24)
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Today when you write `T: SomeTrait`, you are also implicitly allowed to rely on
10+
`T: AnySuperTraits`. However, if `SomeTrait` has additional constraints besides
11+
supertraits, they are must be repeated every time `T: SomeTrait` is written.
12+
This RFC allows `T: SomeTrait` to implicitly inherit any constraints of
13+
`SomeTrait` that are on a concrete type.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
For traits which have type parameters, the parameter is often as important as
19+
the type itself. For example, consider that `T: Into<U>` and `U: From<T>` are
20+
functionally equivalent. `T` and `U` can be considered to be of equal
21+
importance.
22+
23+
However, they are not equivalent when it comes to trait definitions. If you were
24+
to write `trait Foo where Self: From<Bar>`, then writing `T: Foo` implicitly
25+
also means `T: From<Bar>`. If you change that to the functionally equivalent
26+
form of `trait Foo where Bar: Into<Self>`, then every place that `T: Foo` is
27+
written is required to specify that `Bar: Into<T>` as well. There is no reason
28+
that these should be treated differently.
29+
30+
As a concrete example of where this has caused pain in the wild, Diesel went so
31+
far as to change a trait from `T: NativeSqlType<DB>` to `DB: HasSqlType<T>`,
32+
simply so that [they could constraint that a "backend" supports the ANSI
33+
standard types](https://github.com/diesel-rs/diesel/commit/b2476d1d). That crate
34+
also has had to avoid adding constraints like `String: FromSql<Text, Self>` as
35+
doing a similar re-organization would cause coherence issues.
36+
37+
# Detailed design
38+
[design]: #detailed-design
39+
40+
For the purposes of this RFC, a "constraint" is defined as an item which appears
41+
in the where clause in the form `target: obligation`. A constraint is considered
42+
"inherited" if `T: SomeTrait` implicitly adds constraints from the definition of
43+
`SomeTrait`.
44+
45+
For all examples given, assume that an item has been fully desugared (`trait
46+
Foo<Bar: Baz>` is expanded to `trait Foo<Bar> where Bar: Baz`, `trait Foo: Bar`
47+
is expanded to `trait Foo where Self: Bar`, and `where T: Foo + Bar` is expanded
48+
to `where T: Foo, T: Bar`).
49+
50+
There is no RFC that specifically lays out when constraints are inherited, or
51+
what the specific reasoning was. However, the design appears to be intended to
52+
treat traits and structs similarly. Constraints on structs are assumed to be
53+
based around the type parameters. This is a reasonable assumption for structs,
54+
since any reference to the `Self` type by definition includes the type
55+
parameters.
56+
57+
The reasoning behind requiring that `T: SomeTrait<U>` also repeat the
58+
constraints that `SomeTrait` laid out for `U` is likely to ensure that
59+
[loosening bounds is not a breaking change](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-loosening-bounds),
60+
as any caller relying on those bounds would have had to state them explicitly.
61+
62+
However, traits differ due to the fact that the type of `Self` is not known.
63+
Referencing `Self` does not necessarily mean that we are referencing the type
64+
parameters of the trait. For this reason, `T: SomeTrait` inherits any any
65+
constraints that `SomeTrait` places on `Self`.
66+
67+
Supertraits also differ from normal constraints in that they affect the vtable
68+
for trait objects. This RFC does not intend to change what is defined as a
69+
supertrait, or in any way affect trait objects or object safety. This RFC only
70+
seeks to expand when a constraint is inherited by `T: SomeTrait`.
71+
72+
The current rules for whether a constraint is inherited are simply "the target
73+
of the constraint is `Self`". This RFC proposes that a constraint is inherited
74+
when one of the following is true:
75+
76+
- The target of the constraint is `Self`
77+
- The type parameters of the trait do not appear in the target and `Self`
78+
appears in either the target or the obligation
79+
80+
Here are some concrete examples (all assume the trait `Foo<T>`)
81+
82+
- `Self: Bar`
83+
- Before: inherited
84+
- After: inherited
85+
- `Self: Bar<T>`
86+
- Before: inherited
87+
- After: inherited
88+
- `T: Bar<Self>`
89+
- Before: not inherited
90+
- After: not inherited
91+
- `Vec<Self>: Bar`
92+
- Before: not inherited
93+
- After: inherited
94+
- `i32: Bar<Self>`
95+
- Before: not inherited
96+
- After: inherited
97+
- `Result<Self, T>: Bar`
98+
- Before: not inherited
99+
- After: not inherited
100+
- `i32: Bar<T, Self>`
101+
- Before: not inherited
102+
- After: inherited
103+
104+
This RFC does *not* propose any changes in what is required in the actual trait
105+
declaration. For example, if writing `trait Foo where Bar: Baz<Self>` required
106+
that `Self: Sized`, that constraint would still need to be explicitly added to
107+
the definition of `Foo`. However, `Baz` may have other constraints that fall
108+
under these new rules, which one could now assume about `Bar`.
109+
110+
# How We Teach This
111+
[how-we-teach-this]: #how-we-teach-this
112+
113+
The term "constraint" is already pretty well established with the meaning used
114+
in this RFC. I haven't found the terminology "inherited" used in reference to
115+
constraints (but actual documentation around them is surprisingly sparse). Since
116+
this is giving a name to something which currently only occurs for supertraits
117+
(which emulate a subtype relationship) and lifetime requirements (which is an
118+
actual subtype relationship), the term "inherited" seems fitting.
119+
120+
This idea is a continuation of an existing Rust pattern, but one that is mostly
121+
undocumented so would need to be essentially introduced as a new one.
122+
123+
This feature is introduced to new and existing users simply by the compiler
124+
telling them what to do. Other than concerns regarding backwards compatibility,
125+
it doesn't need to be explicitly taught.
126+
127+
_The Rust Programming Language_ does not cover supertraits or where clauses, and
128+
I do not think that we need to add it. _Rust by Example_ would benefit from
129+
supertraits being mentioned in its section on "bounds", but I do not think it
130+
needs to lay out the full set of rules.
131+
132+
The Rust Reference does not currently have a section where this addition would
133+
fit. A new section should be added about constraints in general which covers:
134+
135+
- The implicit `T: Sized`
136+
- Constraints of structs
137+
- Supertraits
138+
- These new rules
139+
140+
# Drawbacks
141+
[drawbacks]: #drawbacks
142+
143+
This change potentially causes loosening constraints to be considered a major
144+
change under [RFC #1105]. The RFC currently lists loosening constraints as a
145+
minor change, but it does so under "Signatures in type definitions" which
146+
doesn't actually cover traits. Supertraits are never explicitly mentioned in
147+
that document. If 1105 is interpreted to have removing a supertrait be
148+
considered a major change, this RFC expands the potential for a major change.
149+
150+
(1105 should be amended to clarify whether removing a supertrait is a major
151+
change, regardless of whether this RFC is accepted).
152+
153+
[RFC #1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md
154+
155+
However, there is likely very little code in the wild which has constraints that
156+
would be inherited by these new rules today. The type of constraint covered by
157+
this RFC is significantly less useful if it is not inherited. Users who do wish
158+
to add this sort of constraint in a way that is not inherited can place the
159+
constraint on specific methods instead of on the trait itself.
160+
161+
# Alternatives
162+
[alternatives]: #alternatives
163+
164+
We could also have the simpler rule of "target is a type other than `Self` and
165+
no type parameters from the trait appear in the constraint". However, that
166+
leaves the case of `Self: Bar<T>` as a weird inconsistency. (It should be noted
167+
that a constraint that doesn't include the type parameters by definition must
168+
include `Self` to be valid, otherwise it is simply an assertion which is always
169+
true or false)
170+
171+
# Unresolved questions
172+
[unresolved]: #unresolved-questions
173+
174+
None

0 commit comments

Comments
 (0)