Hello everyone,
I have probably a dumb question – why there are no traits for network sockets in the standard/core library? I am working on library that depends on UDP socket implementation. Initially I binded to std::net::UdpSocket
and created socket objects internally. When I decided that it may worth providing no_std
implmenetation, it became a hell though. I have looked through several popular crates in order to find out how to deal with std/no_std
switching and wast majority of crates' implementations look something like that:
// ...
mod str {
#[cfg(not(feature = "std))]
use core::str;
#[cfg(feature = "std)]
use std::str;
}
// ...
I found no-std-net crate that provides base no_std
network related primitives like ToSocketAddrs
trait and SocketAddr
and IpAddr
related stuff. But that's it. Other libraries like smoltcp, async-std define their own socket structures.
I could make in my library something like pattern above:
// ...
mod net {
#[cfg(not(feature = "std))]
use smoltcp::net;
#[cfg(feature = "std)]
use std::net;
}
// ...
and deal with reexporing things and resolving interface differences, but that seems as tremendous overhead, since my library just want to send some data over UDP and it does not really care about crate's implementation. For example:
- UDP socket
bind()
signature in the smoltcp:
pub fn bind<T: Into<IpEndpoint>>(&mut self, endpoint: T) -> Result<()>
- UDP socket
bind()
signature in the std:
pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<UdpSocket>
So, I came to a solution with defining my own traits that implies library required interface to be implemented:
pub trait NtpUdpSocket {
fn send_to<T: net::ToSocketAddrs>(
&self,
buf: &[u8],
addr: T,
) -> core::result::Result<usize, Error>;
fn recv_from(
&self,
buf: &mut [u8],
) -> core::result::Result<(usize, net::SocketAddr), Error>;
}
It comes another point that Rust currently does not have - rebalancing coherence. Someone cannot just implement my library trait on the UdpSocket
structure they wants. Another wrapper required:
// ...
use std::net::UdpSocket;
struct MyNtpUdpSocketWrap {
socket: UdpSocket;
}
impl NtpUdpSocket for MyNtpUdpSocketWrap {
// ...
}
That is, every user of my library has to write such a wrapper every time. Although I can provide trait implementation for std
, smoltcp
, etc., that may be burdensome since other TCP/IP stack implementation may come to scene.
So, I would like to ask for help with those points:
- is defining own trait for such a base thing can be considered ideomatic?
- is it worth preparing RFC for introducing traits for TCP/UDP sockets to be included into the
core
? - maybe there are better options to solve the problem mentioned?