A port is always 16 bits (2 bytes). An IPv4 address is 4 bytes. This means the optimal size for SocketAddrV4 is 6 bytes (8 bytes with alignment 4 for Ipv4Addr). However,
size_of::<SocketAddrV4>() == 16
(on Linux). This is 2x the expected size
Hmm.. Yes, this is because the implementation is:
pub struct SocketAddrV4 {
inner: c::sockaddr_in,
}
And sockaddr_in
has more data than just the IP and port.
It's also not (currently) possible to make the new
constructors for these socket addresses const fn
s. That's because how it must transform the Ipv4Addr
and u16
arguments into the sockaddr_in
representation. See https://github.com/rust-lang/rust/pull/67315.
Because of this system level representation, the standard library also has to use unsafe
in SocketAddrV4::ip
.
So, the above is a list of cons of having this fundamental address type be represented directly as the system representation. What are the benefits? I guess one benefit is that whenever a SocketAddrV4
or SocketAddrV6
is sent to, or received from the system, conversion is just a simple cast. See https://doc.rust-lang.org/1.47.0/src/std/net/addr.rs.html#558-569 for this. This simple cast performance benefit is paid for by having to do the conversion in the new
constructors instead. I benchmarked this, and found that a const fn new
for a naively implemented struct holding just an Ipv4Addr
and u16
was almost 2x as fast as the current constructor.
So... A struct looking like this:
struct SocketAddrV4 {
ip: Ipv4Addr,
port: u16,
}
Would...
- occupy less memory (~2x)
- have faster methods (maybe not all, but some)
- use less
unsafe
- have been full of
const fn
s many Rust versions ago - have been in
core
a long time ago - and be way easier to read and understand.
Since the internal representation is not exposed from std
(right?), I assumed any benefits of this system representation would be limited to usage of sockets from std
... But I was a bit shocked to find that mio
(and by extension tokio
) relies on these structs indeed being implemented directly as libc::sockaddr
: https://github.com/tokio-rs/mio/blob/27fbd5f04bb5f52a4d1c358cf0c04c6074a3d46b/src/sys/unix/net.rs#L78-L85. I don't find this specified in the documentation for std::net::SocketAddrV4/6
. Does mio
invalidly rely on this representation, or have I missed where this contract was set up between std
and the rest of the world?
I guess (hope) there is a good explanation for this implementation of std::net::SocketAddrV4
(and V6). The above is a list of all the confusing things I found when I just wanted to check why SocketAddr::new
was not yet a const fn
. I hope someone will post a good explanation, and people coming after me can find this thread instead of falling down a rabbit hole . Given that mio
relies on this representation I guess it's too late to change the representation, even if the assumptions they have made are not correct