DIA
Language / Pre-alpha
Build: Active
Design note / 001

Why I’m building Dia

Dia stands for “do it all”. I’m building a language for writing complete applications, with a UI library, native compilation, and access to existing C libraries.

I want more of the work that goes into an application to be supported by the language and its standard libraries. Building an interface, editing rich text, calling a native library, and writing GPU code should fit into the same project without requiring a different language for each part.

The design draws from tools I like: Rust’s approach to ownership, JSX’s syntax for interfaces, Solid’s fine-grained reactivity, and ProseMirror’s approach to rich text editing. I’m interested in how these ideas work together, and how much application code they can save.

Dia is pre-alpha. This post describes the direction of the project; some features are implemented, and others are still being built.

01 / USE

What would you build with it?

A familiar way to write interfaces

If you write web applications, much of Dia’s UI code should look familiar. Components are functions, properties are typed parameters, and state changes update the parts of the interface that depend on them.

DSX: markup with Dia expressions

Dia has JSX-like syntax called DSX. The compiler checks component names, properties, and children along with the rest of your code. A misspelled property is a type error.

counter.dia / Illustrative syntax
import ui.{View, run, Column, Text, Button, state}

struct CounterState {
  value: i32
}

fn Counter(): View !{alloc} {
  let s = state(CounterState{ value: 0 })

  <Column spacing=12>
    <Text>"Count: ${s.value}"</Text>
    <Button
      label="Increment"
      on_click=() => { s.value += 1 }
    />
  </Column>
}

pub fn main(): void !{alloc, io} {
  run(() => <Counter />)
}
The syntax and library are evolving. The effect annotations !{alloc} and !{alloc, io} declare allocation and I/O.

Properties accept Dia expressions directly, so the callback needs no extra braces around it. Children are expressions too: strings, components, conditionals, and loops use the language’s ordinary syntax.

Updates follow the state you read

Dia’s reactivity design borrows heavily from Ryan Carniato’s work on Solid. Reading reactive state establishes a dependency. When that state changes, the computations and UI bindings that depend on it update. In the counter, changing s.value updates the count text.

Reactive dependencies / Illustrative syntax
let cart = state(Cart{ quantity: 2, unit_price: 15 })

<Text>"Total: ${cart.quantity * cart.unit_price}"</Text>
<Button label="Add one" on_click=() => { cart.quantity += 1 } />
Clicking Add one changes the displayed total from 30 to 45. The text tracks both fields it reads.

Building reactivity alongside the type system gives Dia more information about how state moves through a program. For example, it can distinguish a tracked value from a plain copy, and reject a copy where a component requires ongoing updates.

See changes in a running application

I want the quick feedback of web development in a compiled native application. Incremental compilation and state-preserving hot reload are part of the compiler’s design, so changing a view can let you continue from the screen you were already testing.

Hot reload / Illustrative syntax
// The running counter is at 7. Edit its view:
<Text>"Count: ${s.value}"</Text>

// Save this version:
<Text>"You clicked ${s.value} times"</Text>
// Intended result after reload: "You clicked 7 times"
This illustrates the intended behavior for a view edit that preserves the state’s shape.

Lazy imports and production code splitting are planned too. In a browser build, a settings screen should be able to load when someone opens it. Native builds can resolve the same import from code already linked into the application.

Load a screen on demand / Proposed API
let settings = lazy import app.settings
settings.open()
The planned browser build loads the settings package on demand. The same import resolves immediately in a native build.

Typed queries for the backend

The same attention to embedded syntax extends to the backend. The language design includes a typed SQL DSL, with the aim of checking queries and their result types as part of compilation. I want database code to get the same useful errors as interface code.

Typed SQL / Proposed API
let min_age = 18
let users = sql {
  select id, name from users where age >= ${min_age}
}

for user in users {
  print(user.name)
  // user.email: error, email was not selected
}
The intended result type follows the selected columns. Query inputs use Dia expressions.

A UI library you can customize

Dia’s UI library uses its own GPU renderer, including for text. It does not require a webview. The aim is to share application and interface code across desktop, mobile, and browser targets.

That includes ordinary application work: forms, navigation, selection, keyboard input, and editable text. Those details take time to implement well. I want the standard UI library to handle them and make its underlying services available when an application needs something custom.

Reuse text editing in a custom canvas

Consider an infinite canvas with editable labels. You need text layout, caret movement, selection, and input handling inside a view that you draw yourself. Dia’s UI architecture is intended to let you reuse those text services in your own renderer.

Editable text in a canvas / Proposed API
<Canvas draw=(ctx) => {
  ctx.circle(center=[120, 80], radius=40)
}>
  <TextInput
    @bind=label.text
    position=[80, 140]
  />
</Canvas>
Proposed composition: the canvas draws the scene; the embedded input supplies text layout, selection, and caret handling.

Keep the button, replace its drawing

The same principle applies to controls. I want you to be able to replace a button’s drawing with custom 2D shapes or a 3D effect while retaining its interaction behavior, accessibility semantics, and reactive properties.

One button, two renderers / Proposed API
// Standard drawing.
<Button label="Save" on_click=() => { save() } />

