Everything in the file you downloaded

RustCFML is deliberately opinionated about what belongs in a language runtime: a lean, stable core, with the rest handled by libraries and by native extensions. What is in the core is the part that applications cannot do without.

Script first. Tags when you want them.

Most CFML written today is script, and it reads like it. The tags are still there: a preprocessor turns fifty-plus of them into CFScript, and everything after that is one pipeline: lexer, parser, AST, bytecode, VM. The two syntaxes are not modes bolted onto each other; by the time it runs, they are the same program.

  • Arrow functions, closures and first-class functions
  • ??, ?: and ?. for null coalescing, elvis and safe navigation
  • Member methods on arrays, structs, queries, lists and strings
  • Higher-order map(), filter(), reduce(), some(), every()
  • Components with single inheritance, interfaces and implicit accessors
  • Spread, for … in, try/catch/finally and rethrow
  • And the tags: custom tags, tag files, and .cfm includes with their own output rules

Compilation pipeline and crate layout: Architecture.

lib/Slack.cfc
component implements="Notifier" accessors="true" {

    property name="webhook" type="string";

    function init( required string webhook ) {
        variables.webhook = arguments.webhook;
        return this;
    }

    // Arrow functions and chained member methods.
    public string function notifyAll( required array people, required string text ) {
        return arguments.people
            .filter( ( p ) => p.active ?: false )
            .map( ( p ) => "@#p.handle#" )
            .toList( " " ) & " #text#";
    }

    private string function truncate( required string body ) {
        return arguments.body.len() > 3000
             ? arguments.body.left( 2997 ) & "..."
             : arguments.body;
    }
}

Four hundred of them, including the ones you would expect to install

Strings, arrays, structs, dates, maths, lists, queries, JSON, XML, regex, encoding and hashing, with the modern password algorithms included rather than left to a library. Where a built-in would duplicate something better served by a library, the project leaves it out and makes sure the engine can run the library.

Password hashing
bcrypt, scrypt and argon2, with hashPassword() and verifyPassword()
Documents
Native .xlsx read, edit and write including charts, styling and formulas
Images
Resize, scale, crop, rotate, flip and base64, in pure Rust, native and WASM
Crypto
HMAC, AES via encrypt(), and every hash from MD5 to SHA-512

Partial surfaces and known divergences are listed under Known Issues.

register.cfm
<cfscript>
    hash   = hashPassword( form.password, "bcrypt")
    digest = hash( form.email, "SHA-256" )
    token  = hmac( digest, application.secret, "SHA-256" )

    payload = {
          id    = digest
        , roles = [ "editor", "reviewer" ]
        , seen  = now()
    }
    body = serializeJSON( payload )

    // Structs behave like structs, including in the debugger.
    payload.roles
        .map( ( r ) => uCase( r ) )
        .each( ( r ) => writeLog( r ) )
</cfscript>

The application you already wrote, served by one flag

--serve gives you a real web server: the Application.cfc lifecycle in the order you expect, sessions that can live in process, in Memcached or across a cluster, cookies, multipart uploads and Tuckey-compatible URL rewriting. This site is that feature working: a plain CFML application with includes and a rewrite file.

  • Full hook set: onApplicationStart through onSessionEnd, plus onError and onMissingTemplate
  • urlrewrite.xml with regex backreferences, conditions and last="true" chaining
  • Session storage in process, Memcached or clustered, with this.sessioncookie control
  • Datasources, mappings and mail from .cfconfig.json, with environment-variable substitution for secrets
  • Optional sandbox: isolate the filesystem the engine can see, or embed files in the binary
urlrewrite.xml
<?xml version="1.0" encoding="utf-8"?>
<urlrewrite>
  <rule>
    <from>^/([a-z][a-z0-9-]*)/?$</from>
    <to last="true">/$1.cfm</to>
  </rule>
  <rule>
    <from>^/old-page$</from>
    <to type="permanent-redirect">/new-page</to>
  </rule>
  <rule>
    <condition type="method">POST</condition>
    <from>^/api/(.*)$</from>
    <to>/api.cfm/$1</to>
  </rule>
</urlrewrite>

Four databases, object storage, and SQL over queries

queryExecute and cfquery run over SQLite, MySQL, PostgreSQL and MSSQL with connection pooling and cftransaction. Query-of-queries runs on a pure-Rust SQL engine parallelised across cores with rayon, with no JDBC and no HSQLDB in sight, which is why it leaves Lucee more than seven times behind on the standard benchmark suite.

