Chaining iterators together to remove repeated code - rust

The following code attempts to chain two iterators together.
fn main() {
let height = 3;
let width = 4;
let horizontal = (0..height).map(|row| {let rw = row * width; rw..rw + width});
horizontal.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
let vertical = (0..width).map(|col| (0..height).map(move |n| col + n * width));
vertical.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
let all = horizontal.chain(vertical);
//all.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
}
But the compiler complains about mismatched types.
error[E0271]: type mismatch resolving `<Map<std::ops::Range<{integer}>, [closure#src/main.rs:6:35: 6:82]> as IntoIterator>::Item == std::ops::Range<{integer}>`
--> src/main.rs:8:26
|
8 | let all = horizontal.chain(vertical);
| ^^^^^ expected struct `Map`, found struct `std::ops::Range`
|
= note: expected type `Map<std::ops::Range<{integer}>, [closure#src/main.rs:6:57: 6:81]>`
found struct `std::ops::Range<{integer}>`
The signature of chain is:
fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter>ⓘ where
U: IntoIterator<Item = Self::Item>
Both iterators have as Item type an Iterator with the same Item type, which admittedly is not quite what the signature demands. But I can call for example .for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");}) on each iterator, so why can't I construct the chain to call it on the chain? Is there another way to remove such code duplication?

It's because your types doesn't have the same Item:
.map(|row| {let rw = row * width; rw..rw + width});
.map(|col| (0..height).map(move |n| col + n * width))
One is a Range the other is a Map of a Range.
The solution is to use flatten() or on your case flat_map():
fn main() {
let height = 3;
let width = 4;
println!("Horizontal:");
let horizontal = (0..height).flat_map(|row| {
let rw = row * width;
rw..rw + width
});
for x in horizontal.clone() {
println!("{:?}", x);
}
println!("\nVertical:");
let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
for x in vertical.clone() {
println!("{:?}", x);
}
println!("\nAll:");
let all = horizontal.chain(vertical);
for x in all {
println!("{:?}", x);
}
}
This made both vertical and horizontal iterator have the same Item type. Also, I remove for_each() in my opinion it's make the code unclear as for loop are for side effect that is imperative paradigm and iterator chaining is functional paradigm.
Bonus:
fn print_my_iter(name: &str, iter: impl Iterator<Item = i32>) {
println!("{}:", name);
for x in iter {
println!("{:?}", x);
}
}
fn main() {
let height = 3;
let width = 4;
let horizontal = (0..height).flat_map(|row| {
let rw = row * width;
rw..rw + width
});
print_my_iter("Horizontal", horizontal.clone());
let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
print_my_iter("\nVertical", vertical.clone());
let all = horizontal.chain(vertical);
print_my_iter("\nAll", all);
}
Ultra bonus:
use std::io::{self, Write};
fn print_my_iter(name: &str, iter: impl Iterator<Item = i32>) -> Result<(), io::Error> {
let stdout = io::stdout();
let mut handle = stdout.lock();
writeln!(handle, "{}:", name)?;
iter.map(|x| writeln!(handle, "{:?}", x)).collect()
}
fn main() -> Result<(), io::Error> {
let height = 3;
let width = 4;
let horizontal = (0..height).flat_map(|row| {
let rw = row * width;
rw..rw + width
});
print_my_iter("Horizontal", horizontal.clone())?;
let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
print_my_iter("\nVertical", vertical.clone())?;
let all = horizontal.chain(vertical);
print_my_iter("\nAll", all)?;
Ok(())
}

Related

How to properly initialize a struct in Rust, with good enough encapsulations?

