Skip to content

Commit 992550b

Browse files
committed
Shrink the scope of the associated const changes to RFC 195, and mention trait objects.
1 parent 1ad497d commit 992550b

File tree

1 file changed

+22
-145
lines changed

1 file changed

+22
-145
lines changed

text/0195-associated-items.md

Lines changed: 22 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,7 @@ trait Foo<Input1, Input2> {
882882
type Output1;
883883
type Output2;
884884
lifetime 'a;
885+
const C: bool;
885886
...
886887
}
887888
```
@@ -894,6 +895,7 @@ T: Foo<I1, I2, Output1 = O1>
894895
T: Foo<I1, I2, Output2 = O2>
895896
T: Foo<I1, I2, Output1 = O1, Output2 = O2>
896897
T: Foo<I1, I2, Output1 = O1, 'a = 'b, Output2 = O2>
898+
T: Foo<I1, I2, Output1 = O1, 'a = 'b, C = true, Output2 = O2>
897899
```
898900

899901
The output constraints must come after all input arguments, but can appear in
@@ -954,20 +956,21 @@ trait Foo<Input1, Input2> {
954956
type Output1;
955957
type Output2;
956958
lifetime 'a;
959+
const C: bool;
957960
...
958961
}
959962
```
960963

961964
Unlike the case for static trait bounds, which do not have to specify any of the
962-
associated types or lifetimes (but do have to specify the input types), trait
963-
object types must specify all of the types:
965+
associated types, lifetimes, or consts, (but do have to specify the input types),
966+
trait object types must specify all of the types:
964967

965968
```rust
966969
fn consume_foo<T: Foo<I1, I2>>(t: T) // this is valid
967970
fn consume_obj(t: Box<Foo<I1, I2>>) // this is NOT valid
968971

969972
// but this IS valid:
970-
fn consume_obj(t: Box<Foo<I1, I2, Output1 = O2, Output2 = O2, 'a = 'static>>)
973+
fn consume_obj(t: Box<Foo<I1, I2, Output1 = O2, Output2 = O2, 'a = 'static, C = true>>)
971974
```
972975

973976
With this design, it is clear that none of the non-`Self` types are erased as
@@ -1199,70 +1202,10 @@ clauses, it is probably too much of a hack to be viable for use in `libstd`.
11991202

12001203
### Associated consts in generic code
12011204

1202-
There are some restrictions on uses of associated consts in generic code. These
1203-
might be loosened or removed in the future (see the related sub-sections in
1204-
"Unresolved questions" below).
1205-
1206-
1. Values of constant expressions in match patterns cannot depend on a type
1207-
parameter (by extension, neither can the types of such expressions). This
1208-
restriction is necessary for exhaustiveness and reachability to be checked
1209-
in generic code.
1210-
1211-
Note that the dependence of a value on a type parameter may be indirect:
1212-
1213-
```rust
1214-
enum MyEnum {
1215-
Var1,
1216-
Var2,
1217-
}
1218-
trait HasVar {
1219-
const VAR: MyEnum;
1220-
}
1221-
fn do_something<T: HasVar>(x: MyEnum) {
1222-
const y: MyEnum = <T>::VAR;
1223-
// The following is forbidden because the value `y` depends on `T`.
1224-
match x {
1225-
y => { /* ... */ }
1226-
_ => { /* ... */ }
1227-
}
1228-
// However, this is OK because the guard is not a part of the pattern.
1229-
match x {
1230-
z if z == y => { /* ... */ }
1231-
_ => { /* ... */ }
1232-
}
1233-
}
1234-
```
1235-
1236-
2. Array sizes that depend on type parameters cannot be compared for equality
1237-
by type-checking, with one exception: if the expression for an array size
1238-
comprises only a single reference to a constant item (or associated item),
1239-
it will be considered equal to any other array size that refers to the same
1240-
item, even if that item itself depends on the type parameters.
1241-
1242-
For clarification, here are some examples. Assume that `T` is a type
1243-
parameter in the outer scope, and that it is known to have an associated
1244-
const `<T>::N` of type `usize`.
1245-
1246-
```rust
1247-
// This is OK (but there are limitations to how x can be used).
1248-
let x: [u8; <T>::N] = [0u8; <T>::N];
1249-
// Equivalent to the above.
1250-
let x = [0u8; <T>::N];
1251-
// Neither of the following are allowed (type checking shouldn't have to
1252-
// know anything about arithmetic).
1253-
let x: [u8; 2 * <T>::N] = [0u8; <T>::N + <T>::N];
1254-
let x: [u8; <T>::N + 1] = [0u8; 1 + <T>::N];
1255-
// Still not allowed.
1256-
let x: [u8; <T>::N + 1] = [0u8; <T>::N + 1];
1257-
// Workaround for the expression above.
1258-
const N_PLUS_1: usize = <T>::N + 1;
1259-
let x: [u8; N_PLUS_1] = [0u8; N_PLUS_1];
1260-
// Neither of the following are allowed.
1261-
const ALIAS_N_PLUS_1: usize = N_PLUS_1;
1262-
let x: [u8; N_PLUS_1] = [0u8; ALIAS_N_PLUS_1];
1263-
const ALIAS_N: usize = <T>::N;
1264-
let x: [u8; <T>::N] = [0u8; ALIAS_N];
1265-
```
1205+
If the value of an associated const depends on a type parameter (including
1206+
`Self`), it cannot be used in a constant expression. This restriction will
1207+
almost certainly be lifted in the future, but this raises questions outside the
1208+
scope of this RFC.
12661209

12671210
# Staging
12681211

@@ -1471,97 +1414,31 @@ on implementation concerns, which are not yet clear.
14711414
## Generic associated consts in match patterns
14721415

14731416
It seems desirable to allow constants that depend on type parameters in match
1474-
patterns, but it's not clear how to do so.
1475-
1476-
Looking at the `HasVar` example above, one possibility would be to simply treat
1477-
the first, forbidden match expression as syntactic sugar for the second, allowed
1478-
match expression that uses a pattern guard. This is simple to implement because
1479-
one can simply ignore the constant when performing exhaustiveness and
1480-
reachability checks. Unfortunately, this approach blurs the difference between
1481-
match patterns (which provide strict checks) and pattern guards (which are just
1482-
useful syntactic sugar), and it does not increase the expressiveness of the
1483-
language.
1484-
1485-
An alternative would be to allow `where` clauses to place constraints on
1486-
associated consts. If an associated const is known to be equal/unequal to some
1487-
other value (or in the case of integers, inside/outside a given range), this can
1488-
inform exhaustiveness and reachability checks. But this requires more design and
1489-
implementation work, and more syntax.
1417+
patterns, but it's not clear how to do so while still checking exhaustiveness
1418+
and reachability of the match arms. Most likely this requires new forms of
1419+
where clause, to constrain associated constant values.
14901420

14911421
For now, we simply defer the question.
14921422

14931423
## Generic associated consts in array sizes
14941424

1495-
The above solution for type-checking array sizes is somewhat unsatisfactory. In
1496-
particular, it is counter-intuitive that neither of the following will type
1497-
check:
1425+
It would be useful to be able to use trait-associated constants in generic code.
14981426

14991427
```rust
15001428
// Shouldn't this be OK?
15011429
const ALIAS_N: usize = <T>::N;
15021430
let x: [u8; <T>::N] = [0u8; ALIAS_N];
1503-
// This is likely to yield an embarrassing error message such as:
1504-
// "couldn't prove that `<T>::N + 1` is equal to `<T>::N + 1`"
1505-
let x: [u8; <T>::N + 1] = [0u8; <T>::N + 1];
1506-
```
1507-
1508-
A function like this is especially affected:
1509-
1510-
```rust
1511-
trait HasN {
1512-
const N: usize;
1513-
}
1514-
fn foo<T: HasN>() -> [u8; <T>::N + 1] {
1515-
// Can't be verified to be correct for the return type, and can't use the
1516-
// intermediate const workaround due to scoping issues.
1517-
[0u8; <T>::N + 1]
1518-
}
1431+
// Or...
1432+
let x: [u8; T::N + 1] = [0u8; T::N + 1];
15191433
```
15201434

1521-
This can be worked around with type-level naturals that use associated consts to
1522-
produce array sizes, but this is syntactically a bit inelegant.
1435+
However, this causes some problems. What should we do with the following case in
1436+
type checking, where we need to prove that a generic is valid for any `T`?
15231437

15241438
```rust
1525-
// Assume that `TypeAdd` and `One` are from a type-level naturals or similar
1526-
// library, and that `NAsTypeNatN` provides some way of translating the `N`
1527-
// on a `HasN` to a type compatible with that library.
1528-
trait HasN {
1529-
const N: usize;
1530-
type TypeNatN;
1531-
}
1532-
fn foo<T: HasN>() -> [u8; TypeAdd<<T>::TypeNatN, One>::AsUsize] {
1533-
// Because the type `TypeAdd<<T>::TypeNatN, One>` can be verified to be
1534-
// equal to itself in type checking, we know that the associated const
1535-
// `AsUsize` below must be the same item as the `AsUsize` mentioned in the
1536-
// return type above.
1537-
[0u8; TypeAdd<<T>::NAsTypeNat, One>::AsUsize]
1538-
}
1439+
let x: [u8; T::N + T::N] = [0u8; 2 * T::N];
15391440
```
15401441

1541-
There are a variety of possible ways to address the above issues, including:
1542-
1543-
- Implementing smarter handling of consts that are just aliases of other
1544-
constant items.
1545-
- Allowing `where` clauses to constrain some associated constants to be equal,
1546-
to other expressions, and using this information in type checking.
1547-
- Adding normalization with little or no awareness of arithmetic (e.g. allowing
1548-
expressions that are exactly the same to be considered equal, or using only
1549-
a very basic understanding of which operations are commutative and/or
1550-
associative).
1551-
- Adding new syntax and/or new capability to plugins to allow type-level
1552-
naturals to be used with more ergonomic and clear syntax.
1553-
- Implementing a dependent type system that provides built-in semantics for
1554-
integer arithmetic at the type level, rather than implementing this in an
1555-
external or standard library.
1556-
- Using a full-fledged SMT solver.
1557-
- Some other creative solutions not on this list.
1558-
1559-
While there are many ways to improve on the current design, and many of these
1560-
approaches are not mutually exclusive, much more work is needed to investigate
1561-
and implement a self-consistent, effective, and ideally intuitive set of
1562-
solutions.
1563-
1564-
Though admittedly not very satisfying at the moment, the current approach has
1565-
the advantage of being (arguably) a good minimalist design, allowing associated
1566-
consts to be used for array sizes in generic code now, but also allowing for any
1567-
of a number of improved systems to be implemented later.
1442+
We would like to handle at least some obvious cases (e.g. proving that
1443+
`T::N == T::N`), but without trying to prove arbitrary statements about
1444+
arithmetic. The question of how to do this is deferred.

0 commit comments

Comments
 (0)