Create a structure that accept T and Arc<T> for the same field

Hi, I have a use case where I want to define a structure like the follow:

struct Animal {
    breed: String
}

this structure is used in two parts of my application where in one part it's not read concurrently, in another part it is. To avoid copying the String all over, I would like to use Arc only for the part of the application that uses concurrency.

Is there a way for me to declare a field that can accept both types (perhaps via a common trait) as opposed to introduce generics and bring them everywhere in my code?

If it is to be read concurrently, then you may be able to just use scoped threads (or join-style async concurrency, if you are using async). Then you can share an &Animal with all the threads/tasks, and don't need to change anything about the contents of the structure.

Why? Doing this will be more complex and not significantly more efficient than using Arc all the time, unless you are planning to repeatedly mutate the String in-place during the non-concurrent part.

But if you really want to:

struct Animal<S> {
    breed: S
}

impl<S: AsRef<str>> Animal<S> {
    // ...
}

This will accept String, Arc<str>, Rc<str>, Box<str>, and even &str.

Oh, you don't want generics. Well, you could use an enum with variants for String and Arc<str>, but there really isn’t much point in doing that unless you have a very particular sort of access pattern.

4 Likes

thank you for your reply. Definitely generics solve this, but you bring around the type specification that is something I would avoid to do where possible.
Since, from an equality perspective, String and Arc are the same, I wanted to "hide" from this struct the fact that its content can be Arc-able or not and leave that to the consumer of the struct.
That's because I'd really want to avoid Arc-ing when it's not required as that creates overhead under certain load pressure

What overhead? Please be more specific about the requirements and access patterns that you are expecting, so that we can give better advice.

Note that Arc does not perform any atomic operations unless you clone it, and that will still be much cheaper than cloning a String.

1 Like

So the access pattern is the following:

  • The Animal is stored in a initial hashmap, this is a high, write heavy (99% of times), hashmap. Once an Animal is added to the dashmap is never changed, only new animals can be added
  • After some time the Animal in the first hashmap is copied in a bunch of other data structures (>30) and here I like the Arc power with interning so that I can deduplicate the strings across all the copies minimizing memory.

So those strings could be Arc-ed or not and was exploring if there was a different way outside from generics (which apparently there isn't?)

You didn't mention threads so should we assume there is only one? Arc is only needed with multiple threads.

Nothing in the access pattern you have described will benefit from using String instead of Arc<str>. Having an Arc<str> doesn't make it more expensive to move or access the Animal struct as a whole.

But switching in the middle from String to Arc<str> (or Arc<String>) will be an entirely unnecessary cost. So, don't do it.

3 Likes