I'm working on a codebase which binds to a Tokio socket and manages a TCP connection. In production, it binds to an AF_VSOCK using the tokio-vsock crate.
While developing locally on Mac, the AF_VSOCK API isn't available as there is no hypervisor -> VM connection — it's just being run natively using cargo run.
When running locally, I have been creating a standard tokio::net::TcpListener struct and in production I have been creating a tokio_vsock::VsockListener. Both structs are mostly interchangeable and expose the same methods. The rest of the code works perfectly, regardless of which struct is being used.
So far, I have just kept both structs and simply commented out the one that isn't needed locally — this is clearly not "good practice". My code is below:
#[tokio::main]
async fn main() -> Result<(), ()> {
// Production AF_VSOCK listener (comment out locally)
let mut listener = tokio_vsock::VsockListener::bind(
&SockAddr::Vsock(
VsockAddr::new(
VMADDR_CID_ANY,
LISTEN_PORT,
)
)
)
.expect("Unable to bind AF_VSOCK listener");
// Local TCP listener (comment out in production)
let mut listener = tokio::net::TcpListener::bind(
std::net::SocketAddr::new(
std::net::IpAddr::V4(
std::net::Ipv4Addr::new(0, 0, 0, 0)
),
LISTEN_PORT as u16,
)
)
.await
.expect("Unable to bind TCP listener");
// This works regardless of which listener is used
let mut incoming = listener.incoming();
while let Some(socket) = incoming.next().await {
match socket {
Ok(mut stream) => {
// Do something
}
}
}
Ok(())
}
I tried using the cfg!() macro with target_os set as the condition, but the compiler complained that the types returned by both bind() methods were mismatched.
My question is: What is the idiomatic way in Rust of assigning different values with different types to a variable, depending on the compilation target OS?