# Environment Variables

### Environment Variables

An environment variable is a named value provided to a program by the operating system.

Programs use environment variables for configuration.

Examples:

```text
HOME
PATH
TMPDIR
USER
EDITOR
```

A program may also define its own variables:

```text
APP_PORT
DATABASE_URL
LOG_LEVEL
```

Environment variables are text.

If you need a number, boolean, path, or URL, you must parse the text yourself.

#### Viewing Environment Variables

On Linux and macOS:

```bash
echo $HOME
```

On Windows Command Prompt:

```bat
echo %USERNAME%
```

Programs inherit environment variables from the process that started them.

For example:

```bash
APP_PORT=8080 ./my_program
```

The program can then read:

```text
APP_PORT
```

from its environment.

#### Reading an Environment Variable

The standard library provides access to environment variables through `std.process`.

A common pattern is:

```zig
const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const home = std.process.getEnvVarOwned(allocator, "HOME") catch {
        std.debug.print("HOME not set\n", .{});
        return;
    };
    defer allocator.free(home);

    std.debug.print("HOME = {s}\n", .{home});
}
```

This asks for the value of:

```text
HOME
```

If the variable exists, the function returns an allocated string.

If the variable does not exist, the function returns an error.

#### Why an Allocator Is Needed

Environment variable lengths are not known at compile time.

The returned text must be stored somewhere.

That is why this function needs an allocator:

```zig
std.process.getEnvVarOwned(allocator, "HOME")
```

The returned memory belongs to the caller.

That is why this line matters:

```zig
defer allocator.free(home);
```

Without freeing, the memory would leak.

This follows Zig’s normal ownership rules:

The function allocates memory.

The caller later frees it.

#### Handling Missing Variables

Environment variables are optional.

A program should not assume a variable exists unless it controls the environment itself.

This pattern handles missing variables:

```zig
const value = std.process.getEnvVarOwned(allocator, "APP_PORT") catch {
    std.debug.print("APP_PORT not set\n", .{});
    return;
};
```

You can also provide a default value.

```zig
const port_text = std.process.getEnvVarOwned(allocator, "APP_PORT") catch {
    const default = "8080";
    std.debug.print("using default port {s}\n", .{default});
    return;
};
```

But remember the distinction:

A missing variable may be acceptable.

A malformed variable is usually an error.

Do not silently accept broken input.

#### Parsing Environment Variables

Environment variables are strings.

Suppose:

```text
APP_PORT=8080
```

You still need to parse the number.

```zig
const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const port_text = std.process.getEnvVarOwned(
        allocator,
        "APP_PORT",
    ) catch {
        std.debug.print("APP_PORT not set\n", .{});
        return;
    };
    defer allocator.free(port_text);

    const port = try std.fmt.parseInt(u16, port_text, 10);

    std.debug.print("port = {}\n", .{port});
}
```

This turns:

```text
"8080"
```

into:

```zig
8080
```

of type `u16`.

#### Boolean Environment Variables

Suppose:

```text
DEBUG=true
```

You can parse it manually.

```zig
const std = @import("std");

fn parseBool(text: []const u8) !bool {
    if (std.mem.eql(u8, text, "true")) return true;
    if (std.mem.eql(u8, text, "false")) return false;

    return error.InvalidBoolean;
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const debug_text = std.process.getEnvVarOwned(
        allocator,
        "DEBUG",
    ) catch {
        std.debug.print("DEBUG not set\n", .{});
        return;
    };
    defer allocator.free(debug_text);

    const debug = try parseBool(debug_text);

    std.debug.print("debug = {}\n", .{debug});
}
```

This accepts only:

```text
true
false
```

That strictness is useful.

#### Empty Variables

An environment variable can exist but contain an empty string.

Example:

```bash
APP_PORT=
```

The variable exists, but its value is:

```text

```

an empty string.

This is different from the variable being missing entirely.

So these are separate cases:

| Situation | Meaning |
|---|---|
| Variable missing | No value provided |
| Variable exists but empty | Value provided, but empty |

Your program should decide how to handle both.

#### Listing Environment Variables

Some APIs allow iterating through the whole environment.

The general structure is:

```zig
var env_map = try std.process.getEnvMap(allocator);
defer env_map.deinit();

var it = env_map.iterator();

while (it.next()) |entry| {
    std.debug.print("{s} = {s}\n", .{
        entry.key_ptr.*,
        entry.value_ptr.*,
    });
}
```

This creates a map of environment variables.

Then it iterates through them.

The exact API details can vary across Zig versions, but the idea is stable:

Environment variables are key-value string pairs.

#### Common Environment Variables

Some variables appear often on Unix-like systems.

