1

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?

Jonathan Woollett-light
  • 2,813
  • 5
  • 30
  • 58
  • 2
    If your structure is complex and you sometimes only need parts of it, it probably isn't very SRP and you probably should refactor your code anyway. – mcarton Jun 23 '20 at 15:45
  • 3
    Your question is "how do I not clone things I don't need" and the answer is "don't clone things you don't need". I don't see what you are hoping to get from an answer beyond that. – Shepmaster Jun 23 '20 at 15:49
  • See also [What happens when an Arc is cloned?](https://stackoverflow.com/q/40984932/155423) – Shepmaster Jun 23 '20 at 15:50
  • One idea might be to put some of your "many large fields" behind `Arc`s, so that cloning the struct becomes relatively cheap. This has some advantages over putting the whole struct inside an `Arc` (for instance, different structs may share a subset of their fields). You can use `.make_mut()` to get copy-on-write semantics if that is desirable. But it may still be better to refactor this big struct into something more manageable. – trent Jun 23 '20 at 17:03

1 Answers1

2

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().

parker_codes
  • 3,267
  • 1
  • 19
  • 27