Field not present in the struct (PhantomData)

Hello,

I am trying to make sense of this piece of code

pub fn can_write_head(&self) -> bool {
        if !T::should_read_first() {    // <--- I don't understand what's happening here.
            if let Reading::Closed = self.state.reading {
                return false;
            }
        }
        match self.state.writing {
            Writing::Init => true,
            _ => false,
        }
    }

Basically, the type T has to implement the trait Http1Transaction, but that field is not present in neither the struct definition nor the instanciation method (using the Conn::new(io: I) struct building method).

pub(crate) struct Conn<I, B, T> {
    io: Buffered<I, EncodedBuf<B>>,
    state: State,
    _marker: PhantomData<fn(T)>,
}

It is wrapped inside a PhantomData marker, as the function's pointer's only argument.

Here is the file where it is implemented.

Also, the file where the Conn struct is used with that T::should_error_on_parse_eof() syntax.

I guess that the compiler will choose either options, but I still don't understand how that would work. If someone is familiar with the hyper source code and could help me out with this, I've already spent a good amount of time trying to figure it out.

  • Client::should_error_on_parse_eof()

  • Server::should_error_on_parse_eof()

How does the code "know" which one to use? I am reverse engineering the Client code examples but I just don't know where in the code that check happens (to decide to either use the Client or Server implementation).

Maybe someone is familiar with hyper's source code and can point me out in the right direction.

Thank you in advance,

It's defined on the trait here. Rust knows which implementation to call because a generic type T is always going to be known when compiling the code, and if multiple types are used from different places, the code is duplicated for every type T in use, with the code using it being compile to call the correct version.

Thank you Alice. That part makes sense now. I took note of it.

Can you please explain what PhantomData<fn(T)> means in plain english? Thank you.
Why not just writing down PhantomData? What difference does it make in this particular context.

I̶ ̶s̶t̶i̶l̶l̶ ̶d̶o̶n̶'̶t̶ ̶g̶e̶t̶ ̶h̶o̶w̶ ̶w̶e̶ ̶c̶a̶n̶ ̶m̶a̶g̶i̶c̶a̶l̶l̶y̶ ̶w̶r̶i̶t̶e̶ ̶s̶o̶m̶e̶t̶h̶i̶n̶g̶ ̶l̶i̶k̶e̶ ̶t̶h̶i̶s̶ ̶T̶:̶:̶s̶h̶o̶u̶l̶d̶_̶e̶r̶r̶o̶r̶_̶o̶n̶_̶p̶a̶r̶s̶e̶_̶e̶o̶f̶(̶)̶ ̶i̶n̶s̶i̶d̶e̶ ̶a̶n̶ ̶i̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶.̶ ̶I̶ ̶d̶o̶n̶'̶t̶ ̶u̶n̶d̶e̶r̶s̶t̶a̶n̶d̶ ̶t̶h̶e̶ ̶r̶a̶t̶i̶o̶n̶a̶l̶e̶ ̶b̶e̶h̶i̶n̶d̶ ̶i̶t̶.̶
I got it. Now the question is to look where the compiler chooses between using either Client or Server. Any idea of where to look?

Having a field of type PhantomData<fn(T)> means that the compiler should treat the struct as if it had a field of type fn(T), without actually making it have such a field. It is necessary to have a PhantomData for every generic parameter that is not used in any of the other fields. Note that the choice of fn(T) rather than just T makes a difference because it affects variance.

As for whether it uses Client or Server. To answer this, go up the call-stack until you reach a position that constructs either an Conn<_, _, Client> or Conn<_, _, Server>. The one it uses determines which method it calls.

2 Likes

Thank you Alice.