Skip to content

Environment Variables Module

← Back to Standard Library

System environment variable access and manipulation.

Import

use <sys/env>

The import brings EnvError, the module's error enum: the bare name needs it, and use <sys/env> as env puts it behind the dot (env.EnvError.NotFound).

Overview

The env module provides functions for reading and modifying environment variables. It uses POSIX getenv() and setenv() functions for Unix portability across macOS, Linux, and other Unix-like systems.

Functions

getenv

Get an environment variable value.

fn getenv(string key) -> Maybe@(string)

Parameters: - key - Environment variable name

Returns: - Maybe.Some(value) if variable exists - Maybe.None() if variable does not exist

Example:

use <sys/env>

fn main() i32:
    match getenv("PATH"):
        Maybe.Some(path) ->
            println("PATH: {path}")
        Maybe.None() ->
            println("PATH not set")

    return Result.Ok(0)

Common environment variables:

use <sys/env>

fn main() i32:
    # User information
    let Maybe@(string) home = getenv("HOME")
    let Maybe@(string) user = getenv("USER")

    # System paths
    let Maybe@(string) path = getenv("PATH")
    let Maybe@(string) tmpdir = getenv("TMPDIR")

    # Shell information
    let Maybe@(string) shell = getenv("SHELL")

    # Display variables
    let Maybe@(string) display = getenv("DISPLAY")

    if (home.is_some()):
        println("Home: {home.realise('')}")

    return Result.Ok(0)

setenv

Set an environment variable value.

fn setenv(string key, string value) -> Result@(i32)

Parameters: - key - Environment variable name - value - New value to set

An existing variable with the same name is always overwritten.

Returns: - Result.Ok(0) on success - Result.Err() on failure (e.g., insufficient memory, invalid name)

Example:

use <sys/env>

fn configure() i32 | EnvError:
    # Set a custom environment variable -- ?? propagates EnvError, so the
    # enclosing function must declare it
    setenv("MY_APP_CONFIG", "/etc/myapp.conf")??

    # Verify it was set
    match getenv("MY_APP_CONFIG"):
        Maybe.Some(config_path) ->
            println("Config path: {config_path}")
        Maybe.None() ->
            println("Failed to set variable")

    return Result.Ok(0)

fn main() i32:
    return Result.Ok(configure().realise(1))

Overwriting an existing value:

use <sys/env>

fn set_twice() i32 | EnvError:
    # Set an initial value
    setenv("MY_VAR", "initial")??

    # A second call always overwrites the existing value
    setenv("MY_VAR", "overwritten")??

    let string value = getenv("MY_VAR").realise("")
    println("MY_VAR: {value}")  # MY_VAR: overwritten

    return Result.Ok(0)

fn main() i32:
    return Result.Ok(set_twice().realise(1))

Error Handling

Both functions integrate with Sushi's error handling system:

getenv Error Handling

Since getenv returns Maybe@(string), use pattern matching or .realise():

use <sys/env>

fn main() i32:
    # With pattern matching
    match getenv("CONFIG_FILE"):
        Maybe.Some(path) ->
            println("Using config: {path}")
        Maybe.None() ->
            println("Using default config")

    # With .realise() for default value
    let string config = getenv("CONFIG_FILE").realise("/etc/default.conf")
    println("Config: {config}")

    # With .expect() for required variables
    let string required = getenv("REQUIRED_VAR").expect("REQUIRED_VAR must be set")

    return Result.Ok(0)

setenv Error Handling

Since setenv returns Result@(i32, EnvError), use error propagation -- inside a function that declares | EnvError -- or pattern matching:

use <sys/env>

fn set_var() i32 | EnvError:
    # With error propagation (??)
    setenv("MY_VAR", "value")??
    return Result.Ok(0)

fn main() i32:
    # With explicit error handling
    let Result@(i32, EnvError) set_result = setenv("MY_VAR", "value")
    match set_result:
        Result.Ok(_) ->
            println("Variable set successfully")
        Result.Err(_) ->
            println("Failed to set variable")

    return Result.Ok(set_var().realise(1))

Platform-Specific Behavior

macOS (darwin)

Platform-specific implementation in sushi_stdlib/src/_platform/darwin/env.py: - Uses standard POSIX getenv() and setenv() - Follows BSD semantics

Linux

Platform-specific implementation in sushi_stdlib/src/_platform/linux/env.py: - Uses standard POSIX getenv() and setenv() - Follows GNU/Linux semantics

Windows (partial support)

Windows support is planned but not yet fully implemented.

Security Considerations

Sensitive Data

Environment variables may contain sensitive information:

use <sys/env>

fn main() i32:
    # Be cautious when logging or displaying env vars
    let Maybe@(string) api_key = getenv("API_KEY")

    # Don't print sensitive values
    if (api_key.is_some()):
        println("API key is configured")
        # Bad: println("API key: {api_key.realise("")}")

    return Result.Ok(0)

Validation

Always validate environment variable values:

use <sys/env>

fn is_valid_port(peek string port) bool:
    # Add validation logic
    return Result.Ok(true)

fn main() i32:
    match getenv("SERVER_PORT"):
        Maybe.Some(port) ->
            # port is a match binding, a borrow: pass it by peek rather than
            # by value (a by-value call would need port.clone() instead)
            if (is_valid_port(peek port).realise(false)):
                println("Using port: {port}")
            else:
                println("Invalid port in SERVER_PORT")
        Maybe.None() ->
            println("Using default port: 8080")

    return Result.Ok(0)

Name Restrictions

Environment variable names should: - Contain only uppercase letters, digits, and underscores - Not start with a digit - Not contain = or null bytes

Invalid names will cause setenv to return Result.Err().

Example: Configuration from Environment

use <sys/env>
use <collections/strings>

struct Config:
    string host
    i32 port
    bool debug

fn load_config() Config:
    let string host = getenv("APP_HOST").realise("localhost")

    let string port_str = getenv("APP_PORT").realise("8080")
    let i32 port = port_str.to_i32().realise(8080)

    let string debug_str = getenv("APP_DEBUG").realise("false")
    let bool debug = debug_str == "true" or debug_str == "1"

    let Config config = Config(host, port, debug)
    return Result.Ok(config)

fn main() i32:
    let Config config = load_config().realise(Config("localhost", 8080, false))

    println("Host: {config.host}")
    println("Port: {config.port}")

    if (config.debug):
        println("Debug mode enabled")

    return Result.Ok(0)

Testing with Environment Variables

Test files can use setenv to set up test conditions:

use <sys/env>

fn test_env_vars() i32 | EnvError:
    # Setup test environment
    setenv("TEST_VAR", "test_value")??

    # Run tests
    let string value = getenv("TEST_VAR").realise("")

    if (value == "test_value"):
        println("Test passed")
    else:
        println("Test failed")

    return Result.Ok(0)

fn main() i32:
    return Result.Ok(test_env_vars().realise(1))

See Also