mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-02-09 05:38:15 +00:00
463 lines
23 KiB
HTML
463 lines
23 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Plumbing - Salsa</title>
|
|
|
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
<link rel="stylesheet" href="css/variables.css">
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" href="css/chrome.css">
|
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="highlight.css">
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
<link rel="stylesheet" href="mermaid.css">
|
|
|
|
|
|
|
|
</head>
|
|
<body class="light">
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">
|
|
var path_to_root = "";
|
|
var default_theme = "light";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
document.body.className = theme;
|
|
document.querySelector('html').className = theme + ' js';
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div class="sidebar-scrollbox">
|
|
<ol class="chapter"><li><a href="about_salsa.html"><strong aria-hidden="true">1.</strong> About salsa</a></li><li><a href="how_to_use.html"><strong aria-hidden="true">2.</strong> How to use Salsa</a></li><li><a href="how_salsa_works.html"><strong aria-hidden="true">3.</strong> How Salsa works</a></li><li><a href="common_patterns.html"><strong aria-hidden="true">4.</strong> Common patterns</a></li><li><ol class="section"><li><a href="common_patterns/selection.html"><strong aria-hidden="true">4.1.</strong> Selection</a></li><li><a href="common_patterns/on_demand_inputs.html"><strong aria-hidden="true">4.2.</strong> On-demand (Lazy) inputs</a></li></ol></li><li><a href="videos.html"><strong aria-hidden="true">5.</strong> YouTube videos</a></li><li><a href="plumbing.html" class="active"><strong aria-hidden="true">6.</strong> Plumbing</a></li></ol>
|
|
</div>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
|
|
<div id="menu-bar" class="menu-bar">
|
|
<div id="menu-bar-sticky-container">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<h1 class="menu-title">Salsa</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<h1><a class="header" href="#plumbing" id="plumbing">Plumbing</a></h1>
|
|
<p><strong>Last updated:</strong> 2020-06-24</p>
|
|
<p>This chapter documents the code that salsa generates and its "inner workings".
|
|
We refer to this as the "plumbing".</p>
|
|
<p>This page walks through the <a href="https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs">"Hello, World!"</a> example and explains the code that
|
|
it generates. Please take it with a grain of salt: while we make an effort to
|
|
keep this documentation up to date, this sort of thing can fall out of date
|
|
easily.</p>
|
|
<p>If you'd like to see for yourself, you can set the environment variable
|
|
<code>SALSA_DUMP</code> to 1 while the procedural macro runs, and it will dump the full
|
|
output to stdout. I recommend piping the output through rustfmt.</p>
|
|
<h2><a class="header" href="#query-groups-and-query-group-structs" id="query-groups-and-query-group-structs">Query groups and query group structs</a></h2>
|
|
<p>When you define a query group trait:</p>
|
|
<pre><code class="language-rust ignore">#[salsa::query_group(HelloWorldStorage)]
|
|
trait HelloWorld: salsa::Database {
|
|
// For each query, we give the name, some input keys (here, we
|
|
// have one key, `()`) and the output type `Arc<String>`. We can
|
|
// use attributes to give other configuration:
|
|
//
|
|
// - `salsa::input` indicates that this is an "input" to the system,
|
|
// which must be explicitly set. The `salsa::query_group` method
|
|
// will autogenerate a `set_input_string` method that can be
|
|
// used to set the input.
|
|
#[salsa::input]
|
|
fn input_string(&self, key: ()) -> Arc<String>;
|
|
|
|
// This is a *derived query*, meaning its value is specified by
|
|
// a function (see Step 2, below).
|
|
fn length(&self, key: ()) -> usize;
|
|
}
|
|
</code></pre>
|
|
<p>the <code>salsa::query_group</code> macro generates a number of things:</p>
|
|
<ul>
|
|
<li>a copy of the <code>HelloWorld</code> trait, minus the salsa annotations, and lightly edited</li>
|
|
<li>a "group struct" named <code>HelloWorldStorage</code> that represents the group; this struct implements <code>plumbing::QueryGroup</code>
|
|
<ul>
|
|
<li>somewhat confusingly, this struct doesn't actually contain the storage itself, but rather has an associated type that leads to the "true" storage struct</li>
|
|
</ul>
|
|
</li>
|
|
<li>an impl of the <code>HelloWorld</code> trait, for any database type</li>
|
|
<li>for each query, a "query struct" named after the query; these structs implement <code>plumbing::Query</code> and sometimes other plumbing traits</li>
|
|
<li>a group key, an enum that can identify any query within the group and store its key</li>
|
|
<li>the associated storage struct, which contains the actual hashmaps that store the data for all queries in the group</li>
|
|
</ul>
|
|
<p>Note that there are a number of structs and types (e.g., the group descriptor
|
|
and associated storage struct) that represent things which don't have "public"
|
|
names. We currently generate mangled names with <code>__</code> afterwards, but those names
|
|
are not meant to be exposed to the user (ideally we'd use hygiene to enforce
|
|
this).</p>
|
|
<p>So the generated code looks something like this. We'll go into more detail on
|
|
each part in the following sections.</p>
|
|
<pre><code class="language-rust ignore">// First, a copy of the trait, though sometimes with some extra
|
|
// methods (e.g., `set_input_string`)
|
|
trait HelloWorld: salsa::Database {
|
|
fn input_string(&self, key: ()) -> Arc<String>;
|
|
fn set_input_string(&mut self, key: (), value: Arc<String>);
|
|
fn length(&self, key: ()) -> usize;
|
|
}
|
|
|
|
// Next, the group struct
|
|
struct HelloWorldStorage { }
|
|
impl<DB> salsa::plumbing::QueryGroup<DB> for HelloWorldStorage { ... }
|
|
|
|
// Next, the impl of the trait
|
|
impl<DB> HelloWorld for DB
|
|
where
|
|
DB: salsa::Database,
|
|
DB: salsa::plumbing::HasQueryGroup<HelloWorldStorage>,
|
|
{
|
|
...
|
|
}
|
|
|
|
// Next, a series of query structs and query impls
|
|
struct InputQuery { }
|
|
unsafe impl<DB> salsa::Query<DB> for InputQuery
|
|
where
|
|
DB: HelloWorld,
|
|
DB: salsa::plumbing::HasQueryGroup<#group_struct>,
|
|
DB: salsa::Database,
|
|
{
|
|
...
|
|
}
|
|
struct LengthQuery { }
|
|
unsafe impl<DB> salsa::Query<DB> for LengthQuery
|
|
where
|
|
DB: HelloWorld,
|
|
DB: salsa::plumbing::HasQueryGroup<#group_struct>,
|
|
DB: salsa::Database,
|
|
{
|
|
...
|
|
}
|
|
|
|
// For derived queries, those include implementations
|
|
// of additional traits like `QueryFunction`
|
|
unsafe impl<DB> salsa::QueryFunction<DB> for LengthQuery
|
|
where
|
|
DB: HelloWorld,
|
|
DB: salsa::plumbing::HasQueryGroup<#group_struct>,
|
|
DB: salsa::Database,
|
|
{
|
|
...
|
|
}
|
|
|
|
// The group key
|
|
enum HelloWorldGroupKey__ { .. }
|
|
|
|
// The group storage
|
|
struct HelloWorldGroupStorage__ { .. }
|
|
</code></pre>
|
|
<h3><a class="header" href="#the-group-struct-and-querygroup-trait" id="the-group-struct-and-querygroup-trait">The group struct and <code>QueryGroup</code> trait</a></h3>
|
|
<p>The group struct is the only thing we generate whose name is known to the user.
|
|
For a query group named <code>Foo</code>, it is conventionally called <code>FooStorage</code>, hence
|
|
the name <code>HelloWorldStorage</code> in our example.</p>
|
|
<p>Despite the name "Storage", the struct itself has no fields. It exists only to
|
|
implement the <code>QueryGroup</code> trait. This <em>trait</em> has a number of associated types
|
|
that reference various bits of the query group, including the actual "group
|
|
storage" struct:</p>
|
|
<pre><code class="language-rust ignore">struct HelloWorldStorage { }
|
|
impl<DB> salsa::plumbing::QueryGroup<DB> for HelloWorldStorage {
|
|
type GroupStorage = HelloWorldGroupStorage__; // generated struct
|
|
type GroupKey = HelloWorldGroupKey__;
|
|
type GroupData = ((), Arc<String>, (), usize);
|
|
}
|
|
</code></pre>
|
|
<p>We'll go into detail on these types below and the role they play, but one that
|
|
we didn't mention yet is <code>GroupData</code>. That is a kind of hack used to manage
|
|
send/sync around slots, and it gets covered in the section on slots.</p>
|
|
<h3><a class="header" href="#impl-of-the-hello-world-trait" id="impl-of-the-hello-world-trait">Impl of the hello world trait</a></h3>
|
|
<p>Ultimately, every salsa query group is going to be implemented by your final
|
|
database type, which is not currently known to us (it is created by combining
|
|
multiple salsa query groups). In fact, this salsa query group could be composed
|
|
into multiple database types. However, we want to generate the impl of the query-group
|
|
trait here in this crate, because this is the point where the trait definition is visible
|
|
and known to us (otherwise, we'd have to duplicate the method definitions).</p>
|
|
<p>So what we do is that we define a different trait, called <code>plumbing::HasQueryGroup<G></code>,
|
|
that can be implemented by the database type. <code>HasQueryGroup</code> is generic over
|
|
the query group struct. So then we can provide an impl of <code>HelloWorld</code> for any
|
|
database type <code>DB</code> where <code>DB: HasQueryGroup<HelloWorldStorage></code>. This
|
|
<code>HasQueryGroup</code> defines a few methods that, given a <code>DB</code>, give access to the
|
|
data for the query group and a few other things.</p>
|
|
<p>Thus we can generate an impl that looks like:</p>
|
|
<pre><code class="language-rust ignore">impl<DB> HelloWorld for DB
|
|
where
|
|
DB: salsa::Database,
|
|
DB: salsa::plumbing::HasQueryGroup<HelloWorld>
|
|
{
|
|
...
|
|
fn length(&self, key: ()) -> Arc<String> {
|
|
<Self as salsa::plumbing::GetQueryTable<HelloWorldLength__>>::get_query_table(self).get(())
|
|
}
|
|
}
|
|
</code></pre>
|
|
<p>You can see that the various methods just hook into generic functions in the
|
|
<code>salsa::plumbing</code> module. These functions are generic over the query types
|
|
(<code>HelloWorldLength__</code>) that will be described shortly. The details of the "query
|
|
table" are covered in a future section, but in short this code pulls out the
|
|
hasmap for storing the <code>length</code> results and invokes the generic salsa logic to
|
|
check for a valid result, etc.</p>
|
|
<h3><a class="header" href="#for-each-query-a-query-struct" id="for-each-query-a-query-struct">For each query, a query struct</a></h3>
|
|
<p>As we referenced in the previous section, each query in the trait gets a struct
|
|
that represents it. This struct is named after the query, converted into snake
|
|
case and with the word <code>Query</code> appended. In typical Salsa workflows, these
|
|
structs are not meant to be named or used, but in some cases it may be required.
|
|
For e.g. the <code>length</code> query, this structs might look something like:</p>
|
|
<pre><code class="language-rust ignore">struct LengthQuery { }
|
|
</code></pre>
|
|
<p>The struct also implements the <code>plumbing::Query</code> trait, which defines
|
|
a bunch of metadata about the query (and repeats, for convenience,
|
|
some of the data about the group that the query is in):</p>
|
|
<pre><code class="language-rust ignore">unsafe impl<DB> salsa::Query<DB> for LengthQuery
|
|
where
|
|
DB: HelloWorld,
|
|
DB: salsa::plumbing::HasQueryGroup<#group_struct>,
|
|
DB: salsa::Database,
|
|
{
|
|
// A tuple of the types of the function parameters trom trait.
|
|
type Key = ((), );
|
|
|
|
// The return value of the function in the trait.
|
|
type Value = Arc<String>;
|
|
|
|
// The "query storage" references a type from within salsa
|
|
// that stores the actual query data and defines the
|
|
// logic for accessing and revalidating it.
|
|
//
|
|
// It is generic over the query type which lets it
|
|
// customize itself to the keys/value of this particular
|
|
// query.
|
|
type Storage = salsa::derived::DerivedStorage<
|
|
DB,
|
|
LengthQuery,
|
|
salsa::plumbing::MemoizedStorage,
|
|
>;
|
|
|
|
// Types from the query group, repeated for convenience.
|
|
type Group = HelloWorldStorage;
|
|
type GroupStorage = HelloWorldGroupStorage__;
|
|
type GroupKey = HelloWorldGroupKey__;
|
|
|
|
// Given the storage for the entire group, extract
|
|
// the storage for just this query. Described when
|
|
// we talk about group storage.
|
|
fn query_storage(
|
|
group_storage: &HelloWorldGroupStorage__,
|
|
) -> &std::sync::Arc<Self::Storage> {
|
|
&group_storage.length
|
|
}
|
|
|
|
// Given the key for this query, construct the "group key"
|
|
// that situates it within the group. Described when
|
|
// we talk about group key.
|
|
fn group_key(key: Self::Key) -> Self::GroupKey {
|
|
HelloWorldGroupKey__::length(key)
|
|
}
|
|
}
|
|
</code></pre>
|
|
<p>Depending on the kind of query, we may also generate other impls, such as an
|
|
impl of <code>salsa::plumbing::QueryFunction</code>, which defines the methods for
|
|
executing the body of a query. This impl would then include a call to the user's
|
|
actual function.</p>
|
|
<h3><a class="header" href="#group-key" id="group-key">Group key</a></h3>
|
|
<p>The "query key" is the inputs to the query, and identifies a particular query
|
|
instace: in our example, it is a value of type <code>()</code> (so there is only one
|
|
instance of the query), but typically it's some other type. The "group key" then
|
|
broadens that to include the identifier of the query within the group. So instead
|
|
of just <code>()</code> the group key would encode (e.g.) <code>Length(())</code> (the "length" query
|
|
applied to the <code>()</code> key). It is represented as an enum, which we generate,
|
|
with one variant per query:</p>
|
|
<pre><code class="language-rust ignore">#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
enum HelloWorldGroupKey__ {
|
|
input(()),
|
|
length(()),
|
|
}
|
|
</code></pre>
|
|
<p>The <code>Query</code> trait that we saw earlier includes a method <code>group_key</code> for wrapping
|
|
the key for some individual query into the group key.</p>
|
|
<h3><a class="header" href="#group-storege" id="group-storege">Group storege</a></h3>
|
|
<p>The "group storage" is the actual struct that contains all the hashtables and
|
|
so forth for each query. The types of these are ultimately defined by the
|
|
<code>Storage</code> associated type for each query type. The struct is generic over the
|
|
final database type:</p>
|
|
<pre><code class="language-rust ignore">struct HelloWorldGroupStorage__<DB> {
|
|
input: <InputQuery as Query<DB>>::Storage,
|
|
length: <LengthQuery as Query<DB>>::Storage,
|
|
}
|
|
</code></pre>
|
|
<p>We also generate some impls: first is an impl of <code>Default</code> and the second is a
|
|
method <code>for_each_query</code> that simply iterates over each field and invokes a
|
|
method on it. This method is called by some of the code we generate for the
|
|
database in order to implement debugging methods that "sweep" over all the
|
|
queries.</p>
|
|
<pre><code class="language-rust ignore">impl<DB> HelloWorldGroupStorage__<DB> {
|
|
fn for_each_query(&self, db: &DB, method: &mut dyn FnMut(...)) {
|
|
...
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
<a rel="prev" href="videos.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
<a href="videos.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
<script type="text/javascript" src="mermaid.min.js"></script>
|
|
|
|
<script type="text/javascript" src="mermaid-init.js"></script>
|
|
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|