Dudu manual

Dudu is a statically typed systems language with Python syntax, readable C++ output, and direct C and C++ interop.

Manual for 0.1.0-alpha.13. Alpha syntax and generated ABI may change.

Quickstart

Install a tagged source release on Linux or macOS:

Shell
curl --proto '=https' --tlsv1.2 -sSf https://dudulang.org/install.sh | sh
dudu --version

The installer checks native build dependencies, offers to install them through the system package manager, verifies the release checksum, and installs under ~/.local.

Create a project

Shell
dudu new hello
cd hello
dudu run

The generated entry file is ordinary Dudu:

src/main.dd
def main() -> i32:
    print("hello from dudu")
    return 0

Normal edit-loop commands are:

Shell
dudu fmt
dudu check
dudu run
dudu test

dudu is the project driver. duc is the lower-level compiler driver, comparable to the distinction between Cargo and rustc.

Projects and workflow

A normal project has a manifest, source directory, and user-owned CMake file:

Project layout
hello/
├── CMakeLists.txt
├── dudu.toml
└── src/
    └── main.dd

dudu.toml names the Dudu entry and common native settings. CMake remains available for native details that do not belong in a language manifest.

dudu.toml
name = "hello"
entry = "src/main.dd"

[cxx]
standard = "c++20"

[build]
dir = "build"

[include]
paths = ["include"]

dudu build, dudu run, and dudu test use the CMake backend. Generated files live below the build directory and may be overwritten. User-owned CMakeLists.txt files are not rewritten after project creation.

Named targets

dudu.toml
name = "tools"

[targets.app]
entry = "src/main.dd"
kind = "executable"

[targets.tests]
entry = "tests/main.dd"
kind = "executable"

[targets.app.pkg]
libs = ["raylib"]
Shell
dudu run app
dudu build tests

Manifest-relative paths resolve from the directory containing dudu.toml. Explicit local header imports resolve from the source file containing the import.

Syntax basics

Dudu files use the .dd extension. Indentation defines blocks. Comments begin with #.

Dudu
# Bindings are snake_case. Types are PascalCase.
MAX_PLAYERS: i32 = 64

class Player:
    name: str
    hp: i32

    def damage(self, amount: i32):
        self.hp -= amount

Assignments use =. Augmented assignments include +=, -=, *=, /=, and %=. A binding has one static type for its lifetime.

FormRule
local_nameVariables, functions, methods, and modules use snake_case.
TypeNameClasses, enums, variants, and type aliases use PascalCase.
MAX_COUNTConstants use upper snake case and cannot be rebound.
_The wildcard discards a value. A named binding beginning with _ is still an ordinary binding.

Types and inference

Dudu uses fixed-width scalar names. It does not provide ambiguous native aliases such as int, float, or double in Dudu source.

GroupTypes
Signed integersi8, i16, i32, i64, isize
Unsigned integersu8, u16, u32, u64, usize
Floating pointf32, f64
Other scalarsbool, char, void
Stringsstr owns text; cstr is a native C string view.

Local inference

Initializers infer local types when the answer is unambiguous:

Dudu
count = 10
scale = 2.0
name = "dudu"
player = Player("Ada", 100)

Annotations remain required at public boundaries, for class fields, and where there is no initializer to inspect:

Dudu
window: *SDL_Window = None
pixels: array[Color][320, 240]

def update(player: &Player, dt: f32):
    speed = 240.0
    player.x += speed * dt

Conversions

Dudu-native assignments do not silently change types. Cast with type-call syntax:

Dudu
x: i32 = 10
y: i64 = i64(x)
z: f32 = f32(y)

Functions

Function parameters and returned values are typed. Omit the return annotation for functions that return no value.

Dudu
def clamp(value: i32, lo: i32, hi: i32) -> i32:
    if value < lo:
        return lo
    if value > hi:
        return hi
    return value

def log_value(value: i32):
    print(value)

Calls and multiline calls follow Python syntax. Dudu uses explicit return; the last expression is not returned implicitly.

Function values

Dudu
def less(a: i32, b: i32) -> bool:
    return a < b

compare: fn(i32, i32) -> bool = less
ordered = compare(3, 8)

Dudu does not have lambda or anonymous def expressions. Capturing callables use explicit library types such as imported std.function.

Multiple return values

Dudu
def divmod_i32(a: i32, b: i32) -> tuple[i32, i32]:
    return a / b, a % b

quotient, remainder = divmod_i32(10, 3)

Control flow