| Variable | Meaning |
|---|---|
| `HOME` | User home directory |
| `PATH` | Executable search paths |
| `USER` | Username |
| `PWD` | Current working directory |
| `SHELL` | User shell |
| `TMPDIR` | Temporary directory |

On Windows, names differ somewhat.

Examples:

| Variable | Meaning |
|---|---|
| `USERNAME` | Username |
| `TEMP` | Temporary directory |
| `APPDATA` | Application data directory |

Portable programs should not assume every variable exists on every platform.

#### The `PATH` Variable

`PATH` is especially important.

It contains directories where the operating system searches for executables.

Example on Linux:

```text
/usr/local/bin:/usr/bin:/bin
```

The separator is usually:

```text
:
```

On Windows, it is usually:

```text
;
```

Programs that work with executable lookup often need platform-aware path splitting.

#### Security and Environment Variables

Environment variables come from outside the program.

Treat them as untrusted input.

Bad:

```zig
const level = try std.fmt.parseInt(u8, value, 10);
```

without checking the allowed range.

Bad:

```zig
const path = value;
```

without validating whether the path is acceptable.

An attacker may control the environment.

This matters especially for:

paths

temporary directories

shell commands

library loading

configuration values

Environment variables are convenient, but they are still external input.

#### Avoid Global Hidden State

One reason Zig keeps environment access explicit is that environment variables are global process state.

Hidden global state makes programs harder to reason about.

This style is clearer:

```zig
const port = try loadPortFromEnvironment();
runServer(port);
```

than code that reads environment variables from many unrelated places.

Read configuration once near startup when possible.

Then pass typed values through the program.

#### A Small Configuration Loader

This example loads a small config from the environment.

```zig
const std = @import("std");

const Config = struct {
    port: u16,
    debug: bool,
};

fn parseBool(text: []const u8) !bool {
    if (std.mem.eql(u8, text, "true")) return true;
    if (std.mem.eql(u8, text, "false")) return false;

    return error.InvalidBoolean;
}

fn loadConfig(allocator: std.mem.Allocator) !Config {
    const port_text = std.process.getEnvVarOwned(
        allocator,
        "APP_PORT",
    ) catch return error.MissingPort;
    defer allocator.free(port_text);

    const debug_text = std.process.getEnvVarOwned(
        allocator,
        "DEBUG",
    ) catch return error.MissingDebug;
    defer allocator.free(debug_text);

    return Config{
        .port = try std.fmt.parseInt(u16, port_text, 10),
        .debug = try parseBool(debug_text),
    };
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const config = try loadConfig(allocator);

    std.debug.print("port = {}\n", .{config.port});
    std.debug.print("debug = {}\n", .{config.debug});
}
```

Run it like this:

```bash
APP_PORT=8080 DEBUG=true ./my_program
```

Output:

```text
port = 8080
debug = true
```

#### Default Values

Some programs want defaults.

```zig
fn loadPort(allocator: std.mem.Allocator) !u16 {
    const text = std.process.getEnvVarOwned(
        allocator,
        "APP_PORT",
    ) catch return 8080;

    defer allocator.free(text);

    return std.fmt.parseInt(u16, text, 10);
}
```

This says:

If the variable is missing, use `8080`.

But if the variable exists and is invalid:

```text
APP_PORT=hello
```

parsing still fails.

That is usually the correct behavior.

Missing configuration and malformed configuration are different problems.

#### Environment Variables Are Strings

This is one of the most important ideas.

Everything starts as text.

The operating system does not know:

this variable is a port number

this variable is a boolean

this variable is JSON

this variable is a path

Your program defines the meaning.

That means your program also defines:

which values are valid

how parsing works

what errors should say

what defaults exist

#### Common Mistakes

Do not assume environment variables exist.

Do not assume they contain valid values.

Do not silently ignore malformed configuration.

Do not forget to free allocated environment strings.

Do not hard-code platform-specific variable names unless your program is platform-specific.

Do not scatter environment access across the whole program.

#### The Core Pattern

Read the variable:

```zig
const value = try std.process.getEnvVarOwned(
    allocator,
    "NAME",
);
defer allocator.free(value);
```

Parse the text:

```zig
const port = try std.fmt.parseInt(u16, value, 10);
```

Handle missing variables:

```zig
const value = std.process.getEnvVarOwned(
    allocator,
    "NAME",
) catch {
    return default_value;
};
```

Validate strictly:

```zig
if (!std.mem.eql(u8, value, "true")) {
    return error.InvalidValue;
}
```

#### What You Should Remember

Environment variables are named text values provided by the operating system.

Programs use them for configuration.

Environment values are strings and must often be parsed.

Missing variables and malformed variables are different cases.

Many environment APIs allocate memory, so free the returned strings.

Treat environment variables as external input.

Load configuration early, validate it clearly, and convert it into typed values before using it throughout the program.

