We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 19 of 20
Const generics: moving a size from runtime to compile time
Compute moving averages with a window width supplied as a const generic, then add a dispatcher that accepts a width at runtime.
pub fn moving_average<const W: usize>(v: &[f64]) -> Vec<f64>
pub fn dispatch_window(v: &[f64], w: usize) -> Vec<f64>
Required results
For each complete window, start a new f64 sum at 0.0, add its elements
from left to right, then divide by the width converted to f64. Preserve
window order. If the width is zero or exceeds v.len(), return an empty
vector. Otherwise, return v.len() - width + 1 averages. Width one works
too, and empty input always produces empty output.
For example, width two over [2.0, 4.0, 8.0] produces [3.0, 6.0].
Use ordinary floating-point addition and division, without rounding the
results or replacing them with integer arithmetic. Each window starts its
own sum: a rolling sum or reassociated additions can produce different
results because floating-point addition is not associative.
dispatch_window must call the const-generic implementation for widths
2, 4, 8 and 16. Use a runtime-width loop for every other width,
with the same empty-input and arithmetic rules. The supplied tests use finite
sample values. The harness checks output summaries and directly calls the
width-four function; it does not prove which dispatch branches you wrote.
What a const generic supplies
W is a value parameter of type usize. In a call such as
moving_average::<4>(&values), the width is known for that instantiation.
It can appear in types such as [f64; W] as well as ordinary expressions
in the function body. Stable const parameter types include the integer
types, bool and char; f64 is not a const parameter type.
The compiler can specialize generic code for the instantiations used by the program. This can expose useful optimization opportunities, but does not guarantee separate machine-code copies or a particular speedup.
Repair the starter's array length
The starter contains [0.0f64; W + 1]. Stable Rust rejects arithmetic on a
generic parameter in this array-repeat length. [0.0f64; W] is supported.
You do not need a scratch array to solve this exercise, so remove it and
build the output vector instead.
This restriction does not ban arithmetic involving W throughout the
function. Ordinary expressions can use it. This complete example uses N
both as an array length and in a calculation:
fn zeros<const N: usize>() -> [u8; N] {
[0; N]
}
fn next_size<const N: usize>() -> Option<usize> {
N.checked_add(1)
}
fn main() {
assert_eq!(zeros::<3>(), [0, 0, 0]);
assert_eq!(next_size::<3>(), Some(4));
assert_eq!(next_size::<{ usize::MAX }>(), None);
}
Braces can delimit a const argument such as { usize::MAX } or
{ 2 + 2 }. They do not make a generic array length like W + 1 valid
in the starter. Read the diagnostic at the expression that failed rather
than treating it as a restriction on every use of const parameters.
Borrow each window as a fixed-size array
After handling width zero and widths larger than the input,
v.windows(W) yields overlapping slices, each exactly W elements long.
Each can be converted to &[f64; W] using try_into. This borrows the same
elements; it does not allocate or copy the window.
Here is a complete example for width two:
fn main() {
let values = [2.0_f64, 4.0, 8.0];
let mut averages = Vec::new();
for window in values.windows(2) {
let fixed: &[f64; 2] = window.try_into().expect("two-element window");
let mut sum = 0.0;
for value in fixed {
sum += value;
}
averages.push(sum / 2.0);
}
assert_eq!(averages, [3.0, 6.0]);
}
The conversion cannot fail for a window produced with the same width as
the target array reference. It can fail for an arbitrary slice of a
different length. Calling windows(0) panics, so handle zero first.
Because this implementation forms the type [f64; W], use const widths
whose array layout fits the target architecture. An enormous const width
can fail to compile even if a runtime guard would return early. An oversized
runtime width in the fallback still returns an empty vector.
Specialize selectively, then measure
A known width can help the optimizer simplify loops or checks. Runtime slices can also benefit from inlining and constant propagation. Neither using a const generic nor converting a slice guarantees loop unrolling, removal of every bounds check, or a particular generated instruction sequence.
The four dispatch arms let you practice combining fixed-width and general paths. More instantiations can increase build time and code size, but the final effect depends on optimization and linking. Measure representative inputs before choosing additional specializations.
Preserve the specified addition order while experimenting. Submissions must compile, pass the output tests and pass the runner's Clippy checks.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.