Returning a slice of a struct field

Hi,

I recently hit a point where I go back and forth in between two implementations, changing it and returning to the other one quite frequently.

I am working with a slice of struct holding two fields.

struct equation {
    left: f64,
    right: f64,
}

struct equations<'a> {
    data: &'a [equation]
}

However, doing so is cumbersome for the implementation of the methods of the struct equations as I am working only with the objects &[left] and &[right].

The other possible implementation I used is directly:

struct equations<a'> { 
    left: &'a [f64],
    right: &'a [f64],
}

Using this representation is way better in order to implement my methods, as I am working only either on the left or the right part, but never on both at the same time. However it makes it way less natural for the user (and myself) to create the objects, as it is not possible to create them one by one, it is only possible to create them once all of them are known.

I would like to be able to create an hybrid representation like the following one :

struct equation {
    left: f64,
    right: f64,
}

struct equations<a'> { 
    left: &'a [f64],
    right: &'a [f64],
}

impl<'a> equations<'a> {
    pub fn new(eqs: &'a [equation]) -> Self {
        // Some magic there to create the two slices
    }
}

I don't care much of the performance of the code (as it is only a pre-processing step before an expensive computation). I would just like to have the two slices so the code can be more efficient during the expensive computation.
I struggled has it is not possible to create a new reference in a function as it will go out of scope.
I found other posts discussing this topic (Struct as slice? and Returning a struct alongside a slice of one of its fields). There seams to be a solution using unsafe, but are the slices of [f64] next to each other in memory? Also, would there be a way to do so with safe rust, knowing that the performance of this step does not matter, the sole purpose being to change the layout in memory so futures accesses are faster.

Thanks in advance !

In new, the equations are laid out in order, but there's no guarantee of where the fields are inside of them. Most likely, it'll be left0, right0, left1, right1, ....

However, constructing a slice means the elements have to be contiguous in memory, so what you want to do isn't quite possible, unless you make a copy of the values (and thus have left: Vec<f64> or left: Box<[f64]>). If you don't care about performance I would just make an owned copy.

If the performance doesn't matter, you should just take ownership using something like Vec rather than slices. As it is, there is no safe way to do this, as a slice of T must be contiguous, so a slice of equation would be interspersed with left and right elements. That means you can't slice just the left or right out of it.

You could just make a custom collection type that would act as a random-access, iterable view over the slice of structs, essentially behaving similar to a slice (playground):

struct SliceProjection<'a, T, F> {
    slice: &'a [T],
    projection: F,
}

impl<'a, T, U: 'a, F: Fn(&'a T) -> &'a U> SliceProjection<'a, T, F> {
    pub fn new(slice: &'a [T], projection: F) -> Self {
        SliceProjection { slice, projection }
    }
    
    pub fn len(&self) -> usize {
        self.slice.len()
    }
}

impl<'a, T, U: 'a, F: Fn(&'a T) -> &'a U> Index<usize> for SliceProjection<'a, T, F> {
    type Output = U;
    
    fn index(&self, index: usize) -> &U {
        (self.projection)(&self.slice[index])
    }
}

impl<'a, T, U: 'a, F: Fn(&'a T) -> &'a U> IntoIterator for SliceProjection<'a, T, F> {
    type IntoIter = Map<SliceIter<'a, T>, F>;
    type Item = &'a U;

    fn into_iter(self) -> Self::IntoIter {
        self.slice.iter().map(self.projection)
    }
}

Thanks a lot for the suggestions, I actually opted for a mixed option:

I stayed with the structure that is optimized for my given problem. Hence the user can always choose the more perfomant construction method.

If the user want to use for the ergonomy, I implement a method convert_into_vec() to facilite the data manipulation:

struct equation {
    left: f64,
    right: f64,
}

struct equations<a'> { 
    left: &'a [f64],
    right: &'a [f64],
}

impl<a'> equations<a'> {
    pub fn convert_into_vec(Vec<equation>) -> (Vec<f64>, Vec<f64>) {
         // The creation of the vectors happens here
    }
    pub fn new(lefts: &[f64], rights: &[f64]) -> Self {
        // The constructor
   }
}

The user has a easy way to create the vectors required for the new() method.
I will add a macro to simplify and chain the two calls.

Thanks for the different ideas !

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.