#env-var #environment #variables #env #parse #define #parser

build envparse

Parse compile-time environment variables into consts

1 unstable release

0.1.0 Dec 1, 2024

#91 in Configuration

Download history 113/week @ 2024-11-25 33/week @ 2024-12-02 10/week @ 2024-12-09

156 downloads per month

Unlicense OR MIT OR Apache-2.0

63KB
1K SLoC

envparse

Quick and dirty crate for parsing values out of an environment var provided at compile time. See also: docs.

Usage

Here's an example

const MAX_LEN: usize = envparse::parse_env!("MYCRATE_MAX_THING_LEN" as usize else 64);
struct Thing {
    len: [u8; MAX_LEN],
}

You can bound by ranges too. This one will fail because the MUST_BE_USER_PROVIDED var isn't provided.

const MAX_LEN_LOG2: u32 = envparse::parse_env!("MYCRATE_MAX_LEN_LOG2" as u32 in 0..32);
const MAX_LEN: usize = 1 << MAX_LEN_LOG2;
struct Thing {
    len: [u8; MAX_LEN],
}

You can also try

const MAX_LEN_LOG2: u32 = match envparse::parse_env!(try "OPTIONAL_MAX_LEN_LOG2" as u32 in 0..32) {
    Some(v) => v,
    None => 5,
}
const MAX_LEN: usize = 1 << MAX_LEN_LOG2;
struct Thing {
    len: [u8; MAX_LEN],
}


lib.rs:

A crate which allows parsing environment variables defined at compile time into constants using const fn (rather than proc macros).

See parse_env for the main entry point into the library.

Motivation

env! and option_env! macros are useful for allowing certain forms of compile-time customization for libraries and programs, however they're unfortunately limited: they always produce a &str or Option<&str> as a result. This works so long as you need a string, and not something like a number.

In many cases, it's desirable to allow something like an array size to be configured at compile time. This pattern is fairly common in C and C++ code, where it's handled by allowing the user to tune these values for their use, possibly by providing something like -DFOOBAR_SIZE=32[^1] via an environment variable like CFLAGS.

Unfortunately, using one of these strings as an array length requires it be parsed at compile time, either in a proc macro, or a const fn. Both of these have downsides: proc macros are extremely slow to compile, and

Unfortunately, const fn is very limited in Rust, so parsing this is a pain. That's what this library is for.

[^1]: Or /D with MSVC — you get the idea.

use envparse::parse_env;
// parse `MYCRATE_MAX_THING_LEN` from the environment,
// defaulting to 4 if not provided.
const MAX_THING_LEN: usize = parse_env!("MYCRATE_MAX_THING_LEN" as usize else 4);
struct Thing {
    len: [u8; MAX_THING_LEN],
}

Supported types

Currently, the following types are supported:

Primitive integers

The primitive integer types are all supported: i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isizeandusize`.

These mostly follow a (slight superset of) Rust's syntax, with the exception that a trailing type indicator is not allowed.

Booleans

Booleans are supported, following some mostly ad-hoc conventions described by the table. As with integers, the parsing is not case-sensitive and ignores leading and trailing whitespace

Note that the empty string is not considered a valid bool, so FOOBAR="" neither works to enable or disable something.

bool value accepted strings (case-insensitive, trimmed)
false 0, false, f, off, no or n
true 1, true, t, on, yes or y

Syntax

Integers

Integers are parsed as follows with a couple notes:

  1. Whitespace is ignored at the start or end of the input.
  2. Input is not case-sensitive. 0XABC is equivalent to 0xabc.
  3. + is allowed as a sign prefix, unlike in Rust's syntax.
  4. Unsigned integers reject a leading - sign early, but for the most part bounds/ranges are not checked until after parsing.
integer: ('+' | '-')? (dec_int | oct_int | bin_int | hex_int)

dec_int: digit_dec (digit_dec | '_')*
hex_int: '0x' (digit_hex | '_')* digit_hex (digit_hex | '_')*
oct_int: '0o' (digit_oct | '_')* digit_oct (digit_oct | '_')*
bin_int: '0b' (digit_bin | '_')* digit_bin (digit_bin | '_')*
digit_bin: [0-1]
digit_oct: [0-7]
digit_dec: [0-9]
digit_hex: [0-9a-fA-F]

Booleans

This is entirely case-insensitive, and any whitespace is trimmed from either end.

We're fairly forgiving here (perhaps more-so than we should be), in order to be compatible with some other ways of configuration (rustc's command line arguments, for example).

boolean: (true_str | false_str)
false_str: ( '0' | 'false' | 'f' | 'off' | 'no'  | 'n' )
true_str:  ( '1' | 'true'  | 't' | 'on'  | 'yes' | 'y' )

No runtime deps