Introduction

Duck started as a joke. After months of working on serious language projects with proper type systems and formal semantics, I wanted to build something that prioritized fun over professionalism. The central question was simple: what if code needed permission to run?

The answer became Duck, a dynamically typed language where every code block must be preceded by the keyword "quack" or the interpreter refuses to execute it. The interpreter is called Goose, and it has opinions about your code. At the end of every program, the Goose rates your work on a scale of one to ten. Most programs get a three.

What surprised me was how this joke forced real technical decisions. Parser state management, error message design, module systems. The constraint of building around a silly premise led to genuinely interesting implementation challenges.

The Quack Authorization System

The core mechanic of Duck is block authorization. Code lives inside brackets, and those brackets only execute if preceded by "quack." This sounds trivial until you consider the implications for the parser.

When the parser encounters quack tokens, it needs to track how many it has seen. Multiple consecutive quacks authorize multiple consecutive blocks. The statement quack quack quack [print 1] [print 2] [print 3] executes all three blocks because the parser maintains a quack counter that decrements as blocks are consumed.

This creates interesting debugging possibilities. Removing a quack from a block effectively comments it out without changing the code structure. The block still gets parsed, so syntax errors are still caught, but execution is skipped. The Goose notices these unauthorized blocks and reports them with passive-aggressive commentary.

The authorization system also affects nested structures. Inside a function definition or loop body, each block still needs its own quack. This was a deliberate choice. It keeps the language consistent and makes it visually obvious which lines are active.

Interpreter Architecture

Duck uses a tree-walking interpreter written in Rust. This approach prioritizes flexibility over performance. There is no bytecode compilation step and no virtual machine. The interpreter builds an abstract syntax tree from the source code and walks through it directly during execution.

Tree-walking interpreters are considered slow by language implementation standards, but for a language like Duck, the tradeoffs make sense. Adding new features is straightforward because there is no bytecode format to extend. The sleep function took ten minutes to implement. String interpolation took twenty. When you control the execution model directly, experimentation is cheap.

The interpreter maintains an environment stack for lexical scoping. Each function call creates a new environment frame that chains to its parent. Variable lookups traverse this chain until they find a match or reach the global scope. Closures capture their enclosing environment at definition time, which required some care to implement with Rust's ownership model.

For safety, the interpreter includes an instruction counter that prevents infinite loops from hanging the system. After ten million instructions, execution terminates with a message suggesting the Goose suspects something is wrong.

Error Messages as Interface Design

Traditional compilers treat error messages as technical diagnostics. Duck treats them as conversation. Every error type pulls from a pool of possible responses, selected pseudo-randomly at runtime.

A division by zero might produce "You want me to divide by zero? I'm not falling for that" or "Dividing by zero opens a portal to the void. I'm not doing that." The technical information is present, but the delivery makes reading errors less painful.

This approach revealed something about error UX that I had not considered before. When error messages have personality, people actually read them. In testing, users reported paying more attention to Duck errors than they typically would to compiler output from conventional tools.

The rating system at program termination serves a similar purpose. Knowing that your code will be judged creates a small incentive to write cleanly. The algorithm considers quack coverage, function definitions, struct usage, and loop complexity. Getting a ten requires both good coverage and meaningful structure.

Standard Library

Duck ships with a practical set of built-in functions despite its playful presentation. HTTP requests work out of the box with http-get and http-post, returning struct responses with status codes, bodies, and headers. JSON parsing and serialization are built in. File operations support reading, writing, and appending with path validation that prevents directory traversal attacks.

Higher-order functions like map, filter, and fold are implemented in the interpreter rather than as library code. This allows them to accept Duck lambdas directly. The syntax for lambdas uses arrow notation: [x] -> x * 2 defines a function that doubles its input.

String interpolation uses f-string syntax borrowed from Python. Regular strings treat braces as literal characters, which makes embedding JSON straightforward. F-strings evaluate expressions inside braces and concatenate the results.

Module System

The migrate statement handles imports. Local files can be imported directly with a path. External libraries use a git reference format that resolves to installed packages in the user's home directory.

When a module is imported with an alias, Duck executes the module in a child environment and collects all definitions into a namespace struct. This allows clean separation between libraries without polluting the global scope.

The library installation process uses Git directly. Running goose install user/repo version clones the repository to a known location. A metadata file in each library specifies the entry point and provides description information that appears in listings.

Building Real Things

The toy aesthetic obscures something important: Duck can do actual work.

The clearest example is the Discord library. Discord bots need to authenticate, construct JSON payloads, and communicate with a REST API. Duck's built-in functions handle all of that. The library itself is just wrapper functions that format requests correctly and parse responses into usable structs.

quack [migrate "git+konacodes/discord" as dc]
quack [dc.discord-send token channel-id "Hello from Duck!"]

The implementation philosophy here was to push complexity into the interpreter so that library code stays simple. Users do not need to manually construct HTTP headers or worry about authentication formatting. They pass a token and a message, and the library handles the rest. This is possible because http-post, http-get, and json-parse are robust enough to support real API interactions.

Currently the library supports sending messages, embeds, and reactions. It can fetch channel and user information. Command parsing utilities help with the standard !command args pattern that most bots use.

The limitation is that Discord's real-time features use WebSockets, which Duck does not yet support. The current bot can respond when triggered but cannot listen for events passively. This is high on the roadmap.

What excites me is where this leads. Once WebSocket support lands, Duck could handle persistent connections to chat services, real-time data feeds, or multiplayer game networking. Combined with planned terminal UI support, you could write a program that fetches an article via HTTP and displays it in a formatted terminal reader. The pieces are converging toward something surprisingly capable.

The toy aspect remains. The quacks are still there. The Goose still judges. But underneath the jokes is a language that can talk to external services, parse structured data, and produce useful output. There is room to do real work with this thing.

Planned Features

The suggestions document for Duck contains ideas ranging from quick additions to architectural overhauls. Some highlights from what is actively being considered:

The pipe operator would allow chaining operations left to right instead of nesting function calls. Reading data from a file, trimming it, and converting to uppercase would flow naturally as a pipeline rather than requiring mental unpacking of nested parentheses.

A package registry would provide centralized discovery and distribution for Duck libraries. The current Git-based system works but requires knowing repository locations in advance.

WebSocket support would enable real-time applications. The current HTTP functions are request-response only, which limits what can be built for interactive use cases.

Optional type hints could improve error messages and enable editor tooling without making types mandatory. This would follow the gradual typing approach used by TypeScript and Python.

Debug mode would add step-through execution with breakpoints, allowing inspection of program state at specific lines.

Several utility libraries are in development, including CSV parsing, terminal UI components, input validation, and caching helpers.

Conclusion

Duck proves that constraints, even absurd ones, can produce interesting technical work. The quack requirement forced careful thought about parser state. The personality system demanded attention to error UX. The desire to make something fun led to a language that is genuinely usable for small projects.

The Goose has rated thousands of programs at this point. Most still get threes. But occasionally someone writes something clean enough to earn higher marks, and the Goose grudgingly acknowledges it.

The code is permissively licensed and available for anyone who wants to explore language implementation through a lens that does not take itself too seriously.

curl -fsSL https://raw.githubusercontent.com/konacodes/duck-lang/master/install.sh | bash

GitHub: github.com/konacodes/duck-lang


The Goose rated this blog post: 6/10. "Adequate. The technical depth is there. The jokes could use work."