liveBook · Manning

Domain-specific language

As explained in Write Powerful Rust Macros

A domain-specific language is a language designed to express concepts and operations within a particular application domain.

A domain-specific language (DSL) expresses concepts and operations for one application domain rather than for every kind of programming task. SQL, for example, focuses on database interaction. By narrowing the vocabulary and assumptions, a DSL can make business concepts easier to state and can optimize around domain constraints. section 1.3.2

In Rust, macros can create embedded mini-DSLs: specialized syntax that expands into ordinary Rust. A good DSL captures knowledge learned from domain experts, uses their shared vocabulary, hides irrelevant details, and can perform validation or reject unsupported commands at compile time. Declarative macros are often the practical starting point because they are simple, require little setup, and have stronger IDE support than procedural macros. section 2.2.4 section 3.0

Account-transfer mini-DSL

An exchange! macro can accept commands such as Give 20 to the_poor, Take 10 from the_rich, and Give 30 from the_rich to the_poor. Each pattern expands to Account methods, while the transfer form performs both subtraction and addition so callers need not handle that transaction detail directly. Pattern order matters: a broad matcher placed before a specific one can make the specific command unreachable. section 2.2.4

Listing 2.13 Macro that presents a mini-DSL for exchanging money
macro_rules! exchange {
    (Give $amount:literal to $name:ident) => {
        $name.add($amount)
    };
    (Take $amount:literal from $name:ident) => {
        $name.subtract($amount)
    };
    (Give $amount:literal from $giver:ident to $receiver:ident) => {
        $giver.subtract($amount); #1
        $receiver.add($amount)
    };             
}

fn main() {
    let mut the_poor = Account {
        money: 0,
    };
    let mut the_rich = Account {
        money: 200,
    };

    exchange!(Give 20 to the_poor);            #2
    exchange!(Take 10 from the_rich);         
    exchange!(Give 30 from the_rich to the_poor);  

    println!("Poor: {the.poor?}, rich: {the.rich?}");    #3
}

Inspect the macro patterns and their expansions to see how natural-language-like transfer commands become Account operations. section 2.2.4

Use a DSL when its narrower language earns its complexity:

  • Express a complex domain in terms that developers and domain experts can share, while encapsulating validation and subtle rules. section 2.2.4
  • Describe infrastructure declaratively so environments can be recreated, version controlled, and understood from a readable specification. section 9.1
  • Prefer ordinary Rust when the abstraction is mostly application-specific business logic or when macro complexity outweighs its benefit. section 1.3.3
  • Plan for weaker macro IDE assistance and test infrastructure DSLs beyond unit tests, including verification that cloud resources were actually created. section 1.3.3 section 9.8

How Write Powerful Rust Macros covers it

How deep it goes
Works through a declarative account-transfer DSL and defers negative-account handling and full cloud-test implementation.
Examples
Demonstrates SQL, println!, format!, an Account exchange! macro, and declarative cloud infrastructure specifications.
What you can do after
Build it from scratch with declarative macro patterns that turn domain commands into Rust operations.
How it connects
Connects DSLs with declarative macros, procedural macros, Domain-Driven Design, ubiquitous language, Infrastructure as Code, and end-to-end testing.

Also explained in

Compare how 10 books explain domain-specific language →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Write Powerful Rust Macros. The section links point to the parts of the book it draws on.