#ip #network #cidr #prefix #subnet

no-std generic-ip

IP address types for generic programming

6 releases

0.1.0-rc.4 Aug 7, 2023
0.1.0-rc.3 Aug 2, 2023
0.1.0-rc.2 Apr 27, 2023
0.1.0-alpha.2 Apr 13, 2022

#4 in #ip-address

Download history 9/week @ 2023-06-09 37/week @ 2023-06-16 25/week @ 2023-06-23 23/week @ 2023-06-30 21/week @ 2023-07-07 14/week @ 2023-07-14 36/week @ 2023-07-21 35/week @ 2023-07-28 64/week @ 2023-08-04 46/week @ 2023-08-11 29/week @ 2023-08-18 98/week @ 2023-08-25 22/week @ 2023-09-01 27/week @ 2023-09-08 22/week @ 2023-09-15 14/week @ 2023-09-22

86 downloads per month
Used in 4 crates

MIT license

6.5K SLoC


Crates.io CI/CD codecov docs.rs


A replacement for the stdlib IP address types intended to provide easier generic abstraction over IP address family.


Types and traits for working with IP addresses and prefixes generically over address families.

The IP address types in std::net do not share any common trait that expresses "this thing is an IP address".

This limitation makes writing code that deals with IP addresses in an address-family-independent way unnecessarily difficult.

This crate provides a collection of types that seek to be compatible with the address types from std::net and prefix types from the popular ipnet crate, but which are generic over address-families.

For example:

use ip::{Address, Afi, Error, Ipv4, Ipv6, Prefix};

struct RibEntry<A: Afi> {
    prefix: Prefix<A>,
    next_hop: Address<A>,

impl<A: Afi> RibEntry<A> {
    fn get_next_hop(&self, addr: Address<A>) -> Option<Address<A>> {
        (self.prefix >= addr).then(|| self.next_hop)

fn main() -> Result<(), Error> {
    let v4: RibEntry<Ipv4> = RibEntry {
        prefix: "".parse()?,
        next_hop: "".parse()?,

    let v6: RibEntry<Ipv6> = RibEntry {
        prefix: "2001:db8::/48".parse()?,
        next_hop: "2001:db8:f00::1".parse()?,

    assert_eq!(v6.get_next_hop("2001:db8:ffff::ffff".parse()?), None);



Names such as Address, Interface, Prefix or Afi are re-used in various different modules within the crate. For example Address is used to name:

This can make understanding which item a given name is referring to difficult without understanding the crate layout.


The IP address-families ipv4 and ipv6 are represented in the type system by the zero-sized types concrete::Ipv4 and concrete::Ipv6.

These "concrete" address-families implement traits::Afi, which in turn bounds the generic parameter of the items exported by the concrete module, such as concrete::Address<A> and concrete::Prefix<A>.

Conversely, the [any] module exports a collection of enums with variants corresponding to the two concrete address families, with each variant containing the corresponding concrete::* item.

Address-family classes

Usually a given use-case will call for either processing objects of a single known (at compile time) address-family or objects that may be of either address-family, as in the following:

use ip::{any, concrete, Afi, Ipv4, Ipv6};

// `x` and `y` must be the same address-family
fn longer_concrete<A: Afi>(
    x: concrete::Prefix<A>,
    y: concrete::Prefix<A>,
) -> concrete::Prefix<A> {
    if x.length() > y.length() {
    } else {

// `x` and `y` may be of different address families, so may not be
// comparable
fn longer_any(x: any::Prefix, y: any::Prefix) -> Option<any::Prefix> {
    match (x, y) {
        (any::Prefix::Ipv4(x), any::Prefix::Ipv4(y)) => Some(longer_concrete(x, y).into()),
        (any::Prefix::Ipv6(x), any::Prefix::Ipv6(y)) => Some(longer_concrete(x, y).into()),
        _ => None,

let x4: concrete::Prefix<Ipv4> = "".parse().unwrap();
let y4: concrete::Prefix<Ipv4> = "".parse().unwrap();

let x6: concrete::Prefix<Ipv6> = "2001:db8:f00::/48".parse().unwrap();
let y6: concrete::Prefix<Ipv6> = "2001:db8::/32".parse().unwrap();

assert_eq!(longer_concrete(x4, y4), y4);
assert_eq!(longer_concrete(x6, y6), x6);

assert_eq!(longer_any(x4.into(), y4.into()), Some(y4.into()));
assert_eq!(longer_any(x4.into(), y6.into()), None);

Occassionally, however, one may need a data structure that may sometimes contain a mix of address-families, but at other times must contain only a single address-family.

To deal with such a requirement, traits::AfiClass provides further generalisation to avoid choosing between items from [any] or concrete, by defining a type-level mapping from an "address-family class" to its associated type for Address, Prefix, etc.

AfiClass is implemented for each of Ipv4 and Ipv6. In this context Ipv4/Ipv6 can be conceptually considered to be the singleton classes of address-families { ipv4 } and { ipv6 }.

Additionally, the any::Any type implements AfiClass, providing type-level mappings to the items of the [any] module. [Any] can be thought of as the class { ipv4, ipv6 }.

Various type aliases are defined at the crate root to provide easy access to this mapping. In general, it is easier and clearer to use Address<Ipv4> or Address<Any> than concrete::Address<Ipv4> or any::Address.


use ip::{Address, Afi, AfiClass, Any, Ipv4};

#[derive(Debug, PartialEq)]
struct Foo<A: AfiClass> {
    addr: Address<A>,

impl<A: AfiClass> Foo<A> {
    fn new(addr: Address<A>) -> Self {
        Self { addr }

    fn into_concrete<C>(self) -> Option<Foo<C>>
        C: Afi,
        Address<C>: TryFrom<Address<A>>,

let anys: Vec<Foo<Any>> = vec![
    Foo {
        addr: Address::<Any>::Ipv4("".parse().unwrap()),
    Foo {
        addr: Address::<Any>::Ipv6("2001:db8::1".parse().unwrap()),
    Foo {
        addr: Address::<Any>::Ipv4("".parse().unwrap()),

let filtered: Vec<Foo<Ipv4>> = vec![
    Foo {
        addr: "".parse().unwrap(),
    Foo {
        addr: "".parse().unwrap(),