You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
32 KiB
HTML
516 lines
32 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js rust">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Lenses and Prisms - Rust Design Patterns</title>
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
<meta name="description" content="A catalogue of Rust design patterns, anti-patterns and idioms">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="icon" href="../favicon.svg">
|
|
<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 rel="stylesheet" href="../fonts/fonts.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 -->
|
|
|
|
</head>
|
|
<body>
|
|
<div id="body-container">
|
|
<!-- Provide site root to javascript -->
|
|
<script>
|
|
var path_to_root = "../";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "rust";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script>
|
|
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>
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
var html = document.querySelector('html');
|
|
html.classList.remove('no-js')
|
|
html.classList.remove('rust')
|
|
html.classList.add(theme);
|
|
html.classList.add('js');
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script>
|
|
var html = document.querySelector('html');
|
|
var sidebar = null;
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
} else {
|
|
sidebar = 'hidden';
|
|
}
|
|
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 class="chapter-item expanded "><a href="../intro.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../translations.html"><strong aria-hidden="true">1.1.</strong> Translations</a></li></ol></li><li class="chapter-item expanded "><a href="../idioms/index.html"><strong aria-hidden="true">2.</strong> Idioms</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../idioms/coercion-arguments.html"><strong aria-hidden="true">2.1.</strong> Use borrowed types for arguments</a></li><li class="chapter-item expanded "><a href="../idioms/concat-format.html"><strong aria-hidden="true">2.2.</strong> Concatenating Strings with format!</a></li><li class="chapter-item expanded "><a href="../idioms/ctor.html"><strong aria-hidden="true">2.3.</strong> Constructor</a></li><li class="chapter-item expanded "><a href="../idioms/default.html"><strong aria-hidden="true">2.4.</strong> The Default Trait</a></li><li class="chapter-item expanded "><a href="../idioms/deref.html"><strong aria-hidden="true">2.5.</strong> Collections Are Smart Pointers</a></li><li class="chapter-item expanded "><a href="../idioms/dtor-finally.html"><strong aria-hidden="true">2.6.</strong> Finalisation in Destructors</a></li><li class="chapter-item expanded "><a href="../idioms/mem-replace.html"><strong aria-hidden="true">2.7.</strong> mem::{take(_), replace(_)}</a></li><li class="chapter-item expanded "><a href="../idioms/on-stack-dyn-dispatch.html"><strong aria-hidden="true">2.8.</strong> On-Stack Dynamic Dispatch</a></li><li class="chapter-item expanded "><a href="../idioms/ffi/intro.html"><strong aria-hidden="true">2.9.</strong> Foreign function interface (FFI)</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../idioms/ffi/errors.html"><strong aria-hidden="true">2.9.1.</strong> Idiomatic Errors</a></li><li class="chapter-item expanded "><a href="../idioms/ffi/accepting-strings.html"><strong aria-hidden="true">2.9.2.</strong> Accepting Strings</a></li><li class="chapter-item expanded "><a href="../idioms/ffi/passing-strings.html"><strong aria-hidden="true">2.9.3.</strong> Passing Strings</a></li></ol></li><li class="chapter-item expanded "><a href="../idioms/option-iter.html"><strong aria-hidden="true">2.10.</strong> Iterating over an Option</a></li><li class="chapter-item expanded "><a href="../idioms/pass-var-to-closure.html"><strong aria-hidden="true">2.11.</strong> Pass Variables to Closure</a></li><li class="chapter-item expanded "><a href="../idioms/priv-extend.html"><strong aria-hidden="true">2.12.</strong> Privacy For Extensibility</a></li><li class="chapter-item expanded "><a href="../idioms/rustdoc-init.html"><strong aria-hidden="true">2.13.</strong> Easy doc initialization</a></li><li class="chapter-item expanded "><a href="../idioms/temporary-mutability.html"><strong aria-hidden="true">2.14.</strong> Temporary mutability</a></li><li class="chapter-item expanded "><a href="../idioms/return-consumed-arg-on-error.html"><strong aria-hidden="true">2.15.</strong> Return consumed arg on error</a></li></ol></li><li class="chapter-item expanded "><a href="../patterns/index.html"><strong aria-hidden="true">3.</strong> Design Patterns</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../patterns/behavioural/intro.html"><strong aria-hidden="true">3.1.</strong> Behavioural</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../patterns/behavioural/command.html"><strong aria-hidden="true">3.1.1.</strong> Command</a></li><li class="chapter-item expanded "><a href="../patterns/behavioural/interpreter.html"><strong aria-hidden="true">3.1.2.</strong> Interpreter</a></li><li class="chapter-item expanded "><a href="../patterns/behavioural/newtype.html"><strong aria-hidden="true">3.1.3.</strong> Newtype</a></li><li class="chapter-item expanded "><a href="../patterns/behavioural/RAII.html"><strong aria-hidden="true">3.1.4.</strong> RAII Guards</a></li><li class="chapter-item expanded "><a href="../patterns/behavioural/strategy.html"><strong aria-hidden="true">3.1.5.</strong> Strategy</a></li><li class="chapter-item expanded "><a href="../patterns/behavioural/visitor.html"><strong aria-hidden="true">3.1.6.</strong> Visitor</a></li></ol></li><li class="chapter-item expanded "><a href="../patterns/creational/intro.html"><strong aria-hidden="true">3.2.</strong> Creational</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../patterns/creational/builder.html"><strong aria-hidden="true">3.2.1.</strong> Builder</a></li><li class="chapter-item expanded "><a href="../patterns/creational/fold.html"><strong aria-hidden="true">3.2.2.</strong> Fold</a></li></ol></li><li class="chapter-item expanded "><a href="../patterns/structural/intro.html"><strong aria-hidden="true">3.3.</strong> Structural</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../patterns/structural/compose-structs.html"><strong aria-hidden="true">3.3.1.</strong> Compose Structs</a></li><li class="chapter-item expanded "><a href="../patterns/structural/small-crates.html"><strong aria-hidden="true">3.3.2.</strong> Prefer Small Crates</a></li><li class="chapter-item expanded "><a href="../patterns/structural/unsafe-mods.html"><strong aria-hidden="true">3.3.3.</strong> Contain unsafety in small modules</a></li></ol></li><li class="chapter-item expanded "><a href="../patterns/ffi/intro.html"><strong aria-hidden="true">3.4.</strong> Foreign function interface (FFI)</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../patterns/ffi/export.html"><strong aria-hidden="true">3.4.1.</strong> Object-Based APIs</a></li><li class="chapter-item expanded "><a href="../patterns/ffi/wrappers.html"><strong aria-hidden="true">3.4.2.</strong> Type Consolidation into Wrappers</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="../anti_patterns/index.html"><strong aria-hidden="true">4.</strong> Anti-patterns</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../anti_patterns/borrow_clone.html"><strong aria-hidden="true">4.1.</strong> Clone to satisfy the borrow checker</a></li><li class="chapter-item expanded "><a href="../anti_patterns/deny-warnings.html"><strong aria-hidden="true">4.2.</strong> #[deny(warnings)]</a></li><li class="chapter-item expanded "><a href="../anti_patterns/deref.html"><strong aria-hidden="true">4.3.</strong> Deref Polymorphism</a></li></ol></li><li class="chapter-item expanded "><a href="../functional/index.html"><strong aria-hidden="true">5.</strong> Functional Programming</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../functional/paradigms.html"><strong aria-hidden="true">5.1.</strong> Programming paradigms</a></li><li class="chapter-item expanded "><a href="../functional/generics-type-classes.html"><strong aria-hidden="true">5.2.</strong> Generics as Type Classes</a></li><li class="chapter-item expanded "><a href="../functional/lenses.html" class="active"><strong aria-hidden="true">5.3.</strong> Lenses and Prisms</a></li></ol></li><li class="chapter-item expanded "><a href="../additional_resources/index.html"><strong aria-hidden="true">6.</strong> Additional Resources</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../additional_resources/design-principles.html"><strong aria-hidden="true">6.1.</strong> Design principles</a></li></ol></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-hover-placeholder"></div>
|
|
<div id="menu-bar" class="menu-bar sticky bordered">
|
|
<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</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">Rust Design Patterns</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>
|
|
<a href="https://github.com/rust-unofficial/patterns" title="Git repository" aria-label="Git repository">
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|
</a>
|
|
<a href="https://github.com/rust-unofficial/patterns/edit/main/./functional/lenses.md" title="Suggest an edit" aria-label="Suggest an edit">
|
|
<i id="git-edit-button" class="fa fa-edit"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="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>
|
|
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 id="lenses-and-prisms"><a class="header" href="#lenses-and-prisms">Lenses and Prisms</a></h1>
|
|
<p>This is a pure functional concept that is not frequently used in Rust.
|
|
Nevertheless, exploring the concept may be helpful to understand other
|
|
patterns in Rust APIs, such as <a href="../patterns/behavioural/visitor.html">visitors</a>.
|
|
They also have niche use cases.</p>
|
|
<h2 id="lenses-uniform-access-across-types"><a class="header" href="#lenses-uniform-access-across-types">Lenses: Uniform Access Across Types</a></h2>
|
|
<p>A lens is a concept from functional programming languages that allows
|
|
accessing parts of a data type in an abstract, unified way.<sup class="footnote-reference"><a href="#1">1</a></sup>
|
|
In basic concept, it is similar to the way Rust traits work with type erasure,
|
|
but it has a bit more power and flexibility.</p>
|
|
<p>For example, suppose a bank contains several JSON formats for customer
|
|
data.
|
|
This is because they come from different databases or legacy systems.
|
|
One database contains the data needed to perform credit checks:</p>
|
|
<pre><code class="language-json">{ "name": "Jane Doe",
|
|
"dob": "2002-02-24",
|
|
[...]
|
|
"customer_id": 1048576332,
|
|
}
|
|
</code></pre>
|
|
<p>Another one contains the account information:</p>
|
|
<pre><code class="language-json">{ "customer_id": 1048576332,
|
|
"accounts": [
|
|
{ "account_id": 2121,
|
|
"account_type: "savings",
|
|
"joint_customer_ids": [],
|
|
[...]
|
|
},
|
|
{ "account_id": 2122,
|
|
"account_type: "checking",
|
|
"joint_customer_ids": [1048576333],
|
|
[...]
|
|
},
|
|
]
|
|
}
|
|
</code></pre>
|
|
<p>Notice that both types have a customer ID number which corresponds to a person.
|
|
How would a single function handle both records of different types?</p>
|
|
<p>In Rust, a <code>struct</code> could represent each of these types, and a trait would have
|
|
a <code>get_customer_id</code> function they would implement:</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2018"><span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span>use std::collections::HashSet;
|
|
|
|
pub struct Account {
|
|
account_id: u32,
|
|
account_type: String,
|
|
// other fields omitted
|
|
}
|
|
|
|
pub trait CustomerId {
|
|
fn get_customer_id(&self) -> u64;
|
|
}
|
|
|
|
pub struct CreditRecord {
|
|
customer_id: u64,
|
|
name: String,
|
|
dob: String,
|
|
// other fields omitted
|
|
}
|
|
|
|
impl CustomerId for CreditRecord {
|
|
fn get_customer_id(&self) -> u64 {
|
|
self.customer_id
|
|
}
|
|
}
|
|
|
|
pub struct AccountRecord {
|
|
customer_id: u64,
|
|
accounts: Vec<Account>,
|
|
}
|
|
|
|
impl CustomerId for AccountRecord {
|
|
fn get_customer_id(&self) -> u64 {
|
|
self.customer_id
|
|
}
|
|
}
|
|
|
|
// static polymorphism: only one type, but each function call can choose it
|
|
fn unique_ids_set<R: CustomerId>(records: &[R]) -> HashSet<u64> {
|
|
records.iter().map(|r| r.get_customer_id()).collect()
|
|
}
|
|
|
|
// dynamic dispatch: iterates over any type with a customer ID, collecting all
|
|
// values together
|
|
fn unique_ids_iter<I>(iterator: I) -> HashSet<u64>
|
|
where I: Iterator<Item=Box<dyn CustomerId>>
|
|
{
|
|
iterator.map(|r| r.as_ref().get_customer_id()).collect()
|
|
}
|
|
<span class="boring">}</span></code></pre></pre>
|
|
<p>Lenses, however, allow the code supporting customer ID to be moved from the
|
|
<em>type</em> to the <em>accessor function</em>.
|
|
Rather than implementing a trait on each type, all matching structures can
|
|
simply be accessed the same way.</p>
|
|
<p>While the Rust language itself does not support this (type erasure is the
|
|
preferred solution to this problem), the <a href="https://github.com/TOETOE55/lens-rs/blob/master/guide.md">lens-rs
|
|
crate</a> allows code
|
|
that feels like this to be written with macros:</p>
|
|
<pre><code class="language-rust ignore">use std::collections::HashSet;
|
|
|
|
use lens_rs::{optics, Lens, LensRef, Optics};
|
|
|
|
#[derive(Clone, Debug, Lens /* derive to allow lenses to work */)]
|
|
pub struct CreditRecord {
|
|
#[optic(ref)] // macro attribute to allow viewing this field
|
|
customer_id: u64,
|
|
name: String,
|
|
dob: String,
|
|
// other fields omitted
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Account {
|
|
account_id: u32,
|
|
account_type: String,
|
|
// other fields omitted
|
|
}
|
|
|
|
#[derive(Clone, Debug, Lens)]
|
|
pub struct AccountRecord {
|
|
#[optic(ref)]
|
|
customer_id: u64,
|
|
accounts: Vec<Account>,
|
|
}
|
|
|
|
fn unique_ids_lens<T>(iter: impl Iterator<Item = T>) -> HashSet<u64>
|
|
where
|
|
T: LensRef<Optics![customer_id], u64>, // any type with this field
|
|
{
|
|
iter.map(|r| *r.view_ref(optics!(customer_id))).collect()
|
|
}</code></pre>
|
|
<p>The version of <code>unique_ids_lens</code> shown here allows any type to be in the iterator,
|
|
so long as it has an attribute called <code>customer_id</code> which can be accessed by
|
|
the function.
|
|
This is how most functional programming languages operate on lenses.</p>
|
|
<p>Rather than macros, they achieve this with a technique known as "currying".
|
|
That is, they "partially construct" the function, leaving the type of the
|
|
final parameter (the value being operated on) unfilled until the function is
|
|
called.
|
|
Thus it can be called with different types dynamically even from one place in
|
|
the code.
|
|
That is what the <code>optics!</code> and <code>view_ref</code> in the example above simulates.</p>
|
|
<p>The functional approach need not be restricted to accessing members.
|
|
More powerful lenses can be created which both <em>set</em> and <em>get</em> data in a
|
|
structure.
|
|
But the concept really becomes interesting when used as a building block for
|
|
composition.
|
|
That is where the concept appears more clearly in Rust.</p>
|
|
<h2 id="prisms-a-higher-order-form-of-optics"><a class="header" href="#prisms-a-higher-order-form-of-optics">Prisms: A Higher-Order form of "Optics"</a></h2>
|
|
<p>A simple function such as <code>unique_ids_lens</code> above operates on a single lens.
|
|
A <em>prism</em> is a function that operates on a <em>family</em> of lenses.
|
|
It is one conceptual level higher, using lenses as a building block, and
|
|
continuing the metaphor, is part of a family of "optics".
|
|
It is the main one that is useful in understanding Rust APIs, so will be the
|
|
focus here.</p>
|
|
<p>The same way that traits allow "lens-like" design with static polymorphism and
|
|
dynamic dispatch, prism-like designs appear in Rust APIs which split problems
|
|
into multiple associated types to be composed.
|
|
A good example of this is the traits in the parsing crate <em>Serde</em>.</p>
|
|
<p>Trying to understand the way <em>Serde</em> works by only reading the API is a
|
|
challenge, especially the first time.
|
|
Consider the <code>Deserializer</code> trait, implemented by some type in any library
|
|
which parses a new format:</p>
|
|
<pre><code class="language-rust ignore">pub trait Deserializer<'de>: Sized {
|
|
type Error: Error;
|
|
|
|
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
where
|
|
V: Visitor<'de>;
|
|
|
|
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
where
|
|
V: Visitor<'de>;
|
|
|
|
// remainder ommitted
|
|
}</code></pre>
|
|
<p>For a trait that is just supposed to parse data from a format and return a
|
|
value, this looks odd.</p>
|
|
<p>Why are all the return types type erased?</p>
|
|
<p>To understand that, we need to keep the lens concept in mind and look at
|
|
the definition of the <code>Visitor</code> type that is passed in generically:</p>
|
|
<pre><code class="language-rust ignore">pub trait Visitor<'de>: Sized {
|
|
type Value;
|
|
|
|
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
|
|
where
|
|
E: Error;
|
|
|
|
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
where
|
|
E: Error;
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: Error;
|
|
|
|
// remainder omitted
|
|
}</code></pre>
|
|
<p>The job of the <code>Visitor</code> type is to construct values in the <em>Serde</em> data model,
|
|
which are represented by its associated <code>Value</code> type.</p>
|
|
<p>These values represent parts of the Rust value being deserialized.
|
|
If this fails, it returns an <code>Error</code> type - an error type determined by the
|
|
<code>Deserializer</code> when its methods were called.</p>
|
|
<p>This highlights that <code>Deserializer</code> is similar to <code>CustomerId</code> from earlier,
|
|
allowing any format parser which implements it to create <code>Value</code>s based on what
|
|
it parsed.
|
|
The <code>Value</code> trait is acting like a lens in functional programming languages.</p>
|
|
<p>But unlike the <code>CustomerId</code> trait, the return types of <code>Visitor</code> methods are
|
|
<em>generic</em>, and the concrete <code>Value</code> type is <em>determined by the Visitor itself</em>.</p>
|
|
<p>Instead of acting as one lens, it effectively acts as a family of
|
|
lenses, one for each concrete type of <code>Visitor</code>.</p>
|
|
<p>The <code>Deserializer</code> API is based on having a generic set of "lenses" work across
|
|
a set of other generic types for "observation".
|
|
It is a <em>prism</em>.</p>
|
|
<p>For example, consider the identity record from earlier but simplified:</p>
|
|
<pre><code class="language-json">{ "name": "Jane Doe",
|
|
"customer_id": 1048576332,
|
|
}
|
|
</code></pre>
|
|
<p>How would the <em>Serde</em> library deserialize this JSON into <code>struct CreditRecord</code>?</p>
|
|
<ol>
|
|
<li>The user would call a library function to deserialize the data. This would
|
|
create a <code>Deserializer</code> based on the JSON format.</li>
|
|
<li>Based on the fields in the struct, a <code>Visitor</code> would be created (more on
|
|
that in a moment) which knows how to create each type in a generic data
|
|
model that was needed to represent it: <code>u64</code> and <code>String</code>.</li>
|
|
<li>The deserializer would make calls to the <code>Visitor</code> as it parsed items.</li>
|
|
<li>The <code>Visitor</code> would indicate if the items found were expected, and if not,
|
|
raise an error to indicate deserialization has failed.</li>
|
|
</ol>
|
|
<p>For our very simple structure above, the expected pattern would be:</p>
|
|
<ol>
|
|
<li>Visit a map (<em>Serde</em>'s equvialent to <code>HashMap</code> or JSON's dictionary).</li>
|
|
<li>Visit a string key called "name".</li>
|
|
<li>Visit a string value, which will go into the <code>name</code> field.</li>
|
|
<li>Visit a string key called "customer_id".</li>
|
|
<li>Visit a string value, which will go into the <code>customer_id</code> field.</li>
|
|
<li>Visit the end of the map.</li>
|
|
</ol>
|
|
<p>But what determines which "observation" pattern is expected?</p>
|
|
<p>A functional programming language would be able to use currying to create
|
|
reflection of each type based on the type itself.
|
|
Rust does not support that, so every single type would need to have its own
|
|
code written based on its fields and their properties.</p>
|
|
<p><em>Serde</em> solves this usability challenge with a derive macro:</p>
|
|
<pre><code class="language-rust ignore">use serde::Deserialize;
|
|
|
|
#[derive(Deserialize)]
|
|
struct IdRecord {
|
|
name: String,
|
|
customer_id: String,
|
|
}</code></pre>
|
|
<p>That macro simply generates an impl block causing the struct to implement a
|
|
trait called <code>Deserialize</code>.</p>
|
|
<p>It is defined this way:</p>
|
|
<pre><code class="language-rust ignore">pub trait Deserialize<'de>: Sized {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>;
|
|
}</code></pre>
|
|
<p>This is the function that determines how to create the struct itself.
|
|
Code is generated based on the struct's fields.
|
|
When the parsing library is called - in our example, a JSON parsing library -
|
|
it creates a <code>Deserializer</code> and calls <code>Type::deserialize</code> with it as a
|
|
parameter.</p>
|
|
<p>The <code>deserialize</code> code will then create a <code>Visitor</code> which will have its calls
|
|
"refracted" by the <code>Deserializer</code>.
|
|
If everything goes well, eventually that <code>Visitor</code> will construct a value
|
|
corresponding to the type being parsed and return it.</p>
|
|
<p>For a complete example, see the <a href="https://serde.rs/deserialize-struct.html"><em>Serde</em>
|
|
documentation</a>.</p>
|
|
<p>To wrap up, this is the power of <em>Serde</em>:</p>
|
|
<ol>
|
|
<li>The structure being parsed is represented by an <code>impl</code> block for <code>Deserialize</code></li>
|
|
<li>The input data format (e.g. JSON) is represented by a <code>Deserializer</code> called
|
|
by <code>Deserialize</code></li>
|
|
<li>The <code>Deserializer</code> acts like a prism which "refracts" lens-like <code>Visitor</code>
|
|
calls which actually build the data value</li>
|
|
</ol>
|
|
<p>The result is that types to be deserialized only implement the "top layer" of
|
|
the API, and file formats only need to implement the "bottom layer".
|
|
Each piece can then "just work" with the rest of the ecosystem, since generic
|
|
types will bridge them.</p>
|
|
<p>To emphasize, the only reason this model works on any format and any type is
|
|
because the <code>Deserializer</code> trait's output type <strong>is specified by the
|
|
implementor of <code>Visitor</code> it is passed</strong>, rather than being tied to one specific
|
|
type.
|
|
This was not true in the account example earlier.</p>
|
|
<p>Rust's generic-inspired type system can bring it close to these concepts and
|
|
use their power, as shown in this API design.
|
|
But it may also need procedural macros to create bridges for its generics.</p>
|
|
<h2 id="see-also"><a class="header" href="#see-also">See Also</a></h2>
|
|
<ul>
|
|
<li><a href="https://crates.io/crates/lens-rs">lens-rs crate</a> for a pre-built lenses
|
|
implementation, with a cleaner interface than these examples</li>
|
|
<li><a href="https://serde.rs">serde</a> itself, which makes these concepts intuitive for
|
|
end users (i.e. defining the structs) without needing to undestand the
|
|
details</li>
|
|
<li><a href="https://github.com/phaazon/luminance-rs">luminance</a> is a crate for drawing
|
|
computer graphics that uses lens API design, including proceducal macros to
|
|
create full prisms for buffers of different pixel types that remain generic</li>
|
|
<li><a href="https://web.archive.org/web/20221128185849/https://medium.com/zyseme-technology/functional-references-lens-and-other-optics-in-scala-e5f7e2fdafe">An Article about Lenses in
|
|
Scala</a>
|
|
that is very readable even without Scala expertise.</li>
|
|
<li><a href="https://web.archive.org/web/20220701102832/https://arxiv.org/ftp/arxiv/papers/1703/1703.10857.pdf">Paper: Profunctor Optics: Modular Data
|
|
Accessors</a></li>
|
|
</ul>
|
|
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
|
<p><a href="https://web.archive.org/web/20221128190041/https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial">School of Haskell: A Little Lens Starter Tutorial</a></p>
|
|
</div>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
<a rel="prev" href="../functional/generics-type-classes.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next" href="../additional_resources/index.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
<a rel="prev" href="../functional/generics-type-classes.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next" href="../additional_resources/index.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
window.playground_copyable = true;
|
|
</script>
|
|
|
|
|
|
<script src="../elasticlunr.min.js"></script>
|
|
<script src="../mark.min.js"></script>
|
|
<script src="../searcher.js"></script>
|
|
|
|
<script src="../clipboard.min.js"></script>
|
|
<script src="../highlight.js"></script>
|
|
<script src="../book.js"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|