Cloning Vs Taking Ownership in a Read Only Function with &Option<f32>

I'm using the rust_xlsxwriter library to write some data to an Excel spreadsheet, and I have some data that looks like Vec<Vec<Option<f32>>>. This represents rows of data (the inner vec contains the data within a single row, the outer vec contains the collection of rows). It's possible the data has nulls, so a row has Option<f32> rather than just standard floats.

Since I just plan on reading the data, my function that writes it to the spreadsheet takes a reference to the data (or &Vec<Vec<Option<f32>>>)

The problem I'm running into is that the function write_row_matrix that I'm trying to use to write the data wants something with the IntoExcelData trait.

The trait is defined for Option<T> where T is something else implementing IntoExcelData, and the trait is implemented for f32. However, it's not implemented for &f32 or &Option:

error[E0277]: the trait bound `&std::option::Option<f32>: IntoExcelData` is not satisfied
    --> src\worksheet_functions.rs:64:19
     |
64   |             sheet.write_row_matrix(LIMIT_LOCATION.0 + 2, LIMIT_LOCATION.1, lower_limit)?;
     |                   ^^^^^^^^^^^^^^^^ the trait `IntoExcelData` is not implemented for `&std::option::Option<f32>`
     |
     = help: the trait `IntoExcelData` is implemented for `std::option::Option<T>`

If I had a standard Vec<Vec<Option<f32>>>, I'd be fine. So it seems like I have two workarounds:

  1. Have my function take ownership of the dataset before it writes it to Excel
  2. Use a clone function on the elements of the vector.

This does seem to compile:

let lower_limit: &Vec<Vec<Option<f32>>> = data_source;

// clone the inner elements of the item, so the elements are all Option<f32> vs &Option<f32>.
let data = lower_limit.iter().map(|x| x.iter().map(|y| y.clone()));

// this now works
sheet.write_row_matrix(LIMIT_LOCATION.0 + 2, LIMIT_LOCATION.1, lower_limit)?;

What I wanted to understand is there a "rust preferred" solution to this sort of problem. More generally:

  1. When writing my function, if I'm going to need to clone the underlying data anyway, does it make sense to just take the Vec<Vec<Option<f32>>> in my function signature?
  2. Does cloning the set of f32 cost me anything? For example, if the data is going to be copied when it's consumed by the function that writes to Excel, will clone impact performance, or will it get optimized out?
  3. As I understand, I can't write an implementation for the trait IntoExcelData for &Option<f32> since both are defined elsewhere, but is there something else I could do (such as defining a new container type with Option) that would make this cleaner?

I believe you could use

let data = lower_limit.iter().map(|v| v.iter().copied());

which is a bit cleaner, and conveys that this is only an expensive operation if what gets copied is large, which would be rare. (In this case we know it's an Option<f32> which is not large.)

Not in this case. You're not cloning any Vecs. You're cloning each Option<f32> during the lazy iteration. (You could take &[Vec<Option<f32>] instead.)

Order of operation if you cloned

If you cloned it, you'd have to create the second Vec<Vec<_>>, then the Excel thing gets created while (most likely) the (cloned) inner Vecs get deallocated one by one, and then the (cloned) outer Vec gets deallocated, and then the Excel method returns.

The original Vec<Vec<_>> gets deallocated somewhere else, after your function finishes. (Same as happens now.)

It might make sense to take Vec<Vec<Option<f32>> for cases where the caller doesn't need the data afterwords. If you did that and passed it to the method, it would change where those Vecs get deallocated.

Order of operation comparison

If the caller passed by value into your function without cloning, then the deallocation would look like the clone case above, but now it takes place on the original allocation -- there's never a second Vec<Vec<_>>, and the original is deallocated by the time the Excel method returns.

I don't think there's a need here, but let's say you had Option<SomeExpensiveType>. You could do something like

struct Wrapper<T>(T);
impl IntoExcelData for Wrapper<&Option<SomeExpensiveType>> {
    // ...
}

And then pass in a

data = slice.iter().map(|v| v.iter.map(Wrapper));
1 Like

This makes sense and seems to work well - thank you!

1 Like

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.