Skip to main content

System

Trait System 

Source
pub trait System: Sized {
    type Key: Clone + Ord;
    type Value: Clone + Eq;

    // Required method
    fn compute(
        &self,
        db: &Database<Self>,
        key: &Self::Key,
    ) -> Result<Self::Value, QueryError>;
}
Expand description

The definition of a query system: how every derived query is computed.

A consumer implements System once to describe an entire incremental computation. It ties together three things: the Key that names a query, the Value a query produces, and the compute function that turns one into the other. The Database supplies everything else — caching, dependency tracking, and invalidation — and calls compute only when it must.

§Keys, inputs, and derived queries

A single Key type names every query in the system, usually an enum with one variant per kind of query (Key::Source(FileId), Key::Ast(FileId), Key::TypeOf(DefId), …). A key is an input once its value is placed into the database with Database::set; every other key is derived, and its value comes from compute. The same key type covers both, so a query reads an input and another derived query the same way — through Database::get — and the engine records the dependency either way.

§The contract on compute

compute must be a pure function of the queries it reads. It may read inputs and other derived queries through the db handle it is given, and it must read every value it depends on through that handle — a value pulled in from outside (a global, the clock, the filesystem read directly) is invisible to the engine and will not trigger invalidation when it changes, leaving the cache serving stale results. Given the same inputs, compute must return the same value; the engine relies on that to reuse cached results safely.

§Requirements on the associated types

  • Key: Clone + Ord — keys are stored in the dependency graph and the memo table (a BTreeMap, so Ord rather than Hash; this keeps the engine no_std- and dependency-free). Cloning a key should be cheap; prefer small copyable keys or interned identifiers over owned strings.
  • Value: Clone + Eq — the engine clones a value to hand it back and compares the new value against the old one to decide whether a recomputation actually changed anything. That comparison is early cutoff: when a recomputed value equals its predecessor, queries that depend on it are not recomputed. Make values cheap to clone and compare — wrap large results in an Arc so a clone bumps a refcount rather than copying.

§Examples

A two-layer system: an input number, and a derived query that squares it.

use query_lang::{Database, System, QueryError};

// One enum names every query. `Base` values are set as inputs; `Squared`
// values are computed from a `Base`.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Key {
    Base,
    Squared,
}

struct Arithmetic;
impl System for Arithmetic {
    type Key = Key;
    type Value = i64;
    fn compute(&self, db: &Database<Self>, key: &Key) -> Result<i64, QueryError> {
        match key {
            Key::Base => Ok(0), // a default if `Base` was never set as an input
            Key::Squared => {
                let base = db.get(&Key::Base)?;
                Ok(base * base)
            }
        }
    }
}

let mut db = Database::new(Arithmetic);
db.set(Key::Base, 9);
assert_eq!(db.get(&Key::Squared)?, 81);

Required Associated Types§

Source

type Key: Clone + Ord

The identifier that names a query. Usually an enum with one variant per kind of query. Must be cheap to clone and totally ordered.

Source

type Value: Clone + Eq

The value a query produces. Cloned to return and compared for early cutoff, so it should be cheap to clone and compare (wrap large payloads in an Arc).

Required Methods§

Source

fn compute( &self, db: &Database<Self>, key: &Self::Key, ) -> Result<Self::Value, QueryError>

Compute the value of a derived query.

The engine calls this only on a cache miss or when a dependency has genuinely changed — never for a key that is currently a set input, and never when a cached value is still valid. Read every dependency through db so the engine can track it; see the trait contract.

§Errors

Returns QueryError::Cycle if resolving a dependency closes a cycle back onto a query still being computed. Propagate it with ?; do not attempt to recover from it inside compute, as the whole resolution chain is already unwinding.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§