Suppose we have a tree:
#[derive(Default, Debug, PartialEq, Eq)]
struct Tree {
children: Vec<Tree>,
}
And we want to build it from a list of bools where true is like an open tag and false is like a close tag in XML. I.e. [true, true, false, true, false, false] is
root -> node -> node
`-> node
We can easily parse this recursively like this:
fn read_tree_recursive<'a>(tags: &mut impl Iterator<Item=&'a bool>) -> Tree {
let mut tree = Tree::default();
while let Some(&tag) = tags.next() {
if tag {
tree.children.push(read_tree_recursive(tags));
} else {
break;
}
}
tree
}
Here is some test code:
fn main() {
assert_eq!(
read_tree_recursive(&mut [true, true, false, true, false, false].iter()),
Tree {
children: vec![
Tree {
children: vec![
Tree::default(),
Tree::default(),
],
}
],
},
);
}
But how do you do this iteratively? In any other language you'd make a stack of pointers on the heap something like this:
fn read_tree_iterative(tags: &[bool]) -> Tree {
let mut root = Tree::default();
let tree_stack: Vec<&mut Tree> = vec![&mut root];
for &tag in tags {
if tag {
tree_stack.last().unwrap().children.push(Tree::default());
tree_stack.push(tree_stack.last().unwrap().children.last_mut().unwrap());
} else {
tree_stack.pop();
}
}
root
}
Unfortunately this doesn't work in Rust because Rust can't know that we're only ever mutating tree_stack.last(). The recursive version uses function calls to enforce that, but obviously it has the downsides that come with recursion.
Is there a good way around this other than resorting to RefCell? Unfortunately Rust doesn't have become so TCO isn't a good option either.
You can simply store owned values in your stack and add them to the children when you pop them from the stack:
fn read_tree_iterative<'a> (tags: &mut impl Iterator<Item=&'a bool>) -> Tree {
let mut stack = Vec::new();
let mut last = Tree::default();
for &tag in tags {
if tag {
stack.push (last);
last = Tree::default();
} else {
let mut parent = stack.pop().unwrap();
parent.children.push (last);
last = parent;
}
}
last
}
Playground
Related
I want to lazily consume the nodes of a file tree one by one while sorting the siblings on each level.
In Python, I'd use a synchronous generator:
def traverse_dst(src_dir, dst_root, dst_step):
"""
Recursively traverses the source directory and yields a sequence of (src, dst) pairs;
"""
dirs, files = list_dir_groom(src_dir) # Getting immediate offspring.
for d in dirs:
step = list(dst_step)
step.append(d.name)
yield from traverse_dst(d, dst_root, step)
for f in files:
dst_path = dst_root.joinpath(step)
yield f, dst_path
In Elixir, a (lazy) stream:
def traverse_flat_dst(src_dir, dst_root, dst_step \\ []) do
{dirs, files} = list_dir_groom(src_dir) # Getting immediate offspring.
traverse = fn d ->
step = dst_step ++ [Path.basename(d)]
traverse_flat_dst(d, dst_root, step)
end
handle = fn f ->
dst_path =
Path.join(
dst_root,
dst_step
)
{f, dst_path}
end
Stream.flat_map(dirs, traverse)
|> Stream.concat(Stream.map(files, handle))
end
One can see some language features addressing recursion: yield from in Python, flat_map in Elixir; the latter looks like a classic functional approach.
It looks like whatever is lazy in Rust, it's always an iterator. How am I supposed to do more or less the same in Rust?
I'd like to preserve the structure of my recursive function with dirs and files as vectors of paths (they are optionally sorted and filtered).
Getting dirs and files is already implemented to my liking:
fn folders(dir: &Path, folder: bool) -> Result<Vec<PathBuf>, io::Error> {
Ok(fs::read_dir(dir)?
.into_iter()
.filter(|r| r.is_ok())
.map(|r| r.unwrap().path())
.filter(|r| if folder { r.is_dir() } else { !r.is_dir() })
.collect())
}
fn list_dir_groom(dir: &Path) -> (Vec<PathBuf>, Vec<PathBuf>) {
let mut dirs = folders(dir, true).unwrap();
let mut files = folders(dir, false).unwrap();
if flag("x") {
dirs.sort_unstable();
files.sort_unstable();
} else {
sort_path_slice(&mut dirs);
sort_path_slice(&mut files);
}
if flag("r") {
dirs.reverse();
files.reverse();
}
(dirs, files)
}
Vec<PathBuf can be iterated as is, and there is standard flat_map method. It should be possible to implement Elixir style, I just can't figure it out yet.
This is what I already have. Really working (traverse_flat_dst(&SRC, [].to_vec());), I mean:
fn traverse_flat_dst(src_dir: &PathBuf, dst_step: Vec<PathBuf>) {
let (dirs, files) = list_dir_groom(src_dir);
for d in dirs.iter() {
let mut step = dst_step.clone();
step.push(PathBuf::from(d.file_name().unwrap()));
println!("d: {:?}; step: {:?}", d, step);
traverse_flat_dst(d, step);
}
for f in files.iter() {
println!("f: {:?}", f);
}
}
What I want (not yet working!):
fn traverse_flat_dst_iter(src_dir: &PathBuf, dst_step: Vec<PathBuf>) {
let (dirs, files) = list_dir_groom(src_dir);
let traverse = |d| {
let mut step = dst_step.clone();
step.push(PathBuf::from(d.file_name().unwrap()));
traverse_flat_dst_iter(d, step);
};
// This is something that I just wish to be true!
flat_map(dirs, traverse) + map(files)
}
I want this function to deliver one long flat iterator of files, in the spirit of the Elixir solution. I just can't yet cope with the necessary return types and other syntax. I really hope to be clear enough this time.
What I managed to compile and run (meaningless, but the signature is what I actually want):
fn traverse_flat_dst_iter(
src_dir: &PathBuf,
dst_step: Vec<PathBuf>,
) -> impl Iterator<Item = (PathBuf, PathBuf)> {
let (dirs, files) = list_dir_groom(src_dir);
let _traverse = |d: &PathBuf| {
let mut step = dst_step.clone();
step.push(PathBuf::from(d.file_name().unwrap()));
traverse_flat_dst_iter(d, step)
};
files.into_iter().map(|f| (f, PathBuf::new()))
}
What I'm still lacking:
fn traverse_flat_dst_iter(
src_dir: &PathBuf,
dst_step: Vec<PathBuf>,
) -> impl Iterator<Item = (PathBuf, PathBuf)> {
let (dirs, files) = list_dir_groom(src_dir);
let traverse = |d: &PathBuf| {
let mut step = dst_step.clone();
step.push(PathBuf::from(d.file_name().unwrap()));
traverse_flat_dst_iter(d, step)
};
// Here is a combination amounting to an iterator,
// which delivers a (PathBuf, PathBuf) tuple on each step.
// Flat mapping with traverse, of course (see Elixir solution).
// Iterator must be as long as the number of files in the tree.
// The lines below look very close, but every possible type is mismatched :(
dirs.into_iter().flat_map(traverse)
.chain(files.into_iter().map(|f| (f, PathBuf::new())))
}
There are two approaches:
The first one is to use an existing crate, like walkdir. The benefit is it's being well tested and offers many options.
The second one is to write your own implementation of Iterator. Here's an example, and maybe the basis for your own:
struct FileIterator {
dirs: Vec<PathBuf>, // the dirs waiting to be read
files: Option<ReadDir>, // non recursive iterator over the currently read dir
}
impl From<&str> for FileIterator {
fn from(path: &str) -> Self {
FileIterator {
dirs: vec![PathBuf::from(path)],
files: None,
}
}
}
impl Iterator for FileIterator {
type Item = PathBuf;
fn next(&mut self) -> Option<PathBuf> {
loop {
while let Some(read_dir) = &mut self.files {
match read_dir.next() {
Some(Ok(entry)) => {
let path = entry.path();
if let Ok(md) = entry.metadata() {
if md.is_dir() {
self.dirs.push(path.clone());
continue;
}
}
return Some(path);
}
None => { // we consumed this directory
self.files = None;
break;
}
_ => { }
}
}
while let Some(dir) = self.dirs.pop() {
let read_dir = fs::read_dir(&dir);
if let Ok(files) = read_dir {
self.files = Some(files);
return Some(dir);
}
}
break; // no more files, no more dirs
}
return None;
}
}
playground
The advantage of writing your own iterator is that you'll tune it for your precise needs (sorting, filtering, error handling, etc.). But you'll have to deal with your own bugs.
This is the exact solution I sought. It's none of my achievement; see here. Comments are welcome.
fn traverse_flat_dst_iter(
src_dir: &PathBuf,
dst_step: Vec<PathBuf>,
) -> impl Iterator<Item = (PathBuf, PathBuf)> {
let (dirs, files) = list_dir_groom(src_dir);
let traverse = move |d: PathBuf| -> Box<dyn Iterator<Item = (PathBuf, PathBuf)>> {
let mut step = dst_step.clone();
step.push(PathBuf::from(d.file_name().unwrap()));
Box::new(traverse_flat_dst_iter(&d, step))
};
dirs.into_iter()
.flat_map(traverse)
.chain(files.into_iter().map(|f| (f, PathBuf::new())))
}
Another, more sophisticated take. One has to box things, clone parameters to be shared between lambdas, etc., to satisfy the compiler. Yet it works. Hopefully, on can get the hang of the thing.
fn traverse_dir(
src_dir: &PathBuf,
dst_step: Vec<PathBuf>,
) -> Box<dyn Iterator<Item = (PathBuf, Vec<PathBuf>)>> {
let (dirs, files) = groom(src_dir);
let destination_step = dst_step.clone(); // A clone for handle.
let traverse = move |d: PathBuf| {
let mut step = dst_step.clone();
step.push(PathBuf::from(d.file_name().unwrap()));
traverse_dir(&d, step)
};
let handle = move |f: PathBuf| (f, destination_step.clone());
if flag("r") {
// Chaining backwards.
Box::new(
files
.into_iter()
.map(handle)
.chain(dirs.into_iter().flat_map(traverse)),
)
} else {
Box::new(
dirs.into_iter()
.flat_map(traverse)
.chain(files.into_iter().map(handle)),
)
}
}
I'm trying to flatten a recursive structure but I'm having trouble with recursive iterators.
Here's what the struct looks like:
#[derive(Debug, Clone)]
pub struct C {
name: String,
vb: Option<Vec<B>>,
}
#[derive(Debug, Clone)]
pub struct B {
c: Option<C>,
}
#[derive(Debug, Clone)]
pub struct A {
vb: Option<Vec<B>>,
flat_c: Option<Vec<C>>,
}
My plan is to traverse the vb vector and flatten it into flat_c. I want it to look like this, or at least, be a Vec<String>:
Some([
C {
name: "foo",
vb: None,
},
C {
name: "bar",
vb: None,
},
C {
name: "fizz",
vb: None,
},
C {
name: "buzz",
vb: None,
},
])
Here what I managed to do, somewhat flattening the struct, but only for the last element, as the recursion is not implemented.
impl A {
fn flat_c(self) -> Self {
let fc: Vec<C> = self
.vb
.clone()
.unwrap()
.iter()
.flat_map(|x| x.c.as_ref().unwrap().vb.as_ref().unwrap().iter())
.cloned()
.map(|x| x.c.unwrap())
.collect();
Self {
flat_c: Some(fc),
..self
}
}
}
fn main() {
let a = A {
vb: Some(vec![
B {
c: Some(C {
name: "foo".to_string(),
vb: Some(vec![B {
c: Some(C {
name: "bar".to_string(),
vb: None,
}),
}]),
}),
},
B {
c: Some(C {
name: "fiz".to_string(),
vb: Some(vec![B {
c: Some(C {
name: "buzz".to_string(),
vb: None,
}),
}]),
}),
},
]),
flat_c: None,
};
let a = a.flat_c();
println!("a: {:#?}", a);
}
playground
The output for flat_c:
Some([
C {
name: "bar",
vb: None,
},
C {
name: "buzz",
vb: None,
},
])
I haven't dived into the Iterator trait implementation that might be required for this problem.
How would I tackle this problem? Maybe using a fold? Perhaps a recursive approach is not even needed? I'm at loss.
It's a good idea to be familiar with common data structures. You have a tree, and there are several ways to traverse a tree. You haven't precisely specified which method to use, so I chose one arbitrarily that's easy to implement.
The key here is to implement an iterator that keeps track of some state: all of the nodes yet to be visited. On each call to Iterator::next, we take the next value, save aside any new nodes to visit, and return the value.
Once you have the iterator, you can collect it into a Vec.
use std::collections::VecDeque;
impl IntoIterator for A {
type IntoIter = IntoIter;
type Item = String;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
remaining: self.vb.into_iter().flatten().collect(),
}
}
}
struct IntoIter {
remaining: VecDeque<B>,
}
impl Iterator for IntoIter {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.remaining.pop_front().and_then(|b| {
b.c.map(|C { name, vb }| {
self.remaining.extend(vb.into_iter().flatten());
name
})
})
}
}
fn to_strings(a: A) -> Vec<String> {
a.into_iter().collect()
}
#[derive(Debug, Clone)]
struct A {
vb: Option<Vec<B>>,
}
#[derive(Debug, Clone)]
struct B {
c: Option<C>,
}
#[derive(Debug, Clone)]
struct C {
name: String,
vb: Option<Vec<B>>,
}
fn main() {
let example: A = A {
vb: Some(vec![
B {
c: Some(C {
name: "Hello ".to_string(),
vb: None,
}),
},
B {
c: Some(C {
name: "World!".to_string(),
vb: None,
}),
},
]),
};
println!("The example struct: {:?}", example);
//clone a copy for a second example, because to_strings() takes ownership of the example A struct
let receipt: A = example.clone();
println!("Iterated: {:?}", to_strings(example));
// another example of using to_strings()
println!(
"As a string: {:?}",
to_strings(receipt).into_iter().collect::<String>()
);
}
From here, it should be straight-forward to create an iterator of B if that's what you need. Having all of the None values seemed silly, so I left them out and directly returned Strings.
I also made this a by-value iterator. You could follow the same pattern to create an iterator that returned references to the B / String and only clone them as needed.
See also:
How to implement Iterator and IntoIterator for a simple struct?
Implement IntoIterator for binary tree
Cannot obtain a mutable reference when iterating a recursive structure: cannot borrow as mutable more than once at a time
Recursive inorder traversal of a binary search tree
There is my solution:
impl C {
fn flat(&self) -> Vec<C> {
let mut result = Vec::new();
result.push(C {
name: self.name.clone(),
vb: None,
});
if self.vb.is_some() {
result.extend(
(self.vb.as_ref().unwrap().iter())
.flat_map(|b| b.c.as_ref().map(|c| c.flat()).unwrap_or(Vec::new())),
);
}
return result;
}
}
impl A {
fn flat_c(self) -> Self {
let fc = (self.vb.as_ref().unwrap().iter())
.flat_map(|b| b.c.as_ref().unwrap().flat())
.collect();
Self {
flat_c: Some(fc),
..self
}
}
}
It adds flat function for C because the C is the source of the recursion and only this struct may properly handle it.
Because of those Options it looks scary and there is hard to deal with cryptic error messages. This solution supposes that all b.cs of initial a is not a None. Otherwise, it will panic. My advice is to avoid using Option<Vec> and use just empty vector instead of None.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=09ea11342cdd733b03172c0fc13c85fd
I'm not sure what exactly you want the result of "traverse the vb vector and flatten it into flat_c" to be, but here's a slightly simpler example of flattening a recursive structure, using once for the value that corresponds to the current node, chain to concatenate it with its children and flat_map to flatten everything:
use std::iter::once;
#[derive(Debug)]
struct S {
name: String,
children: Vec<S>,
}
impl S {
fn flat(self) -> Vec<String> {
once(self.name)
.chain(self.children.into_iter().flat_map(|c| c.flat()))
.collect()
}
}
fn main() {
let s = S {
name: "parent".into(),
children: vec![
S {
name: "child 1".into(),
children: vec![],
},
S {
name: "child 2".into(),
children: vec![],
},
],
};
println!("s: {:?}", s);
println!("flat: {:?}", s.flat());
}
playground
I’m trying to transpile a dynamic language into Rust and closures are the most difficult part to implement.
I've tried using a Arc<Mutex<dyn FnMut>>, but it doesn't support recursion.
use std::sync::{Arc, Mutex};
type Data = Arc<DataUnpack>;
enum DataUnpack {
Number(f64),
Function(Box<Mutex<FnMut(Vec<Data>) -> Data>>),
}
fn call(f: Data, args: Vec<Data>) -> Data {
if let DataUnpack::Function(v) = &*f {
let f = &mut *v.lock().unwrap();
f(args)
} else {
panic!("TYPE ERR")
}
}
fn lambda(f: Box<FnMut(Vec<Data>) -> Data>) -> Data {
Arc::new(DataUnpack::Function(Box::new(Mutex::new(Box::leak(f)))))
}
fn main() {
let f: Arc<Mutex<Data>> = Arc::new(Mutex::new(Arc::new(DataUnpack::Number(0.0))));
*f.lock().unwrap() = {
let f = f.clone();
lambda(Box::new(move |xs| {
println!("Ha");
call(f.lock().unwrap().clone(), xs.clone())
}))
};
call(f.lock().unwrap().clone(), vec![]);
}
playground
It shows one Ha and then stops. Where am I wrong?
I want to build a tree using exactly two structs: Node and Tree and then recursively search for a target node from the tree. If a target is found, return true, else return false.
The challenge for me here is how to recursively call the find function, since it is only defined on Tree not Node.
pub struct Node<T> {
value: T,
left: Option<Box<Node<T>>>,
right: Option<Box<Node<T>>>,
}
pub struct Tree<T> {
root: Option<Box<Node<T>>>,
}
impl<T: Ord> Tree<T> {
/// Creates an empty tree
pub fn new() -> Self {
Tree { root: None }
}
// search the tree
pub fn find(&self, key: &T) -> bool {
let root_node = &self.root; // root is Option
match *root_node {
Some(ref node) => {
if node.value == *key {
return true;
}
let target_node = if *key < node.value {
&node.left
} else {
&node.right
};
match *target_node {
Some(sub_node) => sub_node.find(key),
None => {
return false;
}
}
}
None => return false,
}
}
}
fn main() {
let mut mytree: Tree<i32> = Tree::new();
let node1 = Node {
value: 100,
left: None,
right: None,
};
let boxed_node1 = Some(Box::new(node1));
let root = Node {
value: 200,
left: boxed_node1,
right: None,
};
let boxed_root = Some(Box::new(root));
let mytree = Tree { root: boxed_root };
let res = mytree.find(&100);
}
The current code reports the error:
error: no method named `find` found for type `Box<Node<T>>` in the current scope
--> src/main.rs:36:48
|
36 | Some(sub_node) => sub_node.find(key),
| ^^^^
|
= note: the method `find` exists but the following trait bounds were not satisfied: `Node<T> : std::iter::Iterator`
= help: items from traits can only be used if the trait is implemented and in scope; the following traits define an item `find`, perhaps you need to implement one of them:
= help: candidate #1: `std::iter::Iterator`
= help: candidate #2: `core::str::StrExt`
I understand that find is only implemented on Tree, so there is an error, but I don't think it is efficient to implement find on both Tree and Node. Any hint to solve this?
You need to move the majority of the implementation to the Node type, then leave only a small shim in Tree:
impl<T: Ord> Tree<T> {
pub fn find(&self, key: &T) -> bool {
self.root.as_ref().map(|n| n.find(key)).unwrap_or(false)
}
}
impl<T: Ord> Node<T> {
// search the tree
pub fn find(&self, key: &T) -> bool {
if self.value == *key {
return true;
}
let target_node = if *key < self.value {
&self.left
} else {
&self.right
};
target_node.as_ref().map(|n| n.find(key)).unwrap_or(false)
}
}
However, I might avoid multiple comparisons by just matching on the result:
pub fn find(&self, key: &T) -> bool {
use ::std::cmp::Ordering::*;
match self.value.cmp(key) {
Equal => true,
Less => self.left.as_ref().map(|n| n.find(key)).unwrap_or(false),
Greater => self.right.as_ref().map(|n| n.find(key)).unwrap_or(false),
}
}
Or
pub fn find(&self, key: &T) -> bool {
use ::std::cmp::Ordering::*;
let child = match self.value.cmp(key) {
Equal => return true,
Less => self.left.as_ref(),
Greater => self.right.as_ref(),
};
child.map(|n| n.find(key)).unwrap_or(false)
}
I found it is hard to understand target_node.as_ref().map(|n| n.find(key)).unwrap_or(false). I just started to learn the iterator. Is that possible to explain the long expression step by step?
Just follow the type signatures of each function:
self is a &Node<T>
&self.left / &self.right / target_node are a &Option<Box<Node<T>>>
Option::as_ref converts an &Option<T> to Option<&T>. Now we have Option<&Box<Node<T>>>.
Option::map applies a function (which may change the contained type) to the option if it is Some, otherwise it leaves it None.
The function we apply is Node::find, which takes a &Node<T> and returns a bool.
Box<T> implements Deref so any methods on T appear on Box<T>.
Automatic dereferencing allows us to treat &Box<T> as Box<T>.
Now we have Option<bool>
Option::unwrap_or returns the contained value if there is one, otherwise the fallback value provided. The final type is bool.
There is no usage of the Iterator trait. Both Iterator and Option have a map method. If you are interested in the fact that they have the same name and do similar things, that [is what people refer to as a monad. Understanding monads is interesting but not required to actually use them.
Implement the find method on Node and create a stub find method for Tree which could look like this:
impl<T: Ord> Tree<T> {
pub fn find(&self, key: &T) -> bool {
match self.root.as_ref() {
None => false,
Some(x) => x.find(key)
}
}
}
I understand that the preferred way to iterate in Rust is through the for var in (range) syntax, but sometimes I'd like to work on more than one of the elements in that range at a time.
From a Ruby perspective, I'm trying to find a way of doing (1..100).each_slice(5) do |this_slice| in Rust.
I'm trying things like
for mut segment_start in (segment_size..max_val).step_by(segment_size) {
let this_segment = segment_start..(segment_start + segment_size).iter().take(segment_size);
}
but I keep getting errors that suggest I'm barking up the wrong type tree. The docs aren't helpful either--they just don't contain this use case.
What's the Rust way to do this?
Use chunks (or chunks_mut if you need mutability):
fn main() {
let things = [5, 4, 3, 2, 1];
for slice in things.chunks(2) {
println!("{:?}", slice);
}
}
Outputs:
[5, 4]
[3, 2]
[1]
The easiest way to combine this with a Range would be to collect the range to a Vec first (which dereferences to a slice):
fn main() {
let things: Vec<_> = (1..100).collect();
for slice in things.chunks(5) {
println!("{:?}", slice);
}
}
Another solution that is pure-iterator would be to use Itertools::chunks_lazy:
extern crate itertools;
use itertools::Itertools;
fn main() {
for chunk in &(1..100).chunks_lazy(5) {
for val in chunk {
print!("{}, ", val);
}
println!("");
}
}
Which suggests a similar solution that only requires the standard library:
fn main() {
let mut range = (1..100).peekable();
while range.peek().is_some() {
for value in range.by_ref().take(5) {
print!("{}, ", value);
}
println!("");
}
}
One trick is that Ruby and Rust have different handling here, mostly centered around efficiency.
In Ruby Enumerable can create new arrays to stuff values in without worrying about ownership and return a new array each time (check with this_slice.object_id).
In Rust, allocating a new vector each time would be pretty unusual. Additionally, you can't easily return a reference to a vector that the iterator holds due to complicated lifetime concerns.
A solution that's very similar to Ruby's is:
fn main() {
let mut range = (1..100).peekable();
while range.peek().is_some() {
let chunk: Vec<_> = range.by_ref().take(5).collect();
println!("{:?}", chunk);
}
}
Which could be wrapped up in a new iterator that hides the details:
use std::iter::Peekable;
struct InefficientChunks<I>
where I: Iterator
{
iter: Peekable<I>,
size: usize,
}
impl<I> Iterator for InefficientChunks<I>
where I: Iterator
{
type Item = Vec<I::Item>;
fn next(&mut self) -> Option<Self::Item> {
if self.iter.peek().is_some() {
Some(self.iter.by_ref().take(self.size).collect())
} else {
None
}
}
}
trait Awesome: Iterator + Sized {
fn inefficient_chunks(self, size: usize) -> InefficientChunks<Self> {
InefficientChunks {
iter: self.peekable(),
size: size,
}
}
}
impl<I> Awesome for I where I: Iterator {}
fn main() {
for chunk in (1..100).inefficient_chunks(5) {
println!("{:?}", chunk);
}
}
Collecting into a vec can easily kill your performance. An approach similar to in the question is perfectly fine.
fn chunk_range(range: Range<usize>, chunk_size: usize) -> impl Iterator<Item=Range<usize>> {
range.clone().step_by(chunk_size).map(move |block_start| {
let block_end = (block_start + chunk_size).min(range.end);
block_start..block_end
})
}