We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 1 of 28
Closures 101: build a pipeline of boxed closures
Build a pipeline of boxed callables, then apply it to each input number.
pub fn apply_pipeline(nums: Vec<i64>, ops: Vec<String>) -> Vec<i64>
Read operation names from left to right. Recognize these exact, case-sensitive strings and skip every other string without trimming or changing its case:
| Name | Operation on the current value x |
|---|---|
double |
x * 2 |
inc |
x + 1 |
dec |
x - 1 |
neg |
-x |
square |
x * x |
abs |
Absolute value of x |
Apply every recognized operation, including repetitions, to each number in
order. Preserve the order and length of nums. Empty input produces empty
output; an empty list of operations, or a list containing only unknown
names, leaves the numbers unchanged.
For [1, 2, 3] and ["double", "inc"], return [3, 5, 7].
Reversing those operations gives [4, 6, 8].
Inputs to this exercise keep every intermediate result within i64.
In particular, neg and abs are not applied to i64::MIN, and multiplication
must fit before the next operation runs. No wrapping or saturating arithmetic
is requested.
Use a Vec<Box<dyn Fn(i64) -> i64>> for this exercise. The output tests check
values; they do not enforce that representation. The purpose of the boxed
representation is to practice storing callables with different concrete types.
Give the closures a common type
Each closure expression has its own anonymous type, even when two expressions
have identical bodies. The starter returns different closure types inside
Some(...) in its match arms, without a common target type for coercion.
That produces E0308, a type mismatch.
A box containing a callable can coerce to Box<dyn Fn(i64) -> i64> when that
target type is known. Here is a complete example:
fn main() {
let pipeline: Vec<Box<dyn Fn(i64) -> i64>> = vec![
Box::new(|x| x * 2),
Box::new(|x| x + 1),
];
let result = pipeline.iter().fold(3, |value, op| op(value));
assert_eq!(result, 7);
}
If you build the list with filter_map, give its closure an explicit return
type: |name| -> Option<Box<dyn Fn(i64) -> i64>> { ... }. Return None for
an unknown name and Some(Box::new(...)) for a recognized operation.
This gives each match arm a place to perform the coercion.
Function items also implement the callable traits. For absolute value, use
Box::new(i64::abs). The function still needs Box::new; it does not turn
directly into a box through coercion alone.
Boxing is one choice
These operations do not capture any variables, so they can also coerce to function pointers. This is a valid alternative representation outside the exercise's boxed-callable requirement:
fn main() {
let pipeline: [fn(i64) -> i64; 2] = [|x| x * 2, |x| x + 1];
assert_eq!(pipeline.iter().fold(3, |value, op| op(value)), 7);
}
A capturing closure cannot use that function-pointer conversion. Generic
parameters and borrowed callable trait objects are other useful API choices.
Choosing a function at runtime does not inherently require a heap allocation.
Even Box::new needs no allocation for a zero-sized value, such as a
noncapturing closure; the pipeline vector still needs storage for its entries.
Inference and captured values
Closure parameter types can come from annotations, expected types and uses.
An identity closure |x| x does not become generic over unrelated argument
types: using the same closure with both an integer and a string is a mismatch.
A generic function fn identity<T>(x: T) -> T can support those separate calls.
Capturing data and calling the closure are separate concerns. move puts
captured values into the closure, copying them when their types are Copy.
It does not by itself mean the closure can only be called once:
fn main() {
let label = String::from("step");
let length = move || label.len();
assert_eq!(length(), 4);
assert_eq!(length(), 4);
}
This closure owns its string but only reads it when called, so it implements
Fn. A closure that changes captured state may need FnMut; one that moves
a captured non-Copy value out when called can only be called through FnOnce.
The operations in this pipeline capture nothing and can all be called via Fn.
Clippy's redundant_closure lint can suggest passing a function directly when
a closure only forwards its arguments. For example, i64::abs avoids the
wrapper |x| x.abs(). Such suggestions concern a particular expression;
they do not mean every closure wrapper is unnecessary.
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.