Hello i try to implement fill array macro in Rust! And i have a problem!
My macro code:
macro_rules! fill_tensor {
($elem:expr; ($dim:expr, $($dims:expr),+)) => {
vec![fill_tensor!($elem; ($($dims),+)); $dim]
};
($elem:expr; $dim:expr) => {
vec![$elem; $dim]
};
}
When i call my macro like this
fill_tensor!(4; (3_usize, 3_usize))
All works correctly (output is 3 by 3 matrix)
But when i call this macro like this
let shape: (usize, usize) = (3_usize, 3_usize)
fill_tensor!(4; shape)
I got error
rustc: mismatched typesexpected type usize found tuple (usize, usize)
And my question is Why?
Link To Reproduce problem (Rust Playground)
I i try to see hows $dim
looks in this block of code
($elem:expr; $dim:expr) => {
vec![$elem; $dim]
};
I make tiny change in code
macro_rules! fill_tensor {
($elem:expr; ($dim:expr, $($dims:expr),+)) => {
vec![fill_tensor!($elem; ($($dims),+)); $dim]
};
($elem:expr; $dim:expr) => {
assert_eq!($shape, (3_usize, 3_usize))
};
}
And i have no error assertion correct
My final question is Why this pattern $elem:expr; ($dim:expr, $($dims:expr),+)
match to (3_usize, 3_usize)
but not match to this value in variable? And how to fix it?
Macros are, at their base, text- (more precisely, tokens-) based substitution. They can't see through variables, or inspect their types.
If you want to support variables, you will have to switch to a type-system-based solution. It may look like the following:
pub trait FillTensor<Elem> {
type Output: Clone;
fn fill_tensor(self, elem: Elem) -> Self::Output;
}
impl<Elem: Clone> FillTensor<Elem> for usize {
type Output = Vec<Elem>;
fn fill_tensor(self, elem: Elem) -> Self::Output {
vec![elem; self]
}
}
impl<Inner: FillTensor<Elem>, Elem: Clone> FillTensor<Elem> for (usize, Inner) {
type Output = Vec<Inner::Output>;
fn fill_tensor(self, elem: Elem) -> Self::Output {
vec![self.1.fill_tensor(elem); self.0]
}
}
The usage is like FillTensor::fill_tensor((3, (3, 3)), 4)
.
A macro can still make this a tad nicer:
macro_rules! fill_tensor {
// A helper to translate `(dim1, dim2, dim3, ...)` to `(dim1, (dim2, (dim3, ...)))`.
(@construct_dimension ($dim:expr $(, $dims:expr)+ $(,)?)) => {
($dim, fill_tensor!(@construct_dimension ($($dims),+)))
};
(@construct_dimension $dim:expr) => {
$dim
};
($elem:expr; $($dim:tt)+) => {
FillTensor::fill_tensor(
fill_tensor!(@construct_dimension $($dim)+),
$elem,
)
};
}
The tuple flattening can also be done entirely in the type-system with a bit more complicated trait implementations (this won't allow the custom syntax, though).