Trait lifetimes in a HashMap

I'm working on a thing that uses a tcp stream where requests (i.e., writes to the stream) have a response, but the responses can come back in a different order than the requests, and multiple threads can make requests. Each request is given a unique tag so the response can be uniquely identified. My current solution is to start a reader thread whose job it is to route responses to the correct requester. I'm using a HashMap<u16, Receiver<D>> to do this. Receiver is from crossbeam_channel, and I think unimportant to this question. The D type there represents a return message. It's a trait that requires a lifetime in its signature. Here's some simplified code that I think represents this problem ok:

use std::collections::HashMap;

trait T<'de> {
    fn f(&self, i: &'de i32);
}

struct A {}

impl<'a> T<'a> for A{
    fn f(&self, _i: &'a i32) {
        
    }
}

struct S<'a> {
    m: HashMap<u32, Box<dyn T<'a>>>,
}

fn main() {
    let mut s = S{
        m: HashMap::new(),
    };
    let a = A{};
    s.m.insert(1, Box::new(a));
}

My question has to do with the lifetime specifier in the above code. I think that for the struct S it is saying that S has a lifetime of 'a, and that any implementation of trait T must also have a lifetime of 'a. Is that a correct reading of the code? Is that a correct reading?

If so, I think that's problematic because each of these response messages would then have to live at least as long as S, but that's not really possible since I want to make 1 S and a bunch of Ts.

How should I write this HashMap signature such that the message lifetimes don't need to be the same as the struct lifetime?

(As an aside: is there a better way to handle this reader/multiple writers with out-of-order responses thing? I'm not fully convinced the crossbeam_channel crate is the way to go, but I'm a Go programmer and so channels is what I reach for.)

I think we might need to unpack that, because the simplified example you gave:

trait T<'de> {
    fn f(&self, i: &'de i32);
}

I would suggest that this probably doesn't want a lifetime on the trait at all, just a dynamic lifetime on the method argument, probably even left implicit. Having the lifetime on the trait means all of your trait objects can only accept references with the same lifetime, which seems to be your difficulty.

Can you give more of an example of your trait D, and why it requires a fixed lifetime?

1 Like

The exact thing I'm using here is nine::de::Deserialize. I doing something like sending a Tread (I can't link to docs due to a new user 2 link restriction) and expecting back a Rread. So I want to register the tag I sent with the Tread into this HashMap and stick a Receiver for a Rread there. But since there are lots of kinds of messages, I have to use the trait. Is that the information you needed?

When you say "lots of kinds", do you mean other Rust types? That's the only reason you would use a dyn Trait, if the types are not statically known. It sounds like you're really only dealing with Tread and Rread, and you just want HashMap<u16, Receiver<Rread>>, no?

They could be anything that is a Deserializer, which is the trait with the lifetime that I don't know how to describe. I want HashMap<u16, Box<dyn Deserializer>> or something, but I don't know what to write because of the lifetime.

I think you probably want DeserializeOwned, as discussed in the serde lifetimes page, which works for any lifetime of Deserializer. However, these traits aren't object-safe, since they return Self values.