diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 00000000..09c1ce2a --- /dev/null +++ b/FAQ.md @@ -0,0 +1,32 @@ +# Frequently asked questions + +## Why is it called salsa? + +I like salsa! Don't you?! Well, ok, there's a bit more to it. The +underlying algorithm for figuring out which bits of code need to be +re-executed after any given change is based on the algorithm used in +rustc. Michael Woerister and I first described the rustc algorithm in +terms of two colors, red and green, and hence we called it the +"red-green algorithm". This made me think of the New Mexico State +Question --- "Red or green?" --- which refers to salsa. Although this +version no longer uses colors (we borrowed revision counters from +Glimmer, instead), I still like the name. + +## What is the relationship between salsa and an Entity-Component System (ECS)? + +You may have noticed that Salsa "feels" a lot like an ECS in some +ways. That's true -- Salsa's queries are a bit like *components* (and +the keys to the queries are a bit like *entitites*). But there is one +big difference: **ECS is -- at its heart -- a mutable system**. You +can get or set a component of some entity whenever you like. In +contrast, salsa's queries define **define "derived values" via pure +computations**. + +Partly as a consequence, ECS doesn't handle incremental updates for +you. When you update some component of some entity, you have to ensure +that other entities' components are upated appropriately. + +Finally, ECS offers interesting metadata and "aspect-like" facilities, +such as iterating over all entities that share certain components. +Salsa has no analogue to that. + diff --git a/README.md b/README.md index bbe051a5..1ee46fb2 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,16 @@ Yehuda Katz, and Michael Woerister. ## Key idea -The key idea of `salsa` is that you define two things: +The key idea of `salsa` is that you define your program as a set +of **queries**. Queries come in two basic varieties: - **Inputs**: the base inputs to your system. You can change these whenever you like. -- **Queries**: values derived from those inputs. These are defined via - "pure functions" (no side effects). The results of queries can be - memoized to avoid recomputing them a lot. When you make changes to - the inputs, we'll figure out (fairly intelligently) when we can - re-use these memoized values and when we have to recompute them. +- **Functions**: pure functions (no side effects) that transform your + inputs into other values. The results of queries is memoized to + avoid recomputing them a lot. When you make changes to the inputs, + we'll figure out (fairly intelligently) when we can re-use these + memoized values and when we have to recompute them. ## How to use Salsa in three easy steps @@ -34,63 +35,14 @@ Using salsa is as easy as 1, 2, 3... later on you can use more than one to break up your system into components (or spread your code across crates). 2. **Implement the queries** using the `query_definition!` macro. -3. Create the **query context implementation**, which contains a full - listing of all the inputs/queries you will be using. The query - content implementation will contain the storage for all of the - inputs/queries and may also contain anything else that your code - needs (e.g., configuration data). +3. **Implement the query context trait** for your query context + struct, which contains a full listing of all the inputs/queries you + will be using. The query struct will contain the storage for all of + the inputs/queries and may also contain anything else that your + code needs (e.g., configuration data). -Let's walk through an example! This is [the `hello_world` -example](examples/hello_world) from the repository. +To see an example of this in action, check out [the `hello_world` +example](examples/hello_world/main.rs), which has a number of comments +explaining how things work. The [`hello_world` +README](examples/hello_world/README.md) has a more detailed writeup. -### Step 1: Define a query context trait - -The "query context" is the central struct that holds all the state for -your application. It has the current values of all your inputs, the -values of any memoized queries you have executed thus far, and -dependency information between them. - -```rust -pub trait HelloWorldContext: salsa::QueryContext { - salsa::query_prototype! { - /// The fundamental **input** to the system: contains a - /// complete list of files. - fn all_files() for AllFiles; - - /// A **derived value**: filtered list of paths representing - /// jpegs. - fn jpegs() for Jpegs; - - /// A **derived value**: the size of the biggest image. To - /// avoid doing actual image manipulating, we'll use the silly - /// metric of the longest file name. =) - fn largest() for Largest; - } -} -``` - -### - -Let's make a very simple, hello-world sort of example. We'll make two inputs, -each of whihc is - - -## Goals - -It tries to hit a few goals: - -- No need for a base crate that declares the "complete set of queries" -- Each query can define its own storage and doesn't have to be memoized -- Each module only has to know about the queries that it depends on - and that it provides (but no others) -- Compiles to fast code, with no allocation, dynamic dispatch, etc on - the "memoized hit" fast path -- Can recover from cycles gracefully (though I didn't really show - that) -- Should support arenas and other lifetime-based things without requiring - lifetimes everywhere when you're not using them (untested) - -## Example - -There is a working `hello_world` example which is probably the best documentation. -More to come when I expand out a few more patterns. diff --git a/examples/hello_world/README.md b/examples/hello_world/README.md new file mode 100644 index 00000000..b6f4cea9 --- /dev/null +++ b/examples/hello_world/README.md @@ -0,0 +1,172 @@ +The `hello_world` example is intended to walk through the very basics +of a salsa setup. Here is a more detailed writeup. + +### Step 1: Define the query context trait + +The **query context** is the central struct that holds all the state +for your application. It has the current values of all your inputs, +the values of any memoized queries you have executed thus far, and +dependency information between them. + +In your program, however, you rarely interact with the **actual** +query context struct. Instead, you interact with query context +**traits** that you define. These traits define the set of queries +that you need for any given piece of code. You define them using +the `salsa::query_prototype!` macro. + +Here is a simple example of a query context trait from the +`hello_world` example. It defines exactly two queries: `input_string` +and `length`. You see that the `query_prototype!` macro just lists out +the names of the queries as methods (e.g., `input_string()`) and also a +path to a type that will define the query (`InputString`). It doesn't +give many other details: those are specified in the query definition +that comes later. + +```rust +salsa::query_prototype! { + trait HelloWorldContext: salsa::QueryContext { + fn input_string() for InputString; + fn length() for Length; + } +} +``` + +### Step 2: Define the queries + +The actual query definitions are made using the +`salsa::query_definition` macro. For an **input query**, such as +`input_string`, these resemble a variable definition: + +```rust +salsa::query_definition! { + InputString: Map<(), Arc>; +} +``` + +Here, the `Map` is actually a keyword -- you have to write it. The +idea is that each query isn't defining a single value: they are always +a mapping from some **key** to some **value** -- in this case, though, +the type of the key is just the unit type `()` (so in a sense this +*is* a single value). The value type would be `Arc`. + +Note that both keys and values are cloned with relative frequency, so +it's a good idea to pick types that can be cheaply cloned. Also, for +the incremental system to work, keys and value types must not employ +"interior mutability" (no `Mutex` or `AtomicUsize` etc). + +Next let's define the `length` query, which is a function query: + +```rust +salsa::query_definition! { + Length(context: &impl HelloWorldContext, _key: ()) -> usize { + // Read the input string: + let input_string = context.input_string().get(()); + + // Return its length: + input_string.len() + } +} +``` + +Like the `InputString` query, `Length` has a **key** and a **value** +-- but this time the type of the key is specified as the type of the +second argument (`_key`), and the type of the value is specified from +the return type (`usize`). + +You can also see that functions take a first argument, the `context`, +which always has the form `&impl `. This `context` +value gives access to all the other queries that are listed in the +context trait that you specify. + +In the first line of the function we see how we invoke a query: + +```rust +let input_string = context.input_string().get(()); +``` + +When you invoke `context.input_string()`, what you get back is called +a `QueryTable` -- it offers a few methods that let you interact with +the query. The main method you will use though is `get(key)` which -- +given a `key` -- computes and returns the up-to-date value. In the +case of an input query like `input_string`, this just returns whatever +value has been set by the user (if no value has been set yet, it +returns the `Default::default()` value; all query inputs must +implement `Default`). + +### Step 3: Implement the query context trait + +The final step is to create the **query context struct** which will +implement your query context trait(s). This struct combines all the +parts of your system into one whole; it can also add custom state of +your own (such as an interner or configuration). In our simple example +though we won't do any of that. The only field that you **actually** +need is a reference to the **salsa runtime**; then you must also +implement the `QueryContext` trait to tell salsa where to find this +runtime: + +```rust +#[derive(Default)] +struct QueryContextStruct { + runtime: salsa::runtime::Runtime, +} + +impl salsa::QueryContext for QueryContextImpl { + fn salsa_runtime(&self) -> &salsa::runtime::Runtime { + &self.runtime + } +} +``` + +Next, you must use the `query_context_storage!` to define the "storage +struct" for your type. This storage struct contains all the hashmaps +and other things that salsa uses to store the values for your +queries. You won't need to interact with it directly. To use the +macro, you basically list out all the traits and each of the queries +within those traits: + +```rust +query_context_storage! { + struct QueryContextStorage for QueryContextStruct { + // ^^^^^^^^^^^^^^^^^^^ ------------------ + // name of the type the name of your context type + // we will make + impl HelloWorldContext { + fn input_string() for InputString; + fn length() for Length; + } + } +} +``` + +The `query_context_storage` macro will also implement the +`HelloWorldContext` trait for your query context type. + +Now that we've defined our query context, we can start using it: + +```rust +fn main() { + let context = QueryContextStruct::default(); + + println!("Initially, the length is {}.", context.length().get(())); + + context + .input_string() + .set((), Arc::new(format!("Hello, world"))); + + println!("Now, the length is {}.", context.length().get(())); +} +``` + +And if we run this code: + +```bash +> cargo run --example hello_world + Compiling salsa v0.2.0 (/Users/nmatsakis/versioned/salsa) + Finished dev [unoptimized + debuginfo] target(s) in 0.94s + Running `target/debug/examples/hello_world` +Initially, the length is 0. +Now, the length is 12. +``` + +Amazing. + diff --git a/examples/hello_world/main.rs b/examples/hello_world/main.rs new file mode 100644 index 00000000..7c0dc645 --- /dev/null +++ b/examples/hello_world/main.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +/////////////////////////////////////////////////////////////////////////// +// Step 1. Define the query context trait + +// Define a **query context trait** listing out all the prototypes +// that are defined in this section of the code (in real applications +// you would have many of these). For each query, we just give the +// name of the accessor method (`input_string`) and link that to a +// query type (`InputString`) that will be defined later. +salsa::query_prototype! { + trait HelloWorldContext: salsa::QueryContext { + fn input_string() for InputString; + fn length() for Length; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Step 2. Define the queries. + +// Define an **input query**. Like all queries, it is a map from a key +// (of type `()`) to a value (of type `Arc`). All values begin +// as `Default::default` but you can assign them new values. +salsa::query_definition! { + InputString: Map<(), Arc>; +} + +// Define a **function query**. It too has a key and value type, but +// it is defined with a function that -- given the key -- computes the +// value. This function is supplied with a context (an `&impl +// HelloWorldContext`) that gives access to other queries. The runtime +// will track which queries you use so that we can incrementally +// update memoized results. +salsa::query_definition! { + Length(context: &impl HelloWorldContext, _key: ()) -> usize { + // Read the input string: + let input_string = context.input_string().get(()); + + // Return its length: + input_string.len() + } +} + +/////////////////////////////////////////////////////////////////////////// +// Step 3. Implement the query context trait. + +// Define the actual query context struct. This must contain a salsa +// runtime but can also contain anything else you need. +#[derive(Default)] +struct QueryContextStruct { + runtime: salsa::runtime::Runtime, +} + +// Tell salsa where to find the runtime in your context. +impl salsa::QueryContext for QueryContextStruct { + fn salsa_runtime(&self) -> &salsa::runtime::Runtime { + &self.runtime + } +} + +// Define the full set of queries that your context needs. This would +// in general combine (and implement) all the query context traits in +// your application into one place, allocating storage for all of +// them. +salsa::query_context_storage! { + pub struct QueryContextStorage for QueryContextStruct { + impl HelloWorldContext { + fn input_string() for InputString; + fn length() for Length; + } + } +} + +// This shows how to use a query. +fn main() { + let context = QueryContextStruct::default(); + + println!("Initially, the length is {}.", context.length().get(())); + + context + .input_string() + .set((), Arc::new(format!("Hello, world"))); + + println!("Now, the length is {}.", context.length().get(())); +}