Use of array as data structure

I am working on a Rust project. One part of it is based on an algorithm originally written in Matlab. In this Matlab code, arrays are used as data structures. All arrays have fixed length, and each element inside the array represents a specific property (i.e., arrays are state vectors, and thus each element is a state variable). I took the easy route, and basically translated the code from Matlab to Rust almost one-to-one. In an extremely simplified way, the Rust code looks something like this:

const X_A: usize = 0;
const X_B: usize = 1;
const X_LEN: usize = 2;

pub fn algorithm(x: &mut [f64; X_LEN], // State vector
) {
    x[X_A] = do_stuff_to_A();
    x[X_B] = do_stuff_to_B();
    do_linear_algebra_stuff(x);
}

In some cases I need access to each element of x, in other cases I want x to behave as a vector (in the linear algebra sense). In the former cases, a structure would make more sense; in the latter, perhaps is best to use an array as done so far. Alternatively, I could use a struct that impl a conversion to an array.

My questions are: is the code above consider bad style or non-idiomatic? More specifically, what are the main disadvantages (if any) of keeping the Rust code like it is? Is there a better way (e.g., using struct, Vec, enum, etc. instead)?

There's nothing wrong with the code you've included but it's hard to tell whether your code is idiomatic or not without more information. What are the kinds of functions you call on your vector? What do you do with the individual elements?

Vecs and enums are probaby not what you want in this situation. But whether or not a wrapper struct is needed really depends on context. How often are you using this kind of array? How often are you using it as just a list of numbers vs as a vector? Are do_stuff_to_A and do_stuff_to_B always called together?

If you are just starting out with Rust, I wouldn't worry too much about trying to make the code idiomatic. Just write the code in a way that feels natural to you and when you come across any pain points, you can try to look for idiomatic solutions to those problems so you have a well defined problem to solve.

Thanks for the reply. To answer some of your questions:
The general context of the algorithm is for control of a dynamical system.
There are several such vectors besides the state vector x (e.g., the control input vector, the disturbance vector) whose sizes range from 1 element up to 10 elements. These vectors are used in different places (as &mut [f64;N] and as & [f64;N]), sometimes together, sometimes individually. The elements of each vector may be computed individually (like in the code snippet), and sometimes together as a vector using some linear algebra computation (e.g. using nalgebra or ndarray). During simulation, I also store the values of these vectors at different points in time on a 2-D array for later plotting the time evolution of each individual element (e.g., x[X_A] vs time).

I would not call my self a Rust beginner, but certainly I have still a lot to learn. Specially on how to best organize the code/data. My programming background is on C, Matlab / Python with numpy. Hence, the current organization of the data feels okay to me, but I thought maybe there are unforeseen consequences of doing it this way in Rust, and thus my questions.

I think your approach is fine. You could use the bytemuck crate and define a structured alias

#[derive(Pod, Zeroable)]
#[repr(C)]
struct DescriptiveName {
    a: f64,
    b: f64
}

which you can use as a "view" via bytemuck::cast_mut::<[f64; X_LEN], DescriptiveName>.

If you want, you can use destructuring to name the elements:

const X_A: usize = 0;
const X_B: usize = 1;
const X_LEN: usize = 2;

pub fn algorithm(x: &mut [f64; X_LEN], // State vector
) {
    let [x_a, x_b] = x; 

    *x_a = do_stuff_to_A();
    *x_b = do_stuff_to_B();
    do_linear_algebra_stuff(x);
}

But note that directly using indexing may be more flexible in certain situations (to avoid conflicting mutable references).

1 Like

Thank you all. This has been bugging me for a while. It is comforting to know that there are no obvious issues with this approach.