rts
rts

Reputation: 191

How do I create a class with multiple instantiation options

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

Answers (1)

Zefira
Zefira

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

Related Questions