How to properly initialize a struct in Rust, with good enough encapsulations?
Or more naively:
how to leverage object/instance methods in the initialization/constructing process of structs?
For example, as the initialization block in Kotlin:
private class BinaryIndexedTree(nums: IntArray) {
private val nNums = nums.size
private val fenwick = IntArray(nNums + 1) { 0 }
// where to put this block in Rust?
init {
for (idx in nums.indices) {
update(idx, nums[idx])
}
}
fun update(index: Int, value: Int) {
var idx = index + 1
while (idx <= nNums) {
fenwick[idx] += value
idx += (idx and -idx)
}
}
fun query(index: Int): Int {
var sum = 0
var idx = index + 1
while (idx > 0) {
sum += fenwick[idx]
idx -= (idx and -idx)
}
return sum
}
}
According to Rust Design Patterns, there is no regular constructors as other languages, the convention is to use an associated function.
Correspondingly, in Rust:
struct BinaryIndexedTree{
len_ns: isize,
fenwick: Vec<i32>,
}
impl BinaryIndexedTree{
pub fn new(nums: &Vec<i32>) -> Self{
let len_ns: usize = nums.len();
let fenwick: Vec<i32> = vec![0; len_ns + 1];
for (idx, num) in nums.iter().enumerate(){
// how to leverage `update()` for initialization
// update(idx as isize, num);
// or even earlier: where/how to put the initialization logic?
}
Self{
len_ns: len_ns as isize,
fenwick,
}
}
pub fn update(&mut self, index: isize, value: i32){
let mut idx = index + 1;
while idx <= self.len_ns{
self.fenwick[idx as usize] += value;
idx += (idx & -idx);
}
}
pub fn query(&self, index: isize) -> i32{
let mut sum: i32 = 0;
let mut idx = index + 1;
while idx > 0{
sum += self.fenwick[idx as usize];
idx -= (idx & -idx);
}
sum
}
}
Is there any way to properly leverage the update method?
As a rule of thumbs, how to properly handle the initialization work after the creation of (all the fields of) the struct?
The builder pattern is a way to go, which introduces much more code just for initialization.
Yes, you can construct the struct then call a function on it before returning it. There is nothing special about the new function name or how the struct is constructed at the end of the function.
pub fn new(nums: &Vec<i32>) -> Self {
let len_ns: usize = nums.len();
let fenwick: Vec<i32> = vec![0; len_ns + 1];
// Construct an incomplete version of the struct.
let mut new_self = Self {
len_ns: len_ns as isize,
fenwick,
};
// Do stuff with the struct
for (idx, num) in nums.iter().enumerate(){
new_self.update(idx as isize, num);
}
// Return it
new_self
}

How to declare generic types for a function that computes k-shortest-paths using Yen's algorithm and petgraph?