Conditionals and loops are statement-oriented. Conditions must be bool.

Dudu
if health <= 0:
    state = State.Dead
elif health < 25:
    state = State.Hurt
else:
    state = State.Ready

for i in range(count):
    total += values[i]

while queue.not_empty():
    process(queue.pop())

Loop variable types are inferred, or may be explicit when copy/reference behavior matters:

Dudu
for player: &Player in players:
    player.hp -= 1

for player: &const[Player] in players:
    draw_player(player)

Labeled loops

Dudu
outer: for y in range(height):
    for x in range(width):
        if blocked(x, y):
            break outer
        if skip_column(x):
            continue outer

Classes

A Dudu class is a C++ struct-like value type by default. Fields define layout; construction follows field order or named-field syntax.

Dudu
class Vec2:
    x: f32
    y: f32

    def length_squared(self: &const[Self]) -> f32:
        return self.x * self.x + self.y * self.y

    def scale_in_place(self, scale: f32) -> &Self:
        self.x *= scale
        self.y *= scale
        return self

a = Vec2(3.0, 4.0)
b = Vec2(x=8.0, y=2.0)

Bare self means self: &Self. Spell self: &const[Self] for a read-only receiver. Returning Self returns a value; returning &Self returns a mutable reference; returning &const[Self] returns a read-only reference.

Static members

Dudu
class Counter:
    count: static[i32] = 0
    MAX_COUNT: i32 = 1024

    def bump() -> i32:
        Counter.count += 1
        return Counter.count

A class function without self is static. Mutable shared fields use static[T]. Upper-case class constants are static constants.

Operators and swizzling

Dudu uses explicit decorators instead of Python dunder names for operator overloads.

Dudu
class Vec2:
    x: f32
    y: f32

    @operator("+")
    def add(self: &const[Self], other: &const[Vec2]) -> Vec2:
        return Vec2(self.x + other.x, self.y + other.y)

    @operator("+=")
    def add_assign(self, other: &const[Vec2]) -> &Self:
        self.x += other.x
        self.y += other.y
        return self

Supported overload families include arithmetic, comparison, compound assignment, conversion to bool, and indexing through @operator("[]") and @operator("[]=").

GLSL-style swizzling

Dudu
xy = position.xy
rgb = color.rgb
repeated = position.xx
position.xy = Vec2(10.0, 20.0)

Read swizzles may repeat components. Assignment swizzles may not, because writing the same component twice is ambiguous.

Enums, match, and errors

Plain enums

Dudu
enum Direction: u8
    North = 1
    South = 2
    East = 3
    West = 4

Payload enums and match

Dudu
enum Token:
    Eof
    Ident:
        text: str
    IntLit:
        value: i64
    Punct:
        ch: u8

def score(token: Token) -> i32:
    match token:
        case Token.Eof:
            return 0
        case Token.Ident(text):
            return i32(len(text))
        case Token.IntLit(value) if value > 40:
            return i32(value)
        case Token.IntLit(value):
            return i32(value) + 1
        case Token.Punct(ch=value):
            return i32(value)

Payload declarations always use named fields. Constructors and match patterns may use those fields positionally or by name. Match checking understands payload arity, guards, wildcard reachability, and exhaustiveness.

Recursive variants

Dudu
enum Expr:
    Number:
        value: f64
    Add:
        left: *Expr
        right: *Expr

def evaluate(expr: Expr) -> f64:
    match expr:
        case Expr.Number(value):
            return value
        case Expr.Add(left, right):
            return evaluate(*left) + evaluate(*right)

Recursive variants use pointer indirection because a value cannot contain itself directly. Payload fields may otherwise contain any valid Dudu type.

Result and Option

Dudu
enum ReadError:
    NotFound
    PermissionDenied

def read_config(path: str) -> Result[str, ReadError]:
    if not fs.exists(path):
        return Err(ReadError.NotFound)
    return Ok(fs.read_all(path))

def choose(flag: bool) -> Option[i32]:
    if flag:
        return 42
    return None

Use Result and Option for normal Dudu APIs. try, except, and raise exist for C++ exception interop; Dudu does not add finally.

Containers and arrays

TypeMeaning
list[T]Dynamic owning sequence, normally lowered through std::vector<T>.
dict[K, V]Dynamic key/value container.
set[T]Dynamic set.
tuple[T...]Fixed heterogeneous product value with destructuring.
array[T][N]Fixed contiguous array with compile-time extent.
array[T][M, N]Fixed contiguous multidimensional array.

