Skip to content

ddn.var

A compact, dynamic value type for the D programming language.

Overview

The ddn.var module provides a versatile dynamic value type (var) that can hold values of many different types at runtime. It is designed to be compact, efficient, and easy to use for scenarios where static typing is impractical—such as configuration handling, JSON-like data structures, or scripting interfaces.

It serves as the core data structure for other DDN modules like ddn.config.ini, ddn.data.json5, and ddn.data.csv.

Features

Supported Types

The var type supports a comprehensive set of value types within a compact 24-byte footprint (on 64-bit systems):

Type Category Supported Types
Null NULL — absence of a value
Boolean BOOLtrue or false
Integers BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG
Floating Point FLOAT, DOUBLE, REAL (stored as double internally)
Characters CHAR, WCHAR, DCHAR
Strings STRING — UTF-8 encoded strings
Collections ARRAY — dynamic array of var, OBJECT — associative array (stringvar)

Type Conversion

Safe Conversion (as!T)

Use the as!T template to convert a var to a specific D type. If the conversion is impossible, it returns the default value for that type (T.init).

import ddn.var;

var v = 42;
int i = v.as!int;       // Extract as int
string s = v.as!string; // Convert to string representation ("42")

var arr = [1, 2, 3];
var[] elements = arr.as!(var[]); // Extract array elements

Attempted Conversion (tryAs!T)

For scenarios where you need to distinguish between a missing/invalid value and a valid default value (like 0), use tryAs!T, which returns a Nullable!T.

import std.typecons : Nullable;

var v = "not a number";
auto result = v.tryAs!int;

if (result.isNull) {
    // Conversion failed
} else {
    // Use result.get
}

Object/Map Access

Access object fields using indexing or the convenient opDispatch syntax. var supports "auto-vivification" (automatic creation of nested objects) when assigning.

var obj;
obj["name"] = "Alice";
obj["stats"]["level"] = 10; // Automatically creates "stats" object

// Read values
string name = obj["name"].as!string;

// opDispatch syntax (field-like access)
obj.email = "alice@example.com";
int level = obj.stats.level.as!int;

Safe Navigation (getField)

To safely access nested fields without risking an assertion failure or creating unwanted entries, use getField.

// Returns 30 if "timeout" exists, otherwise 30 (default)
int timeout = config.getField("timeout", 30).as!int;

Array Operations

Work with dynamic arrays naturally using standard D syntax.

var arr = [1, 2, 3, 4, 5];

// Access by index
var first = arr[0];

// Slicing
var slice = arr[1 .. 3]; // [2, 3]

// Append
arr ~= 6;

// Iterate
foreach (ref el; arr.as!(var[])) {
    // process element
}

Arithmetic Operations

Perform arithmetic on numeric var values. The result type is automatically promoted (e.g., int + double = double).

var a = 10;
var b = 3;

var sum = a + b;      // 13
var diff = a - b;     // 7
var quot = a / b;     // 3 (integer division)

Note: Division by zero returns var.init (NULL) instead of crashing.

JSON Serialization

var acts as a JSON Document Object Model (DOM).

var data;
data["name"] = "example";
data["values"] = [1, 2, 3];

// Convert to JSON string
string json = data.toString();
// Output: {"name":"example","values":[1,2,3]}

// Write to OutputRange (efficient, no string allocation)
import std.array : appender;
auto buffer = appender!string;
data.toStringTo(buffer);

Deep Copy with dup()

Assignment of var values is shallow for arrays and objects (they share the underlying data). Use dup() for deep copies.

var original;
original["x"] = 1;

var shallow = original;      // Points to same object
var deep = original.dup;     // Independent copy

shallow["x"] = 2;
assert(original["x"].as!int == 2); // Changed
assert(deep["x"].as!int == 1);     // Unchanged

Error Handling

By default, the ddn.var module follows a fail-fast strategy for runtime type misuse:

  • Type Exceptions: Operations like indexing a non-object or using array APIs on non-arrays throw a VarException (specifically VarTypeException or VarKeyException). This ensures that logic errors in data processing are caught early.
  • Null safety: Operations on NULL types generally return valid defaults (e.g., empty array for as!(var[])) or false.
  • Arithmetic safety: Division or modulo by zero returns var.init (NULL) instead of crashing or throwing.

For scenarios where you prefer automatic type conversion over exceptions, see Permissive Mode.

Permissive Mode (DDN_VAR_TYPE_COERCE)

If you prefer a more permissive, "JavaScript-like" behavior, you can compile the ddn library with the D version identifier DDN_VAR_TYPE_COERCE. When this mode is enabled:

  • Auto-coercion: Mutating operations (like v["key"] = value or v ~= item) will automatically convert a scalar var into the required collection type (OBJECT or ARRAY), overwriting its previous value.
  • Neutral read-only access: Read-only accessors (like const v["missing"]) return a NULL value instead of throwing when there's a type mismatch or a missing key.
  • Warnings: In debug builds, these automatic type changes emit warnings to the standard logger to help you track down unintended coercions.

Example (Permissive Mode)

// Compiled with -version=DDN_VAR_TYPE_COERCE
var v = 42;
v.name = "Alice"; // Auto-coerces v from INT to OBJECT
assert(v.type == var.Type.OBJECT);

var missing = v.nonexistent; // Returns Type.NULL instead of throwing
assert(missing.type == var.Type.NULL);

Thread Safety

  • var does not perform internal synchronization.
  • Concurrent mutation of the same instance is a data race.
  • Independent copies can be used in different threads.