I have implemented Yen's algorithm Wikipedia using petgraph in Rust.
In a main function, the code looks like this:
use std::collections::BinaryHeap;
use std::cmp::Reverse;
use std::collections::HashSet;
use petgraph::{Graph, Undirected};
use petgraph::graph::NodeIndex;
use petgraph::stable_graph::StableUnGraph;
use petgraph::algo::{astar};
use petgraph::visit::NodeRef;
fn main() {
let mut graph: Graph<String, u32, Undirected> = Graph::new_undirected();
let c = graph.add_node(String::from("C"));
let d = graph.add_node(String::from("D"));
let e = graph.add_node(String::from("E"));
let f = graph.add_node(String::from("F"));
let g = graph.add_node(String::from("G"));
let h = graph.add_node(String::from("H"));
graph.add_edge(c, d, 3);
graph.add_edge(c, e, 2);
graph.add_edge(d, e, 1);
graph.add_edge(d, f, 4);
graph.add_edge(e, f, 2);
graph.add_edge(e, g, 3);
graph.add_edge(f, g, 2);
graph.add_edge(f, h, 1);
graph.add_edge(g, h, 2);
let start = c;
let goal = h;
// start solving Yen's k-shortest-paths
let (length, path) = match astar(&graph, start, |n| n == goal.unwrap(), |e| *e.weight(), |_| 0) {
Some(x) => x,
None => panic!("Testing!"),
};
println!("Initial path found\tlength: {}", length);
for i in 0..(path.len() - 1) {
println!("\t{:?}({:?}) -> {:?}({:?})", graph.node_weight(path[i].id()).unwrap(), path[i].id(), graph.node_weight(path[i+1].id()).unwrap(), path[i+1].id());
}
let k = 10;
let mut ki = 0;
let mut visited = HashSet::new();
let mut routes = vec![(length, path)];
let mut k_routes = BinaryHeap::new();
for ki in 0..(k - 1) {
println!("Computing path {}", ki);
if routes.len() <= ki {
// We have no more routes to explore
break;
}
let previous = routes[ki].1.clone();
for i in 0..(previous.len() - 1) {
let spur_node = previous[i].clone();
let root_path = &previous[0..i];
let mut graph_copy = StableUnGraph::<String, u32>::from(graph.clone());
println!("\tComputing pass {}\tspur: {:?}\troot: {:?}", i, graph.node_weight(spur_node), root_path.iter().map(|n| graph.node_weight(*n).unwrap()));
for (_, path) in &routes {
if path.len() > i + 1 && &path[0..i] == root_path {
let ei = graph.find_edge_undirected(path[i], path[i + 1]);
if ei.is_some() {
let edge = ei.unwrap().0;
graph_copy.remove_edge(edge);
let edge_obj = graph.edge_endpoints(edge);
let ns = edge_obj.unwrap();
println!("\t\tRemoving edge {:?} from {:?} -> {:?}", edge, graph.node_weight(ns.0).unwrap(), graph.node_weight(ns.1).unwrap());
}
else {
panic!("\t\tProblem finding edge");
}
}
}
if let Some((_, spur_path)) =
astar(&graph_copy, spur_node, |n| n == goal.unwrap(), |e| *e.weight(), |_| 0)
{
let nodes: Vec<NodeIndex> = root_path.iter().cloned().chain(spur_path).collect();
let mut node_names = vec![];
for ni in 0..nodes.len() {
node_names.push(graph.node_weight(nodes[ni]).unwrap());
}
// compute root_path length
let mut path_length = 0;
for i_rp in 0..(nodes.len() - 1) {
let ei = graph.find_edge_undirected(nodes[i_rp], nodes[i_rp + 1]);
if ei.is_some() {
let ew = graph.edge_weight(ei.unwrap().0);
if ew.is_some() {
path_length += ew.unwrap();
}
}
}
println!("\t\t\tfound path: {:?} with cost {}", node_names, path_length);
if !visited.contains(&nodes) {
// Mark as visited
visited.insert(nodes.clone());
// Build a min-heap
k_routes.push(Reverse((path_length, nodes)));
}
}
}
if let Some(k_route) = k_routes.pop() {
println!("\tselected route {:?}", k_route.0);
routes.push(k_route.0);
}
}
}
Now, I want to put this algorithm within a function that I can call from my code. I made an initial attempt with the signature like this:
pub fn yen_k_shortest_paths<G, E, Ty, Ix, F, K>(
graph: Graph<String, u32, Undirected>,
start: NodeIndex<u32>,
goal: NodeIndex<u32>,
mut edge_cost: F,
k: usize,
) -> Result<Vec<(u32, Vec<NodeIndex<u32>>)>, Box<dyn std::error::Error>>
where
G: IntoEdges + Visitable,
Ty: EdgeType,
Ix: IndexType,
E: Default + Debug + std::ops::Add,
F: FnMut(G::EdgeRef) -> K,
K: Measure + Copy,
{
// implementation here
}
However, when I try to call the function with:
let paths = yen::yen_k_shortest_paths(graph, start, goal, |e: EdgeReference<u32>| *e.weight(), 5);
the compiler complains: type annotations needed cannot satisfy <_ as IntoEdgeReferences>::EdgeRef == petgraph::graph::EdgeReference<'_, u32>`
I already tried several alternatives without success. Do you have any suggestion on how to fix this issue?
The issue with the yen_k_shortest_paths() function signature as written is the generic type parameters aren't used correctly. As an example, consider the first declared type parameter on yen_k_shortest_paths(): G, which is intended to represent the graph type. Declaring G like this means that the code that calls yen_k_shortest_paths() gets to pick the graph type G. But the graph argument is declared with the concrete type Graph<String, u32, Undirected>—the caller has no choice. This contradiction is the problem with G. Similar reasoning applies to the other type parameters, except F and K. There are two ways to fix this kind of issue:
Keep the graph argument as Graph<String, u32, Undirected> and remove the G type parameter.
Change the graph argument to take a G.
Approach #1 is simpler but your function won't be as general. Approach #2 can involve needing to add extra bounds and some code changes in the function in order for the code to compile.
In this case, the simplest approach doesn't need any type parameters at all:
fn yen_k_shortest_paths(
graph: &Graph<String, u32, Undirected>,
start: NodeIndex<u32>,
goal: NodeIndex<u32>,
edge_cost: fn(EdgeReference<u32>) -> u32,
k: usize,
) -> Vec<(u32, Vec<NodeIndex<u32>>)> {...}
Here's the full code, which can be run:
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::collections::HashSet;
use petgraph::algo::astar;
use petgraph::graph::{EdgeReference, NodeIndex};
use petgraph::stable_graph::StableUnGraph;
use petgraph::visit::NodeRef;
use petgraph::{Graph, Undirected};
fn main() {
let mut graph: Graph<String, u32, Undirected> = Graph::new_undirected();
let c = graph.add_node(String::from("C"));
let d = graph.add_node(String::from("D"));
let e = graph.add_node(String::from("E"));
let f = graph.add_node(String::from("F"));
let g = graph.add_node(String::from("G"));
let h = graph.add_node(String::from("H"));
graph.add_edge(c, d, 3);
graph.add_edge(c, e, 2);
graph.add_edge(d, e, 1);
graph.add_edge(d, f, 4);
graph.add_edge(e, f, 2);
graph.add_edge(e, g, 3);
graph.add_edge(f, g, 2);
graph.add_edge(f, h, 1);
graph.add_edge(g, h, 2);
let start = c;
let goal = h;
let edge_cost = |e: EdgeReference<u32>| *e.weight();
let k = 10;
let _paths = yen_k_shortest_paths(&graph, start, goal, edge_cost, k);
}
fn yen_k_shortest_paths(
graph: &Graph<String, u32, Undirected>,
start: NodeIndex<u32>,
goal: NodeIndex<u32>,
edge_cost: fn(EdgeReference<u32>) -> u32,
k: usize,
) -> Vec<(u32, Vec<NodeIndex<u32>>)> {
let (length, path) = match astar(graph, start, |n| n == goal, edge_cost, |_| 0) {
Some(x) => x,
None => panic!("Testing!"),
};
println!("Initial path found\tlength: {}", length);
for i in 0..(path.len() - 1) {
println!(
"\t{:?}({:?}) -> {:?}({:?})",
graph.node_weight(path[i].id()).unwrap(),
path[i].id(),
graph.node_weight(path[i + 1].id()).unwrap(),
path[i + 1].id()
);
}
let mut visited = HashSet::new();
let mut routes = vec![(length, path)];
let mut k_routes = BinaryHeap::new();
for ki in 0..(k - 1) {
println!("Computing path {}", ki);
if routes.len() <= ki {
// We have no more routes to explore
break;
}
let previous = routes[ki].1.clone();
for i in 0..(previous.len() - 1) {
let spur_node = previous[i];
let root_path = &previous[0..i];
let mut graph_copy = StableUnGraph::from(graph.clone());
println!(
"\tComputing pass {}\tspur: {:?}\troot: {:?}",
i,
graph.node_weight(spur_node),
root_path
.iter()
.map(|n| graph.node_weight(*n).unwrap())
.collect::<Vec<_>>()
);
for (_, path) in &routes {
if path.len() > i + 1 && &path[0..i] == root_path {
let ei = graph.find_edge_undirected(path[i], path[i + 1]);
if let Some(ei) = ei {
let edge = ei.0;
graph_copy.remove_edge(edge);
let edge_obj = graph.edge_endpoints(edge);
let ns = edge_obj.unwrap();
println!(
"\t\tRemoving edge {:?} from {:?} -> {:?}",
edge,
graph.node_weight(ns.0).unwrap(),
graph.node_weight(ns.1).unwrap()
);
} else {
panic!("\t\tProblem finding edge");
}
}
}
if let Some((_, spur_path)) = astar(
&graph_copy,
spur_node,
|n| n == goal,
|e| *e.weight(),
|_| 0,
) {
let nodes: Vec<NodeIndex> = root_path.iter().cloned().chain(spur_path).collect();
let mut node_names = vec![];
for &node in &nodes {
node_names.push(graph.node_weight(node).unwrap());
}
// compute root_path length
let mut path_length = 0;
for i_rp in 0..(nodes.len() - 1) {
let ei = graph.find_edge_undirected(nodes[i_rp], nodes[i_rp + 1]);
if let Some(ei) = ei {
let ew = graph.edge_weight(ei.0);
if let Some(&ew) = ew {
path_length += ew;
}
}
}
println!(
"\t\t\tfound path: {:?} with cost {}",
node_names, path_length
);
if !visited.contains(&nodes) {
// Mark as visited
visited.insert(nodes.clone());
// Build a min-heap
k_routes.push(Reverse((path_length, nodes)));
}
}
}
if let Some(k_route) = k_routes.pop() {
println!("\tselected route {:?}", k_route.0);
routes.push(k_route.0);
}
}
routes
}
As another example of a possible function signature, this one is generic over the node type N and the edge cost function F:
fn yen_k_shortest_paths<'a, N, F>(
graph: &'a Graph<N, u32, Undirected>,
start: NodeIndex<u32>,
goal: NodeIndex<u32>,
edge_cost: F,
k: usize,
) -> Vec<(u32, Vec<NodeIndex<u32>>)>
where
&'a Graph<N, u32, Undirected>:
GraphBase<NodeId = NodeIndex<u32>> + IntoEdgeReferences<EdgeRef = EdgeReference<'a, u32>>,
N: Debug + Clone,
F: FnMut(EdgeReference<u32>) -> u32,
{...}
As you can see, these bounds can get pretty complicated. Figuring them out involved reading the error messages the compiler emitted, as well as reading the docs for the involved types/traits. (Although, I think in this case the complicated bound &'a Graph<N, u32, Undirected>: GraphBase<NodeId = NodeIndex<u32>> + IntoEdgeReferences<EdgeRef = EdgeReference<'a, u32>> should be inferred, but currently isn't due to a complier bug/limitation)

Use map object as function input

I have a function that calculates the variance of an iterator of floats, I would like to be able to call this function on an iterator after using the map method to transform it.
use num::Float;
fn compute_var_iter<'a, I, T>(vals: I) -> T
where
I: Iterator<Item = &'a T>,
T: 'a + Float + std::ops::AddAssign,
{
// online variance function
// Var = E[X^2] - E[X]^2
// corrects for 1/n -> 1/(n-1)
let mut x = T::zero();
let mut xsquare = T::zero();
let mut len = T::zero();
for &val in vals {
x += val;
xsquare += val * val;
len += T::one();
}
((xsquare / len) - (x / len) * (x / len)) / (len - T::one()) * len
}
fn main() {
let a: Vec<f64> = (1..100001).map(|i| i as f64).collect();
let b: Vec<f64> = (0..100000).map(|i| i as f64).collect();
dbg!(compute_var_iter(&mut a.iter())); // this works
dbg!(compute_var_iter(a.iter().zip(b).map(|(x, y)| x * y))); // this does not work
}
Is there a performant way to get the map output back to an iterator or to make the function take the map object as an input so that we can avoid having to .collect() and keep the execution lazy?
You can use the iterator objects directly without collect:
use num::Float;
fn compute_var_iter<I, T>(vals: I) -> T
where
I: Iterator<Item = T>,
T: Float + std::ops::AddAssign,
{
// online variance function
// Var = E[X^2] - E[X]^2
// corrects for 1/n -> 1/(n-1)
let mut x = T::zero();
let mut xsquare = T::zero();
let mut len = T::zero();
for val in vals {
x += val;
xsquare += val * val;
len += T::one();
}
((xsquare / len) - (x / len) * (x / len)) / (len - T::one()) * len
}
fn main() {
let a = (1..100001).map(|i| i as f64);
let b = (0..100000).map(|i| i as f64);
let c: Vec<f64> = (0..10000).map(|i| i as f64).collect();
dbg!(compute_var_iter(a.clone())); // this works
dbg!(compute_var_iter(c.iter().map(|i| *i))); // this works
dbg!(compute_var_iter(a.zip(b).map(|(x, y)| x * y)));
}
Playground
Notice that you would need to clone the iterator if you intend to use it several times. Also you do not really need to use references since numbers are usually Copy and the cost is the same as creating the references itself.

"Cannot infer type - type annotations needed", but the type is already known

I've been practicing on LC where I hit the following issue.
pub fn largest_rectangle_area(heights: Vec<i32>) -> i32 {
let mut widths = vec![(0usize, heights.len()); heights.len()];
let mut stack = vec![];
for (idx, &x) in heights.iter().enumerate() {
while let Some(&pos) = stack.last() {
if x >= heights[pos] {
break;
}
widths[pos].1 = idx;
stack.pop();
}
stack.push(idx);
}
todo!()
}
error[E0282]: type annotations needed
--> src/lib.rs:11:13
|
11 | widths[pos].1 = idx;
| ^^^^^^^^^^^ cannot infer type
|
= note: type must be known at this point
What's even stranger is that if I do a widths[pos] = (0, idx); first, then the error disappears:
pub fn largest_rectangle_area(heights: Vec<i32>) -> i32 {
let mut widths = vec![(0, heights.len()); heights.len()];
let mut stack = vec![];
for (idx, &x) in heights.iter().enumerate() {
while let Some(&pos) = stack.last() {
if x >= heights[pos] {
break;
}
widths[pos] = (0, idx); // do a full update
stack.pop();
}
stack.push(idx);
}
stack.clear();
for (idx, &x) in heights.iter().enumerate().rev() {
while let Some(&pos) = stack.last() {
if x >= heights[pos] {
break;
}
widths[pos].0 = idx + 1; // now it's fine to use `.0`
stack.pop();
}
stack.push(idx);
}
todo!()
}
Am I missing something, or is this a bug in the compiler ?
Update
# Ömer Erden discovered, that it fails to infer the type of pos which should be usize as it's coming from enumerate() indirectly.
It seems pretty strange because it fails to infer the type of pos when doing widths[pos].1 = idx;, but succeeds when doing widths[pos] = (0, idx);
He also managed to create more minimal MCVE:
fn _some_fn() {
let mut widths = vec![(0usize, 0usize); 100];
let mut stack = vec![];
//let mut stack:Vec<usize> = vec![]; //why explicit type definition needed
let a = stack.pop().unwrap();
let idx = 0usize;
widths[a].1 = idx;
stack.push(idx);
}
Bug Report
https://github.com/rust-lang/rust/issues/90252
I dont know why the compiler is unable to elide the type, but you can manually annotate it with some easy changes:
pub fn largest_rectangle_area(heights: Vec<i32>) -> i32 {
let mut widths = vec![(0usize, heights.len()); heights.len()];
let mut stack = vec![];
for (idx, &x) in heights.iter().enumerate() {
while let Some(&pos) = stack.last() {
if x >= heights[pos] {
break;
}
let at_pos: &mut (usize, usize) = &mut widths[pos];
at_pos.1 = idx;
stack.pop();
}
stack.push(idx);
}
todo!()
}
Playground

How to write a macro that splits a byte into a tuple of bits of user-specified count?

I would like to have macro splitting one byte into tuple with 2-8 u8 parts using bitreader crate.
I managed to achieve that by following code:
use bitreader::BitReader;
trait Tupleprepend<T> {
type ResultType;
fn prepend(self, t: T) -> Self::ResultType;
}
macro_rules! impl_tuple_prepend {
( () ) => {};
( ( $t0:ident $(, $types:ident)* ) ) => {
impl<$t0, $($types,)* T> Tupleprepend<T> for ($t0, $($types,)*) {
type ResultType = (T, $t0, $($types,)*);
fn prepend(self, t: T) -> Self::ResultType {
let ($t0, $($types,)*) = self;
(t, $t0, $($types,)*)
}
}
impl_tuple_prepend! { ($($types),*) }
};
}
impl_tuple_prepend! {
(_1, _2, _3, _4, _5, _6, _7, _8)
}
macro_rules! split_byte (
($reader:ident, $bytes:expr, $count:expr) => {{
($reader.read_u8($count).unwrap(),)
}};
($reader:ident, $bytes:expr, $count:expr, $($next_counts:expr),+) => {{
let head = split_byte!($reader, $bytes, $count);
let tail = split_byte!($reader, $bytes, $($next_counts),+);
tail.prepend(head.0)
}};
($bytes:expr $(, $count:expr)* ) => {{
let mut reader = BitReader::new($bytes);
split_byte!(reader, $bytes $(, $count)+)
}};
);
Now I can use this code as I would like to:
let buf: &[u8] = &[0x72];
let (bit1, bit2, bits3to8) = split_byte!(&buf, 1, 1, 6);
Is there a way to avoid using Tupleprepend trait and create only 1 tuple instead of 8 in the worst scenario?
Because the number of bit widths directly corresponds to the number of returned values, I'd solve the problem using generics and arrays instead. The macro only exists to remove the typing of the [], which I don't really think is worth it.
fn split_byte<A>(b: u8, bit_widths: A) -> A
where
A: Default + std::ops::IndexMut<usize, Output = u8>,
for<'a> &'a A: IntoIterator<Item = &'a u8>,
{
let mut result = A::default();
let mut start = 0;
for (idx, &width) in bit_widths.into_iter().enumerate() {
let shifted = b >> (8 - width - start);
let mask = (0..width).fold(0, |a, _| (a << 1) | 1);
result[idx] = shifted & mask;
start += width;
}
result
}
macro_rules! split_byte {
($b:expr, $($w:expr),+) => (split_byte($b, [$($w),+]));
}
fn main() {
let [bit1, bit2, bits3_to_8] = split_byte!(0b1010_1010, 1, 1, 6);
assert_eq!(bit1, 0b1);
assert_eq!(bit2, 0b0);
assert_eq!(bits3_to_8, 0b10_1010);
}
See also:
How does for<> syntax differ from a regular lifetime bound?
How to write a trait bound for adding two references of a generic type?
How do I write the lifetimes for references in a type constraint when one of them is a local reference?
If it's ok to target nightly Rust, I'd use the unstable min_const_generics feature:
#![feature(min_const_generics)]
fn split_byte<const N: usize>(b: u8, bit_widths: [u8; N]) -> [u8; N] {
let mut result = [0; N];
let mut start = 0;
for (idx, &width) in bit_widths.iter().enumerate() {
let shifted = b >> (8 - width - start);
let mask = (0..width).fold(0, |a, _| (a << 1) | 1);
result[idx] = shifted & mask;
start += width;
}
result
}
macro_rules! split_byte {
($b:expr, $($w:expr),+) => (split_byte($b, [$($w),+]));
}
fn main() {
let [bit1, bit2, bits3_to_8] = split_byte!(0b1010_1010, 1, 1, 6);
assert_eq!(bit1, 0b1);
assert_eq!(bit2, 0b0);
assert_eq!(bits3_to_8, 0b10_1010);
}
See also:
Is it possible to control the size of an array using the type parameter of a generic?

Resources