Lists

Dudu
numbers = [1, 2, 3]
# inferred: list[i32]

players: list[Player] = []
players.append(Player("Ada", 100))
players.append(Player("Lin", 80))

first = players[0]
last = players.back()
for player in players:
    print(player.name)

Nonempty literals infer their element type. Empty collections need an explicit type unless a surrounding expected type supplies it. Conflicting element constraints are errors; use an explicit payload enum when mixed values are intentional.

Collection inference and diagnostics

Dictionaries

Dudu
scores = {
    "ada": 99,
    "lin": 80,
}
# inferred: dict[str, i32]

scores["grace"] = 100

if scores.contains("ada"):
    print(scores["ada"])

Sets

Dudu
visited = {"start", "hall"}
# inferred: set[str]

visited.insert("vault")

if visited.contains("vault"):
    print(visited.size())

Tuples and destructuring

Dudu
def divmod_i32(value: i32, divisor: i32) -> tuple[i32, i32]:
    return value / divisor, value % divisor

quotient, remainder = divmod_i32(43, 10)

Fixed arrays and inferred shapes

Dudu
kernel: array[f32] = [
    [1.0, 0.0, -1.0],
    [1.0, 0.0, -1.0],
    [1.0, 0.0, -1.0],
]
# inferred: array[f32][3, 3]

scratch: array[f32][64, 64]

The initializer supplies shape when present. An uninitialized fixed array needs explicit extents.

Fixed-array and numeric-literal reference

Indexing and views

Dudu parses Python/NumPy-shaped index items without hard-coding tensor policy into the compiler. Built-in arrays and library types dispatch through the same operator surface.

Dudu
pixel = image[y, x, channel]
red = image[:, :, 0]
patch = image[y0:y1:2, x0:x1:2, :]
last = scores[..., -1]
expanded = bias[None, :]
train_x = x[mask, :]
weights[mask, :] = 0.0
logits[rows, cols] += 1.0

A scalar index removes an axis, a slice preserves one, None adds an axis, and ... fills remaining axes. Result shape is available to the type checker and editor when it can be proven statically; runtime-shaped libraries may retain shape in their own values.

Complete arrays, views, and indexing tutorial

C++ standard library containers

Dudu's built-in collection names cover common Python-shaped code. They do not restrict native code to that set. Import a C++ header and use its real container, iterator, and algorithm APIs directly.

Dudu using C++ sequences
from cpp import algorithm
from cpp import array
from cpp import deque
from cpp import span
from cpp import vector

values: std.vector[i32]
values.push_back(4)
values.push_back(1)
std.sort(values.begin(), values.end())

deck: std.deque[i32]
deck.push_front(2)
deck.push_back(40)

fixed: std.array[i32, 3] = std.array[i32, 3]({1, 2, 3})
view: std.span[i32] = std.span[i32](fixed)
Dudu using C++ associative containers
from cpp import map
from cpp import queue
from cpp import unordered_set

counts: std.map[i32, i32]
counts[7] = 42

seen: std.unordered_set[i32]
seen.insert(7)

jobs: std.priority_queue[i32]
jobs.push(10)
jobs.push(42)
highest = jobs.top()

The same import model covers std::list, std::forward_list, std::multimap, std::multiset, container adapters, ranges, and third-party C++ container libraries. Dudu does not wrap them into a separate ecosystem.

Memory and references

Dudu exposes the native memory model. It does not impose Rust ownership or lifetime syntax.

FormMeaning
*TRaw pointer to T.
&TMutable reference to T.
&const[T]Read-only reference to T.
*const[T]Pointer to read-only T.
const[*T]Const-qualified pointer binding when that distinction is required.

Pointer versus reference

Both forms provide indirect access to another object, but they carry different native contracts:

  • A reference must bind to a valid object, cannot be None, and cannot be reseated. Use normal member and value syntax through it.
  • A pointer stores an address. It may be None, may be assigned a different address, and must be dereferenced with * when accessing the pointed-to value.
  • Neither form owns an object by itself. A pointer returned by new[T] has an explicit allocation contract and must eventually be passed to delete. Native handles and borrowed pointers follow the imported API's contract.
  • Use references for required borrowed parameters. Use pointers for nullable values, native handles, arrays, allocation, pointer arithmetic, and APIs that specifically expose pointers.

Read qualifiers from the inside out. *const[T] is a pointer to read-only T. const[*T] is a pointer binding that cannot be reseated. const[*const[T]] is a const pointer to read-only T. These map to const T*, T* const, and const T* const in generated C++.

