Skip to content

Language Basics

This part covers the fundamental syntax and building blocks of Dovetail.


Dovetail uses indentation to define code blocks - no curly braces or semicolons needed. If you’ve used Python, you’ll feel right at home.

  • Use spaces for indentation (tabs cause a compile error)
  • Four spaces is the formatter standard
  • Code at the same indentation level belongs to the same block
  • Lines can be continued by indenting more than the current block

Indented blocks appear in many places in Dovetail:

After = in definitions:

function greet(name: String): String =
let message = "Hello, $name!"
message
let result =
let x = 10
let y = 20
x + y

After then and else in conditionals:

if x > 0 then
debug("positive")
x * 2
else
debug("non-positive")
0

After with in match expressions:

match color with
case Red => "red"
case Green => "green"
case Blue => "blue"

After do in loops:

for item in list do
debug(item)

After => in lambdas:

let process = x =>
let doubled = x * 2
doubled + 1

For short expressions, you can keep everything on one line:

function double(x: Int32): Int32 = x * 2
if x > 0 then x else -x

Lines indented more than the block continue the previous expression:

let result = someFunction(
arg1,
arg2,
arg3)

You can use explicit begin/end and ; instead of relying on layout:

// Using layout (implicit)
function foo() =
let x = 5
x + 1
// Using explicit delimiters
function foo() = begin let x = 5; x + 1 end

Record fields use semicolons as separators:

let point = Point { x = 10; y = 20 }

Or with layout:

let point = Point {
x = 10
y = 20
}

Use // for regular comments:

// This is a comment
let x = 5 // Inline comment

Use /// for documentation comments. They support Markdown formatting:

/// Creates a greeting message for the given name.
///
/// Returns a friendly greeting string that can be displayed
/// to the user.
///
/// ## Example
///
/// ```
/// greet("Bob") // "Hello, Bob!"
/// ```
function greet(name: String): String =
"Hello, $name!"

Documentation comments can be attached to:

  • Functions
  • Types (records, enums, classes, traits)
  • Type fields
  • Packages (via README.md in package directory)

Documentation comments are displayed by tooling, but their code blocks are not executed by dovetail test. Add an explicit test for documented behavior:

/// Adds two numbers together.
function add(a: Int32, b: Int32): Int32 = a + b
test "documented addition" = assert add(2, 3) == 5

The book separately checks marked complete examples in CI; see Book validation. The synchronous debug(value) calls in examples print diagnostic output. For application console I/O, see the async Console API.


Variables are immutable by default using let:

let name = "Alice"
let age = 30
// This would be a compile error:
// name = "Bob"

Use let mutable when you need to reassign a variable:

let mutable counter = 0
counter = counter + 1
counter = counter + 1
// counter is now 2

You can shadow a variable by declaring a new one with the same name:

let x = 5
let x = x + 1 // shadows the previous x, now x = 6
let x = "hello" // shadows again, x is now a String

Shadowing creates a new variable rather than mutating the existing one.

Types are usually inferred, but you can add explicit annotations:

let count: Int32 = 0
let name: String = "Alice"
let ratio: Float64 = 3.14

Dovetail provides the following primitive types:

Type Description
Int8 Signed 8-bit integer
Int16 Signed 16-bit integer
Int32 Signed 32-bit integer
Int64 Signed 64-bit integer
Uint8 Unsigned 8-bit integer
Uint16 Unsigned 16-bit integer
Uint32 Unsigned 32-bit integer
Uint64 Unsigned 64-bit integer
Float32 32-bit floating point
Float64 64-bit floating point
Bool Boolean (true or false)
Char Unicode character
String UTF-8 encoded text
Unit Empty type (like void)
Never Bottom type (no values)
Any Top type (all values)
// Integers
let decimal = 42
let hex = 0xFF
let binary = 0b1010
let octal = 0o755
let byte = 255u8 // with type suffix
let long = 1000000i64
// Floats
let pi = 3.14159
let scientific = 1.5e10
let typed = 3.14f32
// Exact numbers (available from the prelude)
let large = 123456789012345678901234567890big
let amount = 19.99dec
let exactScientific = 1.25e3dec
// Booleans
let active = true
let disabled = false
// Characters
let letter = 'A'
let emoji = '🎉'
let escaped = '\n'
// Strings
let greeting = "Hello, World!"
let multiline = """
This is a
multi-line string
"""
// Unit
let nothing: Unit = ()

BigInt literals use the big suffix and accept integer digits in any supported base, including 0xFFbig. Decimal literals use dec and accept base-10 integers, fractions, and scientific notation. Both are constructed exactly from source digits, without floating-point conversion. Suffixes are lowercase and must touch the number; digit separators are not supported.

These prelude types support +, -, *, unary -, equality, and ordering. Operands must have the same type: write amount * 2dec, not amount * 2. Unsuffixed literals keep their existing types, even with a Decimal annotation. Literal patterns in match are not supported for these types; use an equality guard instead. Division and rounding APIs are not provided yet.

