Every language
14 lenguajes, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
// parse at the boundary, pass real values around after:
const port = Number(process.env.PORT ?? '8000');
const host = process.env.HOST ?? '127.0.0.1';
// dev only — loads .env into process.env before these reads:
// require('dotenv').config();'' (empty string) is NOT missing — ?? keeps it, and Number('') is 0. If empty should fall back too, filter it explicitly. dotenv is a dev-time loader, never a production dependency.
TSTypeScript
function readInt(name: string, fallback: number): number {
const raw = process.env[name];
if (raw === undefined || raw === '') return fallback; // missing AND empty
const n = Number.parseInt(raw, 10);
return Number.isNaN(n) ? fallback : n;
}
const port = readInt('PORT', 8080);Needs @types/node (or `declare const process`) for process.env to typecheck. parseInt never throws — it returns NaN, so the Number.isNaN check IS the error path.
GoGo
import (
"fmt"
"os"
"strconv"
)
// read once at startup — everything downstream takes an int:
func mustPort() int {
raw, ok := os.LookupEnv("PORT") // (value, set?) — the three-way answer
if !ok {
return 8080
}
n, err := strconv.Atoi(raw) // 'abc' and '' both land here
if err != nil {
panic(fmt.Sprintf("bad PORT %q", raw))
}
return n
}os.Getenv flattens missing and empty into '' — LookupEnv's bool return splits them. Atoi is the typed boundary; never echo the value in errors when the var is a secret.
RsRust
use std::env;
fn read_port() -> u16 {
env::var("PORT")
.ok()
.and_then(|v| v.parse::<u16>().ok())
.unwrap_or(8080)
}
// strict: bubble both failures to the caller instead of a default
fn require_port() -> Result<u16, Box<dyn std::error::Error>> {
Ok(env::var("PORT")?.parse::<u16>()?)
}env::var fails NOT_PRESENT when unset and NotUnicode for non-UTF-8 values — the ? chain converts both. parse::<u16>() is the typed boundary; unwrap_or is the default.
PHPPHP
// trap: ?: fires on EVERY falsy string — '' and '0' hit the default too:
$host = getenv('HOST') ?: '127.0.0.1';
// getenv returns false (not null) when missing — test exactly that:
$raw = getenv('PORT');
$port = ($raw === false || $raw === '') ? 8000 : (int) $raw;
// ?? is the null-safe form for the superglobals, not for getenv:
$host = $_SERVER['HOST'] ?? '127.0.0.1';?: conflates '0' and '' with missing — the falsy-string trap. ?? never fires for getenv's false return; it is the safe form on $_ENV/$_SERVER, where a missing key is null.
PyPython
import os
port = int(os.environ.get('PORT', '8080')) # typed at the boundary
host = os.environ['HOST'] # strict: KeyError names the var
debug = os.environ.get('DEBUG') == '1' # explicit compare, never bool()os.getenv('K', d) is the same get-with-default over os.environ. The bool trap: bool('false') is True — every non-empty string is truthy — so compare against the exact allowed spellings.
CC
#include <stdlib.h>
// getenv returns char* or NULL — parse at the boundary, pass longs around:
long read_port(void) {
const char *raw = getenv("PORT");
if (raw == NULL || *raw == '\0') return 8080; // missing AND empty
char *end;
long n = strtol(raw, &end, 10);
return *end == '\0' ? n : 8080; // trailing junk = parse failure
}The string is owned by the environment — never free() it, and a later setenv can invalidate the pointer. Missing vs empty are NULL vs "" — two different answers. strtol signals by end-pointer, not by return: *end == '\0' is the whole check.
C++C++
#include <cstdlib>
#include <stdexcept>
#include <string>
int read_port() {
const char *raw = std::getenv("PORT");
if (raw == nullptr || *raw == '\0') return 8080;
try {
return std::stoi(raw); // the two throws live here
} catch (const std::invalid_argument &) { // "abc", ""
return 8080;
} catch (const std::out_of_range &) { // huge values
return 8080;
}
}std::stoi throws std::invalid_argument on garbage and std::out_of_range on overflow — and stoi("") throws rather than returning 0, unlike C's strtol. std::getenv has the same environ ownership as C: the pointer is not yours to free, and it is not std::string.
C#C#
int port = int.TryParse(
Environment.GetEnvironmentVariable("PORT"),
out var parsed) ? parsed : 8080;
string? host = Environment.GetEnvironmentVariable("HOST"); // null when missingGetEnvironmentVariable returns null when unset — TryParse(null) is false, so the default falls out with no null branch. Real apps read IConfiguration (appsettings.json + env overlay), not raw env access.
JvJava
int port = Integer.parseInt(
System.getenv().getOrDefault("PORT", "8080"));
// required — a named message beats NumberFormatException on null:
String host = System.getenv("HOST");
if (host == null) {
throw new IllegalStateException("HOST is not set");
}System.getenv is a snapshot taken at JVM start — setenv calls afterwards are invisible to it, so read once in main. Keep env values out of exception messages and logs when they are secrets.
SwSwift
import Foundation
let env = ProcessInfo.processInfo.environment // [String: String]
let port = Int(env["PORT"] ?? "") ?? 8000
let host = env["HOST"] ?? "127.0.0.1"environment is a plain [String: String] — missing keys are nil, values stay strings. The double-?? chains default + parse; same API on macOS and Linux command-line tools.
KtKotlin
fun requireEnv(name: String): String =
System.getenv(name) ?: error("missing env var: $name")
fun readEnv(name: String, default: String): String =
System.getenv(name) ?: default
val port = readEnv("PORT", "8080").toInt()
val host = requireEnv("HOST")Same JVM snapshot rule as Java. The elvis operator splits optional from required in one line each; error() names the missing var at startup instead of an NPE three frames later.
RbRuby
port = ENV['PORT'] || '8080' # nil (missing) falls back; '' does not
host = ENV.fetch('HOST') # required — KeyError when absent
debug = ENV['DEBUG'] == '1' # values are strings; there is no to_boolENV is Hash-like with STRING keys AND string values only — parse once (Integer(port, 10)) and pass the number around. ENV.fetch('K', default) is the explicit-default spelling of ||.
ZigZig
const std = @import("std");
fn readPort(gpa: std.mem.Allocator) u16 {
const raw = std.process.getEnvVarOwned(gpa, "PORT")
catch return 8000; // error.EnvironmentVariableNotFound when unset
defer gpa.free(raw); // the caller owns the returned string
return std.fmt.parseInt(u16, raw, 10) catch 8000;
}getEnvVarOwned takes an allocator and errors when unset — you OWN the returned buffer, so defer free it. Read once into a config struct at startup; threading the allocator deep into call sites is the re-read anti-pattern.