Dudu
value: i32 = 42
pointer: *i32 = &value
*pointer = 99

alias: &i32 = value
alias += 1

read_only: &const[i32] = value
maybe_value: *i32 = None
window: *SDL_Window = None

Allocation

Dudu
# Value construction and RAII are the default.
player = Player(100, "Ada")

# Construct one heap object.
heap_player: *Player = new[Player](100, "Ada")
delete heap_player

# Raw storage for native or allocator work.
bytes: *u8 = malloc[u8](1024)
free(bytes)

Value containers own their values. Pointer containers do not own pointees. Custom arenas and allocators are ordinary libraries. Lexical scope and C++ destructors provide cleanup; Dudu does not add a general defer statement.

Allocation, ownership, and lifetime reference

Generics and compile time

Native generics

Dudu
def clamp[T](value: T, lo: T, hi: T) -> T:
    if value < lo:
        return lo
    if value > hi:
        return hi
    return value

class Box[T]:
    value: T

    def get(self: &const[Self]) -> &const[T]:
        return self.value

Generic functions and classes lower to C++ templates. Compile-time value parameters may describe fixed extents and participate in checked constant expressions.

Generics and value-parameter reference

Constants and build values

Dudu
WIDTH: i32 = 320
HEIGHT: i32 = 240
PIXELS: i32 = WIDTH * HEIGHT

@constexpr
def align_up(value: usize, align: usize) -> usize:
    return (value + align - 1) & ~(align - 1)

ROW_BYTES: usize = align_up(usize(WIDTH) * 4, 64)
static_assert(PIXELS == 76800)

if build.DEBUG:
    enable_validation()

Build values come from dudu.toml or compiler definitions. Branches depending only on build.* values are selected at compile time.

Compile-time programming reference

Modules and imports

Dudu modules use Python-shaped imports:

Dudu
import camera
import renderer.camera as render_camera
from vec3 import Vec3
from raytrace import shade_pixel as shade

Imports are qualified by default. Selective imports bind names directly. Ambiguous bindings are compile errors rather than order-dependent shadowing.

Native header forms

Dudu
# System/header-search includes: <thread>, <math.h>
from cpp import thread
from c import math.h

# Explicit source-relative quoted includes.
from cpp.path import vendor/math.hpp as vendor_math
from c.path import local/device.h as device

The c, cxx, cpp, and path portions are semantic import markers. Editor navigation can open the header target and resolve individual imported declarations.

Exact import and collision rules

C interop

Imported C declarations come from real headers scanned by Clang. Types, structs, enums, functions, constants, and callable macros retain their native identity.

Dudu using SDL3
from c import SDL3/SDL.h

def main() -> i32:
    if not SDL_Init(SDL_INIT_VIDEO):
        return 1

    window = SDL_CreateWindow("Dudu", 800, 450, 0)
    if window == None:
        SDL_Quit()
        return 2

    SDL_DestroyWindow(window)
    SDL_Quit()
    return 0

C libraries often prefix their globals, so an unaliased import is readable. Alias a header when it would collide or expose an unmanageable global namespace:

Dudu
from c import sys/stat.h as stat

info: stat.stat
if stat.stat("asset.bin", &info) != 0:
    return 1

The compiler remembers C tag spelling internally. Normal Dudu source uses SDL_Event or stat.stat, not struct SDL_Event.

C++ interop

C++ namespaces remain their real namespaces. Importing more than one standard header merges declarations into std as C++ does.

Dudu using the C++ standard library
from cpp import algorithm
from cpp import string
from cpp import thread
from cpp import vector

names: std.vector[std.string]
workers: list[std.thread] = []
std.sort(names.begin(), names.end())

Header aliases are collision boundaries, not fake replacements for namespaces:

Dudu
from cpp.path import vendor/math.hpp as vendor_math

point: vendor_math.vendor.Vec3
length = vendor_math.vendor.length(point)

Header awareness handles overloads, constructors, methods, operators, templates, const/reference forms, macros that represent complete expressions, and source locations for editor navigation. If a native API cannot be represented directly, cpp(...) is the explicit escape hatch, not normal interop style.

Native templates and macros reference

Dudu called from C++

Top-level Dudu classes and functions generate ordinary C++ declarations. Use duc --emit-header for a C++ header and @extern_c plus --emit-c-header for a C ABI.

