Compile time struct instead of runtime

I have a library for a list of IP ranges which are fixed strings and are in the format of Ipv4Net to produce a IpRange struct. I can use once cell Lazy to initialise this once, but this is at runtime.

I want to move this to compile time as the data is fixed and does not change and this will reduce the overhead if any item is in the wrong format or for reasons is unable to be converted. I have tests, but just in case there is any difference in behaviour. I can create a constant for a vector of strings and also a vector of Ipv4Net struct types. I am unable to create IpRange struct as this seems to initialise using "new" and I would need to call the "add" function for each item.

extern crate iprange;
extern crate ipnet;

use std::net::Ipv4Addr;
use iprange::IpRange;
use ipnet::Ipv4Net;
use once_cell::sync::Lazy;

fn lookup(ip: String) -> bool {
    IP_RANGES.contains(&ip.parse::<Ipv4Addr>().unwrap())
}

static IP_RANGES: Lazy<IpRange<Ipv4Net>> = Lazy::new(|| {
    ranges()
        .iter()
        .map(|s| s.parse().unwrap())
        .collect()
});

fn ranges() -> Vec<&'static str> {
    vec![
        "1.2.3.4/12",
    ]
}

Slight alternative which still has the error for calling the functions.

const IP_RANGES: IpRange<Ipv4Net> = {
    let mut ip_ranges: IpRange<Ipv4Net> = IpRange::new();
    ip_ranges.add("1.2.3.4/12".parse::<Ipv4Net>().unwrap());
    ip_ranges
}

I get these errors.

cannot call non-const fn `IpRange::<Ipv4Net>::new` in constants
calls in constants are limited to constant functions, tuple structs and tuple variants

cannot call non-const fn `IpRange::<Ipv4Net>::add` in constants
calls in constants are limited to constant functions, tuple structs and tuple variants

Is there a way to move this struct create to compile time as it is fixed? I have looked at libraries like konst but this seems to be more for iterators and not functions in general. I have read there are open issues about allowing more logic in constant functions, but these have been open for a few years and there is a lot of debate.

Any ideas on how I can achieve a compile time data structure to avoid parsing or creating this at runtime are appreciated.

I am having similar thing, https://docs.rs/konst/latest/konst/ is quite helpful, but looks a bit peculiar at the end

You might also want to look into something like databake - I'm not sure how well it works with a foreign type though.

I looked at konst and I couldn't find a way to have a constant for the function. Do you know if there are other examples of this?

I have it like this:

use core::net::Ipv4Addr;

use konst::{iter, result::unwrap_or_else, string};
use smoltcp::wire::{self, IpAddress};

macro_rules! parse_ipv4 {
    ($s:expr, $constructor:expr) => {{
        let [a, b, c, d] = iter::collect_const!(u8 =>
            string::split($s, "."),
            map(|s| parse_u8(s, 10, "Invalid IP_V4 address")),
        );
        $constructor(a, b, c, d)
    }};
}
macro_rules! parse_ipv6 {
    ($s:expr, $constructor:expr) => {{
        let [a, b, c, d, e, f, g, h] = iter::collect_const!(u16 =>
            string::split($s, ":"),
            map(|s| parse_u16(s, 16, "Invalid IP_V6 address")),
        );
        $constructor(a, b, c, d, e, f, g, h)
    }};
}

pub const CLOCK_HZ: u32 = 1_000;
pub const SYS_TICK_HZ: u32 = 12_000_000;
pub const MAC: [u8; 6] = iter::collect_const!(u8 =>
    string::split(env!("MAC"), ":"),
        map(|s| parse_u8(s, 16, "Invalid MAC address")),
);
pub const IP_V4: IpAddress = parse_ipv4!(split_ip(env!("IP_V4")).0, IpAddress::v4);
pub const IP_V4_NETMASK: u8 = split_ip(env!("IP_V4")).1;
pub const IP_V4_GATEWAY: wire::Ipv4Address =
    parse_ipv4!(env!("IP_V4_GATEWAY"), wire::Ipv4Address::new);

pub const IP_V6: IpAddress = parse_ipv6!(split_ip(env!("IP_V6")).0, IpAddress::v6);
pub const IP_V6_NETMASK: u8 = split_ip(env!("IP_V6")).1;
pub const IP_V6_GATEWAY: wire::Ipv6Address =
    parse_ipv6!(env!("IP_V6_GATEWAY"), wire::Ipv6Address::new);

pub const MQTT_BROKER_IP: Ipv4Addr = parse_ipv4!(env!("MQTT_BROKER_IP"), Ipv4Addr::new);
pub const MQTT_BROKER_PORT_TLS: u16 =
    parse_u16(env!("MQTT_BROKER_PORT_TLS"), 10, "Invalid MQTT port");
pub const MQTT_BROKER_PORT_TCP: u16 =
    parse_u16(env!("MQTT_BROKER_PORT_TCP"), 10, "Invalid MQTT port");

const fn split_ip(ip: &str) -> (&str, u8) {
    let (ip, mask) = konst::option::unwrap!(string::split_once(ip, "/"));
    (ip, parse_u8(mask, 10, "Invalid IP mask"))
}
const fn parse_u8(s: &str, radix: u32, msg: &str) -> u8 {
    unwrap_or_else!(u8::from_str_radix(s, radix), |_| panic!("{}", msg))
}
const fn parse_u16(s: &str, radix: u32, msg: &str) -> u16 {
    unwrap_or_else!(u16::from_str_radix(s, radix), |_| panic!("{}", msg))
}

Thank you for this. Do you know if this would work with IpRange? I got one working where the strings were Ipv4Net but could not create the IpRange struct until runtime? All of it at compile time would be ideal.

I looked at the implementation of that crate. All functions are not const, so you need to create a PR to constify them. Looking deeper, there are allocations, and it is required to be in the runtime. Either you should reimplement it to not use allocations or you can't have it at compile time.