Is there a way to access a structs fields as an array?

Viewed 227

I'm new to rust and am trying to figure out if this is possible.

So sometimes it's cleaner for functions/methods to access data as an array, and sometimes it's cleaner to access data by name

in rust I can define something like this:

struct Vector3D{
    x : f64,
    y : f64,
    z : f64,
    coordinates : [f64;3]
}

But there needs to only be a single place where each value is stored.

In Java what I'm asking for is (I believe) not possible and the solution I use is this:

public class Vector3D {
    public static class Vec3D {
        public static final int ARRAY_SIZE = 3;
        public static final int X = 0;
        public static final int Y = 1;
        public static final int Z = 2;
    }
    private double[] coords;

    public double x(){
        return coords[Vec3D.X];
    }
}

which is a bit kludgy, but allows me to access the values of the fixed size arrays by name.

Is there a better way to do something like this in Rust?

3 Answers

You can keep coordinates as main storage and provide methods to access each of them by their names. Moreover, implementing Index/IndexMut traits gives some comfort.

#[derive(Debug)]
struct Vector3D {
    coordinates: [f64; 3],
}

impl Vector3D {
    fn x(&self) -> f64 {
        self.coordinates[0]
    }
    fn y(&self) -> f64 {
        self.coordinates[1]
    }
    fn z(&self) -> f64 {
        self.coordinates[2]
    }
    fn x_mut(&mut self) -> &mut f64 {
        &mut self.coordinates[0]
    }
    fn y_mut(&mut self) -> &mut f64 {
        &mut self.coordinates[1]
    }
    fn z_mut(&mut self) -> &mut f64 {
        &mut self.coordinates[2]
    }
}

impl std::ops::Index<usize> for Vector3D {
    type Output = f64;
    fn index(
        &self,
        idx: usize,
    ) -> &Self::Output {
        &self.coordinates[idx]
    }
}

impl std::ops::IndexMut<usize> for Vector3D {
    fn index_mut(
        &mut self,
        idx: usize,
    ) -> &mut Self::Output {
        &mut self.coordinates[idx]
    }
}

fn main() {
    let mut v = Vector3D {
        coordinates: [1.1, 2.2, 3.3],
    };
    println!("x={}, y={}, z={}", v.x(), v.y(), v.z());
    *v.x_mut() += 10.01;
    *v.y_mut() += 20.02;
    *v.z_mut() += 30.03;
    for idx in 0..3 {
        println!("v[{}]={}", idx, v[idx]);
        v[idx] *= -1.0;
    }
    println!("v={:?}", v);
}
/*
x=1.1, y=2.2, z=3.3
v[0]=11.11
v[1]=22.22
v[2]=33.33
v=Vector3D { coordinates: [-11.11, -22.22, -33.33] }
*/

based on @vallentin's comment I put together this code which I feel directly answers the question:

 use core::ops::Index;
    use core::ops::IndexMut;
    fn main() {
        
        let mut v = Vector3D{x:1.0,y:2.0,z:3.0};
        
        println!("v={:?}", v);
        
        // can read/write by name
        v.x = v.y + 2.0;
        
        println!("v={:?}", v);
        
        // can read/write by directly indexing
        v[2] = v.x - v[1];
        
        println!("v={:?}", v);
    }
    
    #[derive(Debug)]
    struct Vector3D {
        x: f64,
        y: f64,
        z: f64,
    }
    
    impl Index<usize> for Vector3D {
        type Output = f64;
    
        fn index(&self, coordinate: usize) -> &Self::Output {
            
            match coordinate {
                0 => &self.x,
                1 => &self.y,
                2 => &self.z,
                _ => panic!("Invalid Vector3D index: {}", coordinate),
            }
        }
    }
    
    impl IndexMut<usize> for Vector3D {
        fn index_mut(&mut self, coordinate: usize) -> &mut f64 {
            
            match coordinate {
                0 => &mut self.x,
                1 => &mut self.y,
                2 => &mut self.z,
                _ => panic!("Invalid Vector3D index: {}", coordinate),
            }
        }
    }

Output:
v=Vector3D { x: 1.0, y: 2.0, z: 3.0 }
v=Vector3D { x: 4.0, y: 2.0, z: 3.0 }
v=Vector3D { x: 4.0, y: 2.0, z: 2.0 }

basically using the Index & IndexMut traits allows the struct to be directly index for read-only or read-write access

Here is another version using Index & IndexMut but without any match statements & with the raw data stored as an array

use core::ops::Index;
use core::ops::IndexMut;
fn main() {
    
    let mut v = Vector3D {
        coordinates: [1.0, 2.0, 3.0],
    };
    
    println!("v={:?}", v);
    
    // can read/write by name
    v[Vec3D::X] = v[Vec3D::Y] + 2.0;
    
    println!("v={:?}", v);
    
    // can read/write by directly indexing
    v[2] = v[Vec3D::X] - v[1];
    
    println!("v={:?}", v);
}

enum Vec3D{
    X = 0,
    Y,
    Z,
}

#[derive(Debug)]
struct Vector3D {
    coordinates: [f64;3],
}

impl Index<Vec3D> for Vector3D {
    type Output = f64;

    fn index(&self, coordinate: Vec3D) -> &Self::Output {
        &self.coordinates[coordinate as usize]
    }
}

impl IndexMut<Vec3D> for Vector3D {
    fn index_mut(&mut self, coordinate: Vec3D) -> &mut f64 {
        &mut self.coordinates[coordinate as usize]
    }
}

impl Index<usize> for Vector3D {
    type Output = f64;

    fn index(&self, coordinate: usize) -> &Self::Output {
        &self.coordinates[coordinate]
    }
}

impl IndexMut<usize> for Vector3D {
    fn index_mut(&mut self, coordinate: usize) -> &mut f64 {
        &mut self.coordinates[coordinate]
    }
}

Or alternatively the Vector3D struct could be removed entirely

use core::ops::Index;
use core::ops::IndexMut;

// this allows us to use Vector3D in place of [f64;3]
type Vector3D = [f64;3];

fn main() {
    
    // without this v[X] would be v[Vec3D::X] etc
    use crate::Vec3D::*;
    
    let mut v : Vector3D = [1.0, 2.0, 3.0];

    println!("v={:?}", v);
    
    // can read/write by name
    v[X] = v[Y] + 2.0;
    
    println!("v={:?}", v);
    
    // can read/write by directly indexing
    v[2] = v[X] - v[1];
    
    println!("v={:?}", v);
    
}

enum Vec3D{
    X = 0,
    Y,
    Z,
}

impl Index<Vec3D> for Vector3D {
    type Output = f64;

    fn index(&self, coordinate: Vec3D) -> &Self::Output {
        &self[coordinate as usize]
    }
}

impl IndexMut<Vec3D> for Vector3D {
    fn index_mut(&mut self, coordinate: Vec3D) -> &mut f64 {
        &mut self[coordinate as usize]
    }
}

which has the advantage of brevity and compile-time index-out-of-bounds checks but does (for better or worse) implement the name based indexing for all arrays of type [f64;3]

Related