Shell
duc emit src/library.dd -o build/library.cpp
duc src/library.dd --emit-header build/library.hpp
duc src/library.dd --emit-c-header build/library.h

Build configuration

Dudu uses CMake as its native ecosystem backend. Small projects can describe common settings in dudu.toml; large projects can keep a user-owned CMake build.

dudu.toml
name = "viewer"
entry = "src/main.dd"

[pkg]
libs = ["sdl3", "sqlite3"]

[include]
paths = ["include", "third_party/include"]

[sources]
cpp = ["src/native_backend.cpp"]

[deps]
ndad = { git = "https://github.com/dudu-language/ndad.git", tag = "v0.1.0" }
local_math = { path = "../local_math" }

[build]
DEBUG = true
RENDER_BACKEND = "vulkan"

Use CMake directly for toolchain files, platform conditionals, installed native packages, generated sources, and complex target graphs. dudu cmake exposes generated integration when inspection is useful.

Git and path dependencies

Missing source dependencies are resolved before normal project commands and pinned in dudu.lock. Existing pins are not silently advanced to a newer remote revision.

Shell
dudu deps fetch
dudu build

Dudu is alpha software. Commit the manifest and dependency lock metadata used by your project, and inspect generated native build files when shipping production binaries.

Tests

Tests are free functions marked with @test. They take no arguments and return no value, bool, or i32.

Dudu
@test
def add_works():
    assert add(20, 22) == 42

@test
def status_code() -> i32:
    return add(2, 2) - 4

@test.ignore
def slow_case():
    run_slow_fixture()
Shell
dudu test
dudu test add
dudu test --filter add
dudu test --no-capture

assert is always checked. debug_assert lowers to native C++ assertion behavior and may disappear when NDEBUG is defined.

Complete test behavior

Editor support

The Dudu VS Code extension starts dudu-lsp and supplies semantic highlighting, diagnostics, formatting, completion, signature help, hover, inlay hints, go to definition, references, rename, and native header navigation.

  • Install the extension from the Visual Studio Marketplace or Open VSX.
  • Open the directory containing dudu.toml, not only an isolated source file.
  • Run dudu fmt from a project root to format every .dd file.
  • Use the extension's inlay-hint toggle to show inferred types and parameter names.

Native hover and navigation depend on Clang header scanning and the same include configuration used by the build. If an imported symbol is unresolved in the editor but builds in CMake, check that the manifest and CMake compile context expose the same include paths and definitions.

Command reference

CommandPurpose
dudu init [path]Initialize a project in an existing or specified directory.
dudu new <name>Create a new project directory.
dudu check [input]Parse and type-check without a native link step.
dudu build [target]Generate modules and compile through CMake.
dudu run [target] -- [args]Build and run an executable target.
dudu test [filter]Build and run matching @test functions.
dudu fmt [path] [--check]Apply or verify canonical formatting.
dudu cleanRemove project build outputs.
dudu clean-cacheRemove compiler analysis caches.
dudu deps fetchFetch declared path/Git dependencies.
dudu updateUpdate an installer-owned toolchain; supports --check and --rollback.
dudu cmakeEmit inspectable CMake integration.
dudu bench compilerRun compiler benchmarks.

Compiler driver

Shell
duc check src/main.dd
duc emit src/main.dd -o build/main.cpp
duc emit-modules src/main.dd -o build/generated
duc fmt src/main.dd --check
duc src/library.dd --emit-header build/library.hpp

Add --timings to project commands when a build feels slow. Dudu then reports elapsed analysis, generation, CMake, compile, and run stages separately.

Differences from Python

Dudu keeps Python's common statement and expression shapes, not Python's dynamic object model.

Not in DuduUse instead
Runtime type changes and monkeypatchingStatic bindings, fields, and explicit variants.
lambdaNamed functions stored as function values.
List/dict/set comprehensionsExplicit containers and loops.
Generator expressions and yieldContainers, callbacks, or imported iterator libraries.
Ternary expressionsAn explicit binding plus if/else.
with context managersRAII values and lexical scope.
async/awaitThreads, atomics, event loops, callbacks, or native task libraries.
Metaclasses, descriptors, eval, and dynamic attributesCompile-time declarations and ordinary functions.
Arbitrary-precision default integersExplicit fixed-width integer types.

See the language tour for side-by-side Python, C, C++, Rust, GLSL, and array-programming examples.

Formal reference

This manual is the public learning and working guide. Exact language references remain in the repository:

Dudu is alpha software. The reference pages describe tested current behavior; the limitations page records known boundaries.