Decimal uses a BigInt coefficient and a nonnegative Int32 scale. Values are normalized: 1.200dec formats as "1.2", and -0.00dec as "0". Scientific notation adjusts scale exactly: 1.25e3dec is 1250dec. Use Decimal.of(123big, 2) for explicit construction, or fromInt32/fromInt64. Negative constructor scales and arithmetic scale overflow panic. Invalid literal scales are compile-time errors. Compiler literal construction has a resource limit of 1,000,000 coefficient digits, including exponent expansion; this does not limit runtime BigInt arithmetic.

BigInt and Decimal have moved from standard.math to standard.prelude; remove their old imports. Decimal.unscaled and the first argument to Decimal.of now use BigInt instead of Int64. RoundingMode remains in standard.math.

Never is special - it represents computations that never complete normally:

function fail(message: String): Never =
panic message

It’s useful for functions that always panic, unreachable code branches, and infallible async operations (Async<T, Never>).

Any is the top type — every type is assignable to Any:

let a: Any = "Hello"
let b: Any = 42
let c: Any = true

To use a value of type Any, you need to test or cast it back to a concrete type using is and as (see Type Testing and Casting).


Operator Description Example
+ Addition a + b
- Subtraction a - b
* Multiplication a * b
/ Division a / b
% Modulo a % b
- Negation (unary) -x
Operator Description Example
== Equal a == b
!= Not equal a != b
< Less than a < b
<= Less or equal a <= b
> Greater than a > b
>= Greater or equal a >= b

Dovetail uses keywords for logical operators to improve readability:

Operator Description Example
and Logical AND a and b
or Logical OR a or b
not Logical NOT not x

Short-circuit evaluation applies: and stops if the left side is false, or stops if the left side is true.

Operator Description Example
& Bitwise AND a & b
| Bitwise OR a | b
^ Bitwise XOR a ^ b
~ Bitwise NOT ~x
<< Left shift a << 2
>> Right shift a >> 2
Operator Description Example
:: Prepend one element to a list x :: xs
++ Join two sequences xs ++ ys

:: is right-associative, so 1 :: 2 :: rest prepends both in order. ++ concatenates; it works on String natively and on any type implementing the Concat trait, including List.

let xs = 1 :: [2, 3] // [1, 2, 3]
let ys = [1, 2] ++ [3, 4] // [1, 2, 3, 4]

From lowest to highest:

  1. or (logical or)
  2. and (logical and)
  3. binary ~ (tuple extension, advanced)
  4. ==, != (equality)
  5. <, <=, >, >= (comparison)
  6. :: (cons — right-associative)
  7. | (bitwise or)
  8. ^ (bitwise xor)
  9. & (bitwise and)
  10. <<, >> (shift)
  11. +, -, ++ (additive and concatenation)
  12. *, /, % (multiplicative)
  13. not, -, ~ (unary prefix)
  14. Function calls, field access, indexing, is, as (postfix)

So x :: xs ++ ys groups as x :: (xs ++ ys), and x :: xs == ys as (x :: xs) == ys.

Use parentheses when precedence is unclear:

let result = (a + b) * c
let check = (x > 0) and (y < 10)

Dovetail supports string interpolation in all strings - no special prefix needed.

Use $ followed by a variable name:

let name = "Alice"
let greeting = "Hello, $name!" // "Hello, Alice!"

Use ${} for complex expressions:

let x = 10
let y = 20
let message = "Sum: ${x + y}" // "Sum: 30"
let user = getUser()
let info = "User ${user.name} is ${user.age} years old"
Sequence Meaning
\n Newline
\r Carriage return
\t Tab
\\ Backslash
\" Double quote
\$ Literal dollar sign
\0 Null character
\u{XXXX} Unicode code point
let escaped = "Line 1\nLine 2"
let price = "Cost: \$99.99" // Literal $, not interpolation
let heart = "\u{2764}" // ❤

A string may carry an identifier on its opening quote — sql"SELECT * FROM t WHERE id = $id". The prefix hands the literal to a library, which decides what interpolation means: in a sql"..." literal, $id binds a parameter rather than splicing text. Prefixed literals also add a spread form, $..values, which exists nowhere else. See Part 24.

Use triple quotes for multi-line strings:

let poem = """
Roses are red,
Violets are blue,
Dovetail is great,
And so are you!
"""

Interpolation works in multi-line strings too:

let name = "World"
let html = """
<html>
<body>
<h1>Hello, $name!</h1>
</body>
</html>
"""

Use ++ to concatenate strings:

let first = "Hello"
let second = "World"
let combined = first ++ ", " ++ second ++ "!"

For building strings dynamically, consider using StringBuilder from the standard library for better performance.


You now understand Dovetail’s basic syntax:

  • Layout-sensitive - Indentation defines blocks, no braces or semicolons needed
  • Comments - // for regular, /// for documentation with Markdown
  • Variables - Immutable by default (let), use let mutable for mutation
  • Primitive types - Integers, floats, booleans, characters, strings, unit, never, any
  • Operators - Standard arithmetic/comparison, keywords for logical (and, or, not)
  • String interpolation - $variable and ${expression} in any string

In the next part, we’ll explore control flow with if expressions, pattern matching, and loops.