Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Welcome to Dewy

Dewy aims for Python-like ease, C-like speed, and safety by default. Begin with small programs; every example in this book is compiled and run with the current compiler.

printl"Hello, Dewy!"

Install Dewy, then follow your first program. The reference records the language design; coverage records what this website build measured.

Dewy at a Glance

Ordinary computation should read clearly. Dewy brings values, typed functions, containers and engineering quantities into one language.

let values = [20 22]
printl(values[0]+values[1])

Try the compiled browser examples or follow the core-language lessons. The language goals are a direction, not a performance claim: see GOALS.

Safer than Rust

Rust is the standard for memory safety, and Dewy does not claim to beat it there. The claim is about everything else a safe Rust program can still do: abort on v[i], abort on unwrap, panic on overflow in a debug build and wrap in a release build. Dewy's rule is that none of these happens behind your back. Each partial operation is proven from facts the compiler can see, handled as a value, or written out by the author.

Some of this works in today's compiler and some is decided and landing. Each section below says which. Every example on this page is compiled by the website build, and a rejection example appears only where today's compiler rejects it.

No hidden panics

Rust panics on an out-of-range index, on unwrap() of a None, on division by zero, and on integer overflow in debug builds. Dewy has no unwrap: an optional is a T | none that the code narrows or gives a default with ??, and int arithmetic is exact, so it cannot overflow.

let xs = [3 1 4]
printl(xs.get(7) ?? -1)      # no element 7: the fallback
printl(xs.get(1) ?? -1)      # 1
let big = 9223372036854775807
printl(big + 1)              # int is exact: no overflow

An index, a nonzero divisor or a narrowing that the compiler cannot prove is an obligation. With checks off, it is a compile error that names the missing fact:

$runtime_checks = false
at = (xs:array<int64> i:int64):>int64 => xs[i]

The fix is more facts (a guard such as if 0 <=? i and i <? xs.length, or a refined parameter) or a value (xs.get(i)), not a hidden check.

Status. Working today: exact int, get, ??, and the compile error above under $runtime_checks = false. Landing: that compile error becomes the default. Until then an unproven index in a default build still compiles to a runtime check that stops the program with exit status 101 and a report.

Fixed-width arithmetic never wraps silently

Rust's i32 overflow panics in a debug build and wraps in a release build, so the same program can behave differently by build mode. In Dewy, arithmetic on int32, uint8 and the other fixed-width types is exact like int; the width is a storage property. Storing a result back into a narrower binding, field, parameter or return is the narrowing obligation: free when the compiler proves it fits, otherwise a compile error with fix-its. Wrapping and saturating are still available, but only when the type says so (uint32<overflow='wrap'> for hashes and checksums).

Status. Decided, in progress, and not yet today's behavior. Today's compiler still wraps a fixed-width result when it is stored (an int32 holding 2147483647 plus one stores -2147483648), and the explicit wrap form is currently uint8<overflow='wrap'>(x). The exact arithmetic, the narrowing error and the overflow= spelling are landing.

Data-race freedom from value semantics

Rust prevents data races with a borrow checker and the Send and Sync traits. Dewy reaches the same guarantee from the other side: assignment, argument passing and return give the receiver its own value, so there is no shared mutable state to race on unless the source writes it (@ at the parameter and at the call, or an atomic). A parallel body cannot change a variable it captured:

total = 0
parallel i in [0..100) { total += i }

Return values from the body instead, and the results are collected in input order:

squares = [parallel i in [0..5) i*i]
printl(squares)

Rust also has data-race freedom, so this is not an advantage over Rust. It is the same guarantee without lifetimes in the signatures, which is why it belongs on this page.

Status. Working today, including the rejection above and the structural Send and Sync checks.

panics and blocks as checkable effects

An effect row says what a function may do, and & no ... forbids one. A function can promise that it never blocks, and the compiler checks the promise through everything it calls:

tick = (n:int64):>int64 & no blocks => {
    printl(n)
    return n + 1
}

printl does I/O, so tick is rejected. Rust has no checked equivalent in the language: whether a function panics or blocks is a convention in its documentation.

The panics effect is the same idea for failure. "May fail a runtime check", or any other possible panic, is an inferred effect that shows in rows. & no panics forbids it outright, and code that opted into runtime checks cannot be called from a scope that did not.

Status. & no blocks works today. panics and & no panics are decided and in progress; today's compiler does not accept no panics yet.

$no_hidden_costs

A module, function or block can ask for the strict lane with one line. It turns on explicit copies, turns off runtime checks and $prototype, and gives functions without a row of their own no allocates. An unproven index is then an error, and so is an allocation or an implicit costly copy:

