Macro to create N dimesional Vector

Hello lovely Rust community,

I'm trying a toy project, recreating numpy in Rust, to help me learn Rust.

I've been trying to work out a way of generalising a Vec to N dimesions.
A function or macro that can generate an Vec of Vecs, to a depth of N.

My current implementation is only made up of functions and an enum going to 4D. However, I very quickly found this made my code huge and repeated.

pub enum ArrayData {
    OneD(Vec<f64>),
    TwoD(Vec<Vec<f64>>),
    ThreeD(Vec<Vec<Vec<f64>>>),
    FourD(Vec<Vec<Vec<Vec<f64>>>>),
}

impl ArrayData {
    fn iter(&self) -> ArrayData {
        match self {
            Self::OneD(arg0) => ArrayData::OneD(arg0.iter().cloned().collect()),
            Self::TwoD(arg0) => ArrayData::TwoD(
                arg0.iter()
                    .map(|arg1| arg1.iter().cloned().collect())
                    .collect(),
            ),
            Self::ThreeD(arg0) => ArrayData::ThreeD(
                arg0.iter()
                    .map(|arg1| {
                        arg1.iter()
                            .map(|arg2| arg2.iter().cloned().collect())
                            .collect()
                    })
                    .collect(),
            ),
            Self::FourD(arg0) => ArrayData::FourD(
                arg0.iter()
                    .map(|arg1| {
                        arg1.iter()
                            .map(|arg2| {
                                arg2.iter()
                                    .map(|arg3| arg3.iter().cloned().collect())
                                    .collect()
                            })
                            .collect()
                    })
                    .collect(),
            ),
        }
    }
}

I then worked out I could use functions that take functions, so that this matching mess would only be in one place. For writing functions like Adding two Arrays together.
However, this still struck me as a hacky solution. Especially as it doesn't work for N dimesions, e.g. N = 1000.

I have been investigating Macros and this looks like the perfect use case. However, no matter what I try I can't get it working.

I've tried recursive macros, but always seem to hit the recursion limit, e.g.
which hits the recursion limit, even though, it can't recurse with the if statement (likely a problem with my limited understand of macros)

macro_rules! resursive {
    ( $v:expr, $s:expr) => {
        if ($s < 0) | ($s > 10) {
            vec![$v]
        } else {
            resursive![vec![$v], $s - 1]
        };
    };
}

The best I've been able to get is similar to the vec! macro

macro_rules! mac1 {
    ( $( $x:expr ),* ) => {
        {
            let temp_vec= vec![0];
            $(
                $x;
                let temp_vec = vec![temp_vec];
            )*
            temp_vec
        }
    };
}

using syntax like mac1[1, 1, 1, 1] and getting a nested vec of vecs [[[[[[0]]]]]].

I can't come up with a way of looping N number of times, with syntax like mac1[4] being the same as mac1[1, 1, 1, 1]. My instinct says this requires using tt instead of expr but that is waaaaay beyond me at the moment.

Any ideas on how to do this?

Oh, it absolutely does. Macros don't (can't) evaluate the generated code. You can't call a macro conditionally based on an if statement. A macro doesn't care what it expands to; it merely performs syntactic substitution, so you have to encode the termination condition as a purely syntactic construct. In practice, this means you would have two cases for the macro invocation to match: one for the base case, and another one for the recursive part.

I.e., your current attempt expands to something like

if condition {
    stuff
} else {
    if condition {
        /* same macro contents */
    } else {
        /* etc /*
    }
}

This results in code of infinite length. Obviously, if one could generate and evaluate infinite amounts of code, it could "do the right thing", i.e., it might encode finite computation, but that doesn't help if the syntax itself is infinite.

1 Like

Nested Vecs are a very poor data structure for multidimensional array data, because of the multiple indirections to access any element, and the potential for "raggedness" (unequal lengths). Consider using a single Vec together with length-of-dimensions data to compute the indices.

4 Likes

Aw okay, that makes sense.
That clears up one of my big misunderstanding about Macros.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.