Engines
SQLite · MySQL · PostgreSQL · MSSQL, configured per .cfconfig.json
Object storage
S3, R2 and MinIO, via the S3* functions and transparent s3:// paths
Query-of-queries
7.1× faster than Lucee 7.0.4 on a 1M-row in-memory table
Also
cfhttp, cfmail, and fluent Spreadsheet() builders
nightly.cfm
<cftransaction>
  <cfquery datasource="app">
    UPDATE leads SET scored = 1
    WHERE  id = <cfqueryparam value="#row.id#" cfsqltype="cf_sql_integer">
  </cfquery>
</cftransaction>

// No connection needed to ask a query a question.
<cfquery dbtype="query" name="hot">
  SELECT region, count(*) AS n
  FROM   leads
  WHERE  scored = 1
  GROUP  BY region
  HAVING n > 40
</cfquery>

// And the artefact lands in object storage.
fileWrite( "s3://reports/#dateFormat( now(), 'yyyymmdd' )#.csv", csv )

Threads that are actually threads

A cfthread body runs concurrently on a real OS thread, on a separate core, not sequentially inline. application, server, session and request are shared live across them, which is what makes fan-out useful and cflock necessary.

Two differences from Lucee are deliberate, and both come from doing threading safely in Rust: terminate is cooperative rather than forceful, because killing a thread mid-instruction can leave locks held; and a cftransaction cannot span the parent-to-child boundary, because its connection cannot be used from another thread.

fanout.cfm
<cfthread action="spawn" name="t#i#" tenant="#tenant#">
  <cfset result = runReport( attributes.tenant )>
  <cflock name="reportCache" type="exclusive" timeout="5">
    <cfset application.cache[ attributes.tenant ] = result>
  </cflock>
</cfthread>

<cfthread action="join" jointimeout="20">
<cfoutput>
  <cfloop struct="#cfthread#" item="done">
    #done.name#: #done.status# in #done.elapsedtime# ms
  </cfloop>
</cfoutput>

WebSockets on the same port as your pages

One CFC per channel with lifecycle methods found by convention: authenticate on connect, subscribe to rooms, presence in and out, and lastEventId resumability so a reconnect does not lose what happened while the tab was closed. Fan-out works across nodes, and any template can emit with wsPublish().

  • Raw WebSocket transport and socket.io, both supported
  • Rooms, presence, per-connection auth and ack-by-return
  • An imperative, socket.io-compatible API via new SocketIoServer()
  • Live from a single binary, with no Redis adapter to stand up to get started
channels/RoomChannel.cfc
component {

    // Checked before the socket is accepted.
    function onAuth( required string token ) {
        return accountService.verify( token );
    }

    function onSubscribe( required string room, required struct connection ) {
        return len( room );
    }

    function onMessage( required string room, required string body, required struct connection ) {
        wsPublish( room, { from = connection.id, text = body } )
        return { delivered = true };   // ack-by-return
    }

    function onPresence( required string kind, required string room ) {
        return kind;  // "join" | "leave"
    }
}

Profiler tooling, zero-cost when switched off

All of it is off by default and costs nothing until you ask for it: a Lucee-style debug footer with queries, template times and scopes; a threshold-gated sampling profiler that only records the requests slow enough to be worth recording; OpenTelemetry traces over OTLP; Prometheus RED metrics; and a native CPU profiler behind a command-line flag.

Per request
getDebugData(), isDebugMode(), debugAdd()
Per slow request
profileNow() and getRequestProfile()
Per process
--profile for a flamegraph or pprof output
Per fleet
OTLP to a collector, tail sampling, Tempo and Grafana
shell & .cfconfig.json
# a flamegraph of the running binary
rustcfml --serve ./webroot --production --profile=30

# the baseline config, with secrets from the environment
{
  "datasources": {
    "app": {
      "type": "PostgreSQL",
      "host": "db.internal",
      "username": "{env:DB_USER}",
      "password": "{env:DB_PASSWORD}"
    }
  },
  "otlp": { "endpoint": "{env:OTEL_EXPORTER_OTLP_ENDPOINT}" }
}
.rcx extensions

Precompiled Rust, loaded at start-up

Drop a file in extensions/ and a stock, unmodified binary gains new built-in functions and classes. That is how this site’s sibling applications get a browser and a protocol monitor.

Extensions

Native modules

Statically linked into your binary

Building your own artifact? Link Rust built-ins and classes straight in, and ship one executable that contains the engine, your code and your extensions.

Native modules

Embedding

CFML inside a Rust program

The engine is a workspace of focused crates. Use cfml-compiler and cfml-vm as libraries and expose a scripting layer to your own application.

Embedding

The same templates, everywhere you can put a binary

Static binaries for Linux and macOS, a Windows build, single-file applications, containers and a WebAssembly target that runs on Cloudflare Workers.

Start with one page

Download a binary, save a .cfm file, and be serving in under two minutes. Nothing to install, nothing to administer.