$no_hidden_costs
at = (xs:array<int64> i:int64):>int64 => xs[i]

Code that needs more opts out with $no_hidden_costs = false in its body, or declares a row of its own. Game code never has to enter this lane.

Status. Working today for checks, allocation and copies. no panics joins the bundle when the panics effect lands.

What this does not claim

  • Memory safety and data-race freedom are table stakes, and Rust has them too.
  • Dewy's unsafe boundary and foreign calls are trusted, as in Rust.
  • The strongest form of the claim, no hidden traps by default, describes where the language is going. This page marks what a current build does.

Getting Started

Install a standalone toolchain or work from a checkout with Python, a C compiler and make.

./tools/dewy check examples/tour.dewy
./tools/dewy examples/tour.dewy

The launcher rebuilds when compiler inputs change. Continue with installation and hello world.

Installation

Use the installation guide for Linux x86-64/ARM64, macOS ARM64 and Windows x64, prerequisites, verification and rollback.

curl -fsSL https://dewy-lang.org/install.sh | bash

On PowerShell:

irm https://dewy-lang.org/install.ps1 | iex

A checkout can use ./tools/dewy directly; it requires Python, a C compiler and make for its first build. The standalone download needs no Python.

Your First Dewy Program

Save this as hello.dewy:

printl"Hello, Dewy!"
dewy check hello.dewy
dewy hello.dewy
dewy build hello.dewy --release -o hello

A file runs from top to bottom. To start with an entry point, define main; it runs once, automatically. Plain dewy with no file starts the interactive prompt.

The output is Hello, Dewy! followed by a newline. Check source without emitting a binary; build when you want to keep an executable. In a checkout, replace dewy with ./tools/dewy.

Stopping to Look

Build with debug information, then use a native debugger or the VS Code extension with CodeLLDB.

dewy build hello.dewy -g -o hello-debug

Configure dewy.core.path for the extension to point to your standalone bin/dewy-core or the checkout launcher. See tools and the debugging reference.

let answer:int64 = 42
printl(answer)

Printing is enough to inspect this example. The reference separately specifies the $breakpoint directive; this lesson does not claim to test that directive.

A Few Ideas That Organize Dewy

Types describe values. Ordinary value boundaries preserve independence; @ makes access to a place explicit. Start with a small value-copy example:

let source = [42]
let copy = source
copy[0] = 0
printl(source[0])

Read values and places, then functions. Design guarantees extend beyond this example; see coverage.

Expressions, Produced Values, and Blocks

A block groups work. Use its last expression as a result; parentheses group an expression.

let answer = { let base = 20; base + 22 }
printl(answer)

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Expressions, Produced Values, and Blocks reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Bindings and Scope

A let binding holds a value. A nested binding can shadow a name without changing the outer value.

let score = 20
{ let score = 42; printl(score) }
printl(score)

Expected output:

42
20

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Bindings and Scope reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Types and Numbers

int is the arbitrary-precision integer; explicit widths such as int64 fix how a value is stored. Type annotations follow a colon.

let count:int64 = 40
let ready:bool = true
printl"{count+2} {ready}"

Expected output:

42 true

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Types and Numbers reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Functions and Calls

Define a function with =>. Parameters are separated by whitespace. @name selects a function handle.

double = (value:int64):>int64 => value*2
let callback = @double
printl(callback(21))

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Functions and Calls reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Branching and Flow Control

An if chooses which block to evaluate. Dewy comparisons use a question mark.

let score = 42
if score >? 40 { printl"high" } else { printl"low" }

Expected output:

high

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Branching and Flow Control reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Optional Values and Narrowing

An optional value is a union with none. Test its member type before using the contained value.

find = (present:bool):>int64|none => { if present { return 42 }; return none }
let result = find(true)
if result is? int64 { printl(result) }

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Optional Values and Narrowing reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Errors as Values

Mint an error type and include it in a return union. The caller handles that alternative explicitly.

ParseError:type = type of error & [message:string]
parse = (text:string):>int64|ParseError => {
    let value = to_int(text)
    if value is? error { return ParseError["invalid integer"] }
    return value
}
let result = parse("42")
if result is? int64 { printl(result) }

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Errors as Values reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Loops and Multiple Iterators

A finite range supplies loop values. Keep a running total in a binding.

let total:int64 = 0
loop value in 1..5 { total += value }
printl(total)

Expected output:

15

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Loops and Multiple Iterators reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Ranges

The two-dot range includes both ends. This loop visits three values.

loop value in 1..3 { printl(value) }

Expected output:

1
2
3

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Ranges reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Values, Copies, and Places

An ordinary assignment gives an independent array value. Updating the copy leaves the original alone.

let original = [20 22]
let copy = original
copy[0] = 99
printl(original[0]+original[1])

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Values, Copies, and Places reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Strings and Graphemes

String length counts graphemes. Interpolation puts values inside braces.

let word = "café"
printl"{word}: {word.length}"

Expected output:

café: 4

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Strings and Graphemes reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Containers

Arrays store values of a common type. A comprehension builds a new array from a loop.

let values = [20 21]
let doubled = [loop value in values value*2]
printl(doubled[1])

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Containers reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Structural Objects

A record type names fields and can give defaults. Field selection uses a dot.

Point:type = [x:int64 y:int64=22]
let point:Point = [x=20]
printl(point.x+point.y)

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Structural Objects reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Numbers and Bases

Base prefixes change how an integer is written. They do not change the value.

let binary:int64 = 0b101010
let hex:int64 = 0x2a
printl"{binary} {hex}"

Expected output:

42 42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Numbers and Bases reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Operators

Use =? for equality and // for integer division. Group an expression explicitly when that makes its intent clearer.

let answer:int64 = (80+4)//2
printl(answer =? 42)

Expected output:

true

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Operators reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Function Values and Composition

A handle can be stored and called. Here a generic identity function is specialized before its handle is selected.

identity = <T>(value:T):>T => value
let callback = @identity<int64>
printl(callback(42))

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Function Values and Composition reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Refinements

A refinement is a condition a type carries. The compiler proves it from literals and control flow, and rejects a call that provably breaks it. A condition written as a single comparison uses a partial operator.

span = (a:int64 b:int64<(>=? a)>):>int64 => b - a
share = (total:int64 n:int64<(>? 0)>):>int64 => total // n
printl(span(3 10))
printl(share(84 2))

Passing a value that provably breaks the condition is a compile error:

share = (total:int64 n:int64<(>? 0)>):>int64 => total // n
printl(share(84 0))

When a condition can't be proven, $runtime_assert states it explicitly:

let count:int64 = 42
$runtime_assert count >? 0
printl(count)

Expected output of the first example:

7
42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Refinements reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

These examples do not measure every refinement proof, effect inference, or a no-allocation guarantee. Those remain separate design contracts.

Physical Quantities and Units

Import units from the library. Dividing a quantity by its unit gives a dimensionless value.

from units import m
let distance = 42m
printl(distance/m)

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Physical Quantities and Units reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

Effects

Keep a computation in a function so its inputs and outputs are clear. The reference specifies the wider effect-contract design.

sum = (left:int64 right:int64):>int64 => left+right
printl(sum(20 22))

Expected output:

42

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Effects reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

The example above does not measure general refinement proofs, effect inference, or a no-allocation guarantee. Those remain separate design contracts.

Modules, Imports, and the Prelude

Import a named binding from a source-relative module. This example includes the companion file used by the checker.

from p"helpers.dewy" import answer
printl(answer())

Expected output:

42

Create helpers.dewy beside the importing file:

answer = ():>int64 => 42

A bare import splats the module's top-level names into the importing file; a name that collides is an error. Add as to keep them in a namespace instead:

import p"helpers.dewy"
import p"helpers.dewy" as helpers
printl(answer())
printl(helpers.answer())

Run the example from a file with dewy example.dewy. Its exact output is checked by the website build on the native release target.

Design boundary. The Modules, Imports, and the Prelude reference specifies the complete language rules. A passing example covers this program only. See generated coverage for related acceptance programs and unmeasured areas.

The Standard Library

The compiler finds its matching libraries beside the standalone executable. dewy-core --print-lib prints the active library directory. In a checkout, lib/ holds containers, platform services and game libraries.

from units import m
let distance = 42m
printl(distance/m)

The second is in the prelude; other physical units come from units. Browse the library source and the current language guide. Host services have target-specific constraints; native coverage does not imply browser support.

Language Feature Index

Use this index to find a worked example, then consult the design reference for the full rule.

See generated coverage for exact program IDs and targets. This index is not an implementation checklist.

Language Design and Compiler Support

The reference is normative language design. This Learn book uses current compiler examples, each with declared output or check intent.

The compiler is implemented in Sloppy and accepts Dewy source through its in-process frontend. It emits native executables and WebAssembly directly. The archived Python and µDewy implementations are not required.

Generated coverage names the source revision, compiler hash, target, examples and acceptance failures. Native release checks measure that target only. Browser examples have their own WebAssembly execution results. Archived implementation.