A quantum computing language, a virtual machine and an interactive book
Dagger provides a user-friendly language that handles both classical and quantum-specific operations in harmony, making it easier to learn the basics of quantum computing and quantum algorithms through an interactive and intuitive experience.
The Dagger compiler is written entirely in Rust. It features a custom lexer, a recursive-descent parser, and a two-pass AST-traversing compiler that emits Dagger Bytecode, which is then executed by the QVM. The compiler itself can be compiled to common Rust targets as well as WebAssembly (WASM) for use in web applications, as shown in the book.
The strong typing enforces type consistency across all common types as well as quantum primitives (gates and state registers), specifically checking for qubit size matching.
The Dagger VM is a lean, stack-based virtual machine implemented in Rust. It offers full debugger support, including breakpoints, code stepping, call stack unwinding, and optional symbol expansion. All primitive operations are natively compiled, and quantum-specific operations are supported by nalgebra.
Dagger is a minimalistic, imperative, strongly-typed language with first-class support for quantum computing primitives. Its syntax is inspired by Rust
It supports the following primitives
| type | size | description |
|---|---|---|
| bit | 1 | boolean container |
| i64 | 8 | 64-bit integer |
| f64 | 8 | 64-bit floating point number |
| c32 | 8 | 32-bit floating point complex number |
| str | 8 | string pointer |
| gate | 8 | quantum gate pointer |
| qubit | 8 | quantum register pointer |
// Types are inferred, therefore explicit types are optionnal
let a: bit = 1b;
let b: c32 = 1 + 2i;
let c: str = "quantum";Fixed-size array of any type can be declared and accessed. They live on the stack, an therefore have no overhead
let a: [i64; 3] = [1, 2, 3];
let b: [[bit; 2]; 2] = [[1b, 0b], [0b, 1b]];Quantum registered are declared using the ket notation, and are auto-normalized on assignment. When combined together through a kronecker product, a larger quantum register is created, and operands are turned into pointer into that larger register. Pointers into quantum registers can be taken using a multi-index notation: x[i0,i1,..];
let x: qubit = |10⟩;
let y: qubit = |0⟩ + |1⟩;
let xy = x * y; // Kronecker product
// Now x points to xy[0,1], and y points to xy[2];
// We may create a pointer into that larger state by using multi pointer notation
let z = xy[0,2];Quantum operators and observables (typed "gates") can be declared from arrays using the std lib function to_gate. Input array must be square and hermitian (square matrix that is equal to its own conjugate transpose). Additionally, the std lib features various usual gates.
let CNot: gate<2> = std::to_gate([[0b, 1b], [1b, 0b]]);
let CNot = std::CNot; // Already in the stdlibThe stdlib contains a few useful constants and functions, invoked through the namespace std::*.
print(fmt: str, ...args): Standard output formatted printassert(a: bool): Runtime assert
Numeric types include i64, f64 and c32
sqrt(x: num) -> num: square rootsin(x: num) -> num: sinecos(x: num) -> num: cosineabs(x: num) -> num: absolute valuepow(x: num, p: num) -> num: x^pmin(a: num, b: num) -> num: minimum of the two valuesmax(a: num, b: num) -> num: maximum of the two valuesrand() -> f64: random number between 0 (inclusive) and 1 (exclusive)
I: single qubit identity gateX, Y, Z: Pauli gatesS: Phase gateT: pi/8 z-rotation gateCNot: two qubit CNot gate (w/ first as control)CZ:Swap: two qubit swap gateCCNot: Troffoli gate
normalize(x: qubit | gate): normalize a state register or a gateto_gate(arr: [[c32; N]; N]) -> gate<N>: create a quantum gate from an hermitian arraymeas(x: qubit) -> [bit; N]: measure a state register in the computational basis (Pauli Z), collapsing it to one of its eigenvectors, and return the collapsed eigenvector bit arraymeas_observable(O: gate, x: qubit) -> f64: mesure the state x with the provided observable, collapsing it to one of its eigenvectors, and return the measured observable eigenvaluemap_to_uf(map: [bit; N]) -> gate<sqrt(N)>: create a binary quantum oracle from a mappinghadamard(n: N) -> gate<N>: create a N qubit hadamard gateidentity(n: N) -> gate<N>: create a N qubit identity gatekronecker(a: gate<A>, b: gate<B>) -> gate<A + B>: compute the kronecker product of the two gateskronecker_exp(a: gate<A>, n: N) -> gate<A^N>: compute the kronecker exponent of a gate

