Reputation: 191
I want to build a class in Rust that allows for variety and convenience in its instantiation similar to the way that std::net::TcpStream
can be provided multiple inputs to its connect
method.
I have the following tests, but very little idea of the structs or traits I need to build to achieve this result.
#[cfg(test)]
mod test {
use super::Ipv4Network;
use std::net::Ipv4Addr;
#[test]
fn instantiation() {
let net_addr = Ipv4Addr::new(10, 0, 0, 0);
let net_mask = Ipv4Addr::new(255, 255, 255, 240);
// Instantiate an Ipv4Network using two Ipv4Addrs.
let with_addrs = Ipv4Network::new(net_addr, net_mask);
// Instantiate an Ipv4Network using an Ipv4Addr and a prefix integer.
let with_addr_prefix = Ipv4Network::new(net_addr, 28);
// Instantiate an Ipv4Network using two strings.
let with_strings = Ipv4Network::new("10.0.0.0", "255.255.255.240");
// Instantiate an Ipv4Network using a string and a prefix integer.
let with_prefix = Ipv4Network::new("10.0.0.0", 28);
// Instantiate an Ipv4Network using a string with CIDR notation.
let with_cidr = Ipv4Network::new("10.0.0.0/28");
}
}
My understanding is that std::net::TcpStream
accomplishes its multiple connect
inputs through the ToSocketAddrs trait, the source of which can be found here. How exactly std::net:TcpStream::connect
uses ToSocketAddrs isn't clear to me, but I imagine I should be able to define an Ipv4Network
as follows:
pub struct Ipv4Network {
addr: Ipv4Addr,
mask: Ipv4Addr,
}
If that's true then how do I go about creating a trait (say ToIpv4Network
) that functions like ToSocketAddrs
does for std::net:TcpStream
?
What should the meat of
impl ToIpv4Network for (Ipv4Addr, Ipv4Addr) {
...
}
look like? Is that even the right path to start going down?
Upvotes: 1
Views: 210
Reputation: 4519
That's a reasonable place to start working from. I think one of your issues is that the interfaces you have defined are going to be difficult to accomplish as written by using a trait.
So, let's talk about the way that ToSocketAddrs
works. First, let's just quickly note and then ignore the fact that the ToSocketAddrs
trait returns an Iterator
of SocketAddrs
. We're ignoring it because it's actually irrelevant to the question at hand.
Here's the body of the trait declaration:
pub trait ToSocketAddrs {
type Iter: Iterator<Item=SocketAddr>;
fn to_socket_addrs(&self) -> Result<Self::Iter>;
}
The purpose of this trait is to allow it to be used as a trait bound in a generic function/struct. Broadly speaking, trait bounds are a way of writing a generic but getting the Rust compiler to ensure that the type that ends up instantiating the generic implements the behavior defined by that trait (or traits.
In the case of ToSocketAddrs
this means that a type can (potentially) be converted to a SocketAddr
. The code that is called inside of connect
then makes use of the to_socket_addrs
function defined on the trait.
As a brief aside, I said potentially because to_socket_addrs
returns a Result
which can take on values of Ok(<success-type>)
and Err(<failure-type>)
, which is probably a good pattern to model your trait on.
This is the key point that relates to why I think your desired interface is maybe not ideal (or implementable, Rust doesn't directly support arity, or any other type of function overloading). Passing a trait bound generic parameter is the easiest way to accomplish your goal.
Upvotes: 1