// Same properties and behavior, custom drawing.
<Button
  label="Save"
  on_click=() => { save() }
  renderer=(ctx, button) => {
    ctx.rounded_rect(
      button.bounds,
      radius=8,
      fill=if button.pressed { pressed_color } else { idle_color }
    )
    ctx.text(button.label, within=button.bounds)
  }
/>
The proposed renderer receives the button’s visual state. The button still owns activation, focus, and accessibility.

Rich text editing belongs in the library

A notes app needs much more than a place to enter characters. It needs document structure, formatting, undo, paste handling, and often embedded content. Dia includes a modular editor framework inspired by ProseMirror.

Choose the extensions the document needs. A rich editor can add Markdown input, undo, and custom embeds:

Compose an editor / Proposed API
let document = rich_document("# Notes")

<Editor
  document=document
  extensions=[
    Markdown(shortcuts=true, input_rules=true, paste=true),
    History(),
    Embed(name="image", kind=.Block, view=Image),
    Embed(name="mention", kind=.Inline, view=Mention),
  ]
/>
Proposed extension API. Input rules turn “# ” into a heading and “- ” into a list; shortcuts include Cmd+B for bold.

History should also work across the application. In a canvas editor, moving a shape and editing its label should participate in the same undo sequence.

Share application history / Proposed API
let history = History()

<Editor document=note extensions=[History(shared=history)] />
<Button label="Undo" on_click=() => { history.undo() } />
The proposed shared history lets document edits participate in the application’s undo and redo sequence.

Access to native libraries

Dia compiles to C and can import C headers. That gives applications access to existing libraries for storage, media, networking, and platform integration. The build still needs the library and its link configuration; the importer handles the declarations from the header.

Call a native library / Illustrative syntax
extern "C" import "math.h" as math

let angle = 0.5
let horizontal = math.cos(angle)
The function declaration comes from the C header. Platform and link configuration belong in the build.

Use the libraries you already know

For a game, I want getting started with an existing C library to be straightforward. Dia can read its header and expose its declarations directly.

C interop / Header import
extern "C" import "raylib.h" as rl
The build supplies the header and links raylib. Interop manifests describe ownership and safety contracts that C headers cannot express.

This lets you use a library such as raylib for a game while writing the application in Dia. Native compilation goes through C, and the compiler also has a WebAssembly target for browser builds. A particular library still needs to support the platform you’re targeting.

Automatic cleanup without a tracing collector

Rust is a major influence on Dia’s memory model. Dia uses ownership, value semantics, and deterministic cleanup, with reference counting for shared ownership. There is no tracing garbage collector, and ordinary owned values are cleaned up automatically.

Borrow, copy, transfer / Illustrative syntax
struct Level {
  name: String
}

fn rename(mut level: Level): void {
  level.name = "Forest"
}

let original = Level{ name: "Untitled" }
var edited = copy original
rename(mut edited)          // original is still "Untitled"
let saved = take edited    // edited can no longer be used
// Each owned value is cleaned up when its scope ends.
The mut parameter borrows for mutation. Copy creates an independent value; take transfers ownership.

Borrowing has restrictions that the compiler checks, but there are no lifetime parameters in the surface language. The aim is memory safety with code that remains practical to write for everyday application work.

For games, predictable cleanup and control over allocation matter. Reference counting and allocation still have costs; the design gives you ways to reason about them. Performance claims will need measurements on real programs.

Share suitable code between CPU and GPU

I also want to write shaders in Dia and reuse suitable functions on both the CPU and GPU. A calculation used by a simulation and its renderer should be expressible once when both targets support it.

One calculation, two targets / Proposed API
fn brightness(rgb: Vec[3, f32]): f32 {
  rgb.x * 0.2126 + rgb.y * 0.7152 + rgb.z * 0.0722
}

// CPU: use it in application code.
let preview = brightness(Vec[3, f32](0.8, 0.4, 0.2))

// GPU: call the same function from a shader.
@compute(workgroup: [8, 8, 1])
fn grayscale(src: Texture2D, dst: StorageImage2D, id: Vec[3, u32]): void {
  let pixel = src.load(id.xy)
  let gray = brightness(pixel.xyz)
  dst.store(id.xy, Vec[4, f32](gray, gray, gray, pixel.w))
}
Proposed shader API. Both callers use the same brightness function; the GPU compiler checks that its types and operations are supported.

The GPU design restricts the types and operations available in shader code. Features such as recursion and reference-counted pointers are excluded, and the compiler checks functions called from a GPU entry point. This is part of the planned language, with implementation still in progress.

Build tools alongside the game

The reactive UI and text editing libraries are useful here too. Level editors, inspectors, dialogue tools, and asset browsers need application interfaces. I want those tools to share types and code with the game they support.

Edit the game’s own data / Proposed API
struct Light {
  intensity: f32
}

let light = state(Light{ intensity: 1.0 })

<Row>
  <GamePreview light=light />
  <Slider @bind=light.intensity min=0.0 max=4.0 />
</Row>
Proposed binding API. The inspector edits the same tracked Light value that the preview reads.
02 / STATUS

What exists today

The compiler has native and WebAssembly code generation, C-header imports, ownership checking, structured concurrency, and a reactive runtime. The UI library, editor framework, and development tools are under active development.

Platform support and the standard libraries still need work. Dia is not ready for production use. The goal is a language I can use to build a complete application, with the common parts supplied and enough control to implement the specific parts myself.

Explore more language examples