- Feature Name:
const_repeat_expr
- Start Date: 2017-10-20
- RFC PR: rust-lang/rfcs#2203
- Rust Issue: rust-lang/rust#49147
⚠ This RFC has mostly been superseded ⚠
This turned out to be more complicated than expected to detect while being intuitive to the programmer. As such, it's expected that this problem space will be addressed with the inline consts from RFC 2920 instead, which have syntax to opt-in to the behaviour.
However, the simpler case of
[SOME_CONST_ITEM; N]
was kept (stabilized in rust-lang/rust#49147).
Relaxes the rules for repeat expressions, [x; N]
such that x
may also be
const
(strictly speaking rvalue promotable), in addition to typeof(x): Copy
.
The result of [x; N]
where x
is const
is itself also const
.
RFC 2000, const_generics
introduced the ability to have generically sized
arrays. Even with that RFC, it is currently impossible to create such an array
that is also const
. Creating an array that is const
may for example be
useful for the const_default
RFC which proposes the following trait:
pub trait ConstDefault { const DEFAULT: Self; }
To add an implementation of this trait for an array of any size where the
elements of type T
are ConstDefault
, as in:
impl<T: ConstDefault, const N: usize> ConstDefault for [T; N] {
const DEFAULT: Self = [T::DEFAULT; N];
}
In the example given by mem::uninitialized()
, a value of type
[Vec<u32>; 1000]
is created and filled. With this RFC, and when Vec::new()
becomes const, the user can simply write:
let data = [Vec::<u32>::new(); 1000];
println!("{:?}", &data[0]);
this removes one common reason to use uninitialized()
which "is incredibly
dangerous".
You have a variable or expression X
which is const, for example:
type T = Option<Box<u32>>;
const X: T = None;
Now, you'd like to use array repeat expressions [X; N]
to create an array
containing a bunch of X
es. Sorry, you are out of luck!
But with this RFC, you can now write:
const X: T = None;
const arr: [T; 100] = [X; 100];
or, if you wish to modify the array later:
const X: T = None;
let mut arr = [X; 100];
arr[0] = Some(Box::new(1));
Values which are const
are freely duplicatable as seen in the following
example which compiles today. This is also the case with Copy
. Therefore, the
value X
in the repeat expression may be simply treated as if it were of a
Copy
type.
fn main() {
type T = Option<Box<u32>>;
const X: T = None;
let mut arr = [X, X];
arr[0] = Some(Box::new(1));
}
Thus, the compiler may rewrite the following:
fn main() {
type T = Option<Box<u32>>;
const X: T = None;
let mut arr = [X; 2];
arr[0] = Some(Box::new(1));
}
internally as:
fn main() {
type T = Option<Box<u32>>;
// This is the value to be repeated.
// In this case, a panic won't happen, but if it did, that panic
// would happen during compile time at this point and not later.
const X: T = None;
let mut arr = {
let mut data: [T; 2];
unsafe {
data = mem::uninitialized();
let mut iter = (&mut data[..]).into_iter();
while let Some(elem) = iter.next() {
// ptr::write does not run destructor of elem already in array.
// Since X is const, it can not panic at this point.
ptr::write(elem, X);
}
}
data
};
arr[0] = Some(Box::new(1));
}
Additionally, the pass that checks const
ness must treat [expr; N]
as a
const
value such that [expr; N]
is assignable to a const
item as well
as permitted inside a const fn
.
Strictly speaking, the set of values permitted in the expression [expr; N]
are those where is_rvalue_promotable(expr)
or typeof(expr): Copy
.
Specifically, in [expr; N]
the expression expr
is evaluated:
- never, if
N == 0
, - one time, if
N == 1
, N
times, otherwise.
For values that are not freely duplicatable, evaluating expr
will result in
a move, which results in an error if expr
is moved more than once (including
moves outside of the repeat expression). These semantics are intentionally
conservative and intended to be forward-compatible with a more expansive
is_const(expr)
check.
It might make the semantics of array initializers more fuzzy. The RFC, however, argues that the change is quite intuitive.
The alternative, in addition to simply not doing this, is to modify a host of
other constructs such as mem::uninitialized()
, for loops over iterators,
[ptr::write
] to be const
, which is a larger change. The design offered by
this RFC is therefore the simplest and most non-intrusive design. It is also
the most consistent.
Another alternative is to allow a more expansive set of values is_const(expr)
rather than is_rvalue_promotable(expr)
. A consequence of this is that checking
constness would be done earlier on the HIR. Instead, checking if expr
is
rvalue promotable can be done on the MIR and does not require significant
changes to the compiler. If we decide to expand to is_const(expr)
in the
future, we may still do so as the changes proposed in this RFC are
compatible with such future changes.
The impact of not doing this change is to not enable generically sized arrays to
be const
as well as encouraging the use of mem::uninitialized
.
There are no unresolved questions.