Search code examples
rustrust-tokio

How could I avoid cloning the entirety of a large struct to send to a thread when only parts are needed?


My use case:

  • I have a large complex struct.
  • I want to take a snapshot of this struct to send it to a thread to do some calculation.
  • Many large fields within this struct are not neccessary for calculation.
  • Many fields within the struct are partially required (a field may be a struct and only a few parameters from this struct are required).

At the moment I simply call .clone() and pass a clone of the entire struct to the thread.

It is difficult to give a good example, but this is a summary of my current method:

use tokio::task;

fn main() {
    let compex_struct = ComplexStruct::new(...);
    // some extra async stuff I don't think is 100% neccessary to this question
    let future = async_function(compex_struct.clone()); // Ideally not cloning whole struct
    // some extra async stuff I don't think is 100% neccessary to this question
}

fn async_function(complex_struct:ComplexStruct) -> task::JoinHandle<_> {
    task::spawn_blocking(move || {
        // bunch of work, then return something
    })
}

My current working idea is to have a seperate struct such as ThreadData which is instantiated with ThreadData::new(ComplexStruct) and effectively clones the required fields. I then pass ThreadData to the thread instead.

What is the best solution to this problem?


Solution

  • I think you've answered your own question. 😁 If you're just looking for validation, I believe a refactor to only the needed parts is a good idea. You may find ways to simplify your code, but the performance boost seems to be your reasoning. We can't see benchmarks on this, but perhaps you want to track that.

    This part is just my opinion, but instead of ThreadData::new(), you could do ThreadData::from(), or better yet, impl From<ComplexStruct> for ThreadData {}. If it only has one purpose then it doesn't matter, but if ThreadData will ever be used in a different context, I like to keep the "new"/"from" functions available for a general instance. Otherwise I eventually have Struct::from_this(), Struct::from_that(), or Struct::from_some_random_input(). 😋