Skip to content

Commit b795879

Browse files
authored
Create 0000-cfg-attribute-in-where.md
1 parent c32adbd commit b795879

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

text/0000-cfg-attribute-in-where.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
- Feature Name: `cfg_attribute_in_where`
2+
- Start Date: 2023-03-11
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Let's make it more elegant to conditionally compile trait bounds by allowing cfg-attributes directly in where clauses.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
Currently, there is limited support for conditionally compiling trait bounds. Rust already supports using cfg-attributes in
15+
angle-bracketed bounds, so the following implementation is possible but unwieldy, and grows combinatorically with multiple
16+
independent compilation condition/bound pairs:
17+
18+
```rust
19+
impl<
20+
#[cfg(something)] T: SomeRequirement,
21+
#[cfg(not(something))] T
22+
> SomeTrait<T> for Thing {}
23+
```
24+
25+
This also can't be used for bounds on associated types or other more complicated left-hand items that can only occur in full where bounds.
26+
27+
Another somewhat-common approach is to create a dummy trait that conditionally branches and implement that, like so:
28+
29+
```rust
30+
#[cfg(something)]
31+
trait Dummy: SomeRequirement {}
32+
#[cfg(something)]
33+
impl<T: SomeRequirement> Dummy for T {}
34+
#[cfg(not(something))]
35+
trait Dummy {}
36+
#[cfg(not(something))]
37+
impl<T> Dummy for T {}
38+
39+
impl<T: Dummy> SomeTrait<T> for Thing {}
40+
```
41+
42+
However, this boilerplate does not grow well for multiple conditionally-compiled requirements, becoming rather soupy even at N = 2:
43+
44+
```rust
45+
#[cfg(something_a)]
46+
trait DummyA: SomeRequirementA {}
47+
#[cfg(something_a)]
48+
impl<T: SomeRequirementA> DummyA for T {}
49+
#[cfg(not(something_a))]
50+
trait DummyA {}
51+
#[cfg(not(something_a))]
52+
impl<T> DummyA for T {}
53+
54+
#[cfg(something_b)]
55+
trait DummyB: SomeRequirementB {}
56+
#[cfg(something_b)]
57+
impl<T: SomeRequirementB> DummyB for T {}
58+
#[cfg(not(something_b))]
59+
trait DummyB {}
60+
#[cfg(not(something_b))]
61+
impl<T> DummyB for T {}
62+
63+
impl<T: DummyA + DummyB> SomeTrait<T> for Thing {}
64+
```
65+
66+
Other alternative ways of achieving this also exist, but are typically macro heavy and difficult to implement or check. Importantly, this
67+
functionality already exists in the language, but quickly grows out of reasonable scope to ergonomically implement.
68+
69+
# Guide-level explanation
70+
[guide-level-explanation]: #guide-level-explanation
71+
72+
Where clauses can use cfg-attributes on individual bounds, like so:
73+
74+
```rust
75+
impl<T> SomeTrait<T> for Thing
76+
where
77+
#[cfg(something_a)] T: SomeRequirementA,
78+
#[cfg(something_b)] T: SomeRequirementB,
79+
{}
80+
```
81+
or on functions, including multiple cfg-attributes on a single bound:
82+
```rust
83+
fn some_function<T>(val: &T)
84+
where
85+
#[cfg(something_a)]
86+
T: SomeRequirementA,
87+
#[cfg(something_b)]
88+
#[cfg(not(something_a))]
89+
#[cfg(target_os(some_os))]
90+
T: SomeRequirementB,
91+
{}
92+
```
93+
and in other situations where where clauses apply.
94+
95+
During compilation, all cfg-attributes on a where bound are evaluated. If the evaluation result is false, then the bound in question is not
96+
compiled and the bound does not apply to the given type. This may cause errors if code that relies on those bounds is not itself also
97+
conditionally compiled. For anyone familiar with cfg-attributes already, this should behave similarly to how they are used in, say, struct
98+
fields or on function signatures.
99+
100+
# Reference-level explanation
101+
[reference-level-explanation]: #reference-level-explanation
102+
103+
In positions that accept where clauses, such as trait implementations and function signatures, individual clauses can now be decorated with
104+
cfg-attributes. The cfg-attribute must be on the left hand of the colon (e.g. `#[cfg(...]) T: Foo` rather than `T: #[cfg(...)] Foo`) and
105+
apply for that one bound, up to the comma or end of the where section. Each bound collection will be conditionally compiled depending on the
106+
conditions specified in the cfg arguments. Note that this may cause a where clause to conditionally compile as having no bound entries
107+
(i.e. an empty where clause), but this has been allowed in Rust since 1.16 and already occurs from time to time when using macros.
108+
109+
# Drawbacks
110+
[drawbacks]: #drawbacks
111+
112+
As with any feature, this adds complication to the language and grammar. In general, conditionally compiled trait bounds can create
113+
unintended interactions or constraints on code based on compilation targets or combinations of features. The drawbacks to this proposed
114+
code path already apply to the existing workarounds used to achieve the same functionality.
115+
116+
# Rationale and alternatives
117+
[rationale-and-alternatives]: #rationale-and-alternatives
118+
119+
This functionality can already be achieved in Rust, but not elegantly, and without a clear relationship between the written code and its
120+
intent. The two main alternatives are dummy traits and cfg-attributes in angle-bracketed bounds. Compared to using dummy traits, adding a
121+
cfg-attribute in a where clause makes the intent immediately local and more directly associates it with the piece of code it's intended to
122+
control. Compared to using cfg-attributes in angle-bracketed bounds, adding a cfg-attribute in a where clause means each bound can be
123+
individually toggled without the need for combinatoric combinations of conditions, and allows conditional compilation on bounds with
124+
nontrivial item paths.
125+
126+
The need for conditionally compiling trait bounds can arise in applications with different deployment targets or that want to release
127+
builds with different sets of functionality (e.g. client, server, editor, demo, etc.). It would be useful to support cfg-attributes
128+
directly here without requiring workarounds to achieve this functionality. Macros, proc macros, and so on are also ways to conditionally
129+
compile where clauses, but these also introduce at least one level of obfuscation from the core goal. Finally, traits can be wholly
130+
duplicated under different cfg-attributes, but this scales poorly with both the size and intricacy of the trait and the number of
131+
interacting attributes (which may grow combinatorically), and can introduce a maintenance burden from repeated code.
132+
133+
# Prior art
134+
[prior-art]: #prior-art
135+
136+
I'm not aware of any prior work in adding this to the language. Languages with preprocessors could support this with something like:
137+
138+
```rust
139+
impl<T> SomeTrait<T> for Thing
140+
where
141+
#ifdef SOMETHING_A
142+
T: SomeRequirementA
143+
#endif
144+
{}
145+
```
146+
but that's not the way I would expect Rust to provide this kind of functionality.
147+
148+
# Unresolved questions
149+
[unresolved-questions]: #unresolved-questions
150+
151+
In theory, I don't see any harm in cfg-attributes decorating individual bounds on the right hand side of the colon. Is it worth adding that
152+
potential feature as well? Personally, I don't see it as being worth the added complexity given that you can have multiple individual bound
153+
declarations for the same item. Doing so would also create an inconsistency, given that this isn't currently allowed in angle-bracketed
154+
bounds either.
155+
156+
# Future possibilities
157+
[future-possibilities]: #future-possibilities
158+
159+
Conditional bounds on where clauses could also be used for [trivial bounds](https://github.com/rust-lang/rust/issues/48214). I don't believe
160+
any extra support would be needed here since the conditional compilation would occur at the grammar level rather than the type level.

0 commit comments

Comments
 (0)