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.
patterns/functional/lenses.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">{ &quot;name&quot;: &quot;Jane Doe&quot;,
&quot;dob&quot;: &quot;2002-02-24&quot;,
[...]
&quot;customer_id&quot;: 1048576332,
}
</code></pre>
<p>Another one contains the account information:</p>
<pre><code class="language-json">{ &quot;customer_id&quot;: 1048576332,
&quot;accounts&quot;: [
{ &quot;account_id&quot;: 2121,
&quot;account_type: &quot;savings&quot;,
&quot;joint_customer_ids&quot;: [],
[...]
},
{ &quot;account_id&quot;: 2122,
&quot;account_type: &quot;checking&quot;,
&quot;joint_customer_ids&quot;: [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(&amp;self) -&gt; u64;
}
pub struct CreditRecord {
customer_id: u64,
name: String,
dob: String,
// other fields omitted
}
impl CustomerId for CreditRecord {
fn get_customer_id(&amp;self) -&gt; u64 {
self.customer_id
}
}
pub struct AccountRecord {
customer_id: u64,
accounts: Vec&lt;Account&gt;,
}
impl CustomerId for AccountRecord {
fn get_customer_id(&amp;self) -&gt; u64 {
self.customer_id
}
}
// static polymorphism: only one type, but each function call can choose it
fn unique_ids_set&lt;R: CustomerId&gt;(records: &amp;[R]) -&gt; HashSet&lt;u64&gt; {
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&lt;I&gt;(iterator: I) -&gt; HashSet&lt;u64&gt;
where I: Iterator&lt;Item=Box&lt;dyn CustomerId&gt;&gt;
{
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&lt;Account&gt;,
}
fn unique_ids_lens&lt;T&gt;(iter: impl Iterator&lt;Item = T&gt;) -&gt; HashSet&lt;u64&gt;
where
T: LensRef&lt;Optics![customer_id], u64&gt;, // 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 &quot;currying&quot;.
That is, they &quot;partially construct&quot; 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 &quot;Optics&quot;</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 &quot;optics&quot;.
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 &quot;lens-like&quot; 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&lt;'de&gt;: Sized {
type Error: Error;
fn deserialize_any&lt;V&gt;(self, visitor: V) -&gt; Result&lt;V::Value, Self::Error&gt;
where
V: Visitor&lt;'de&gt;;
fn deserialize_bool&lt;V&gt;(self, visitor: V) -&gt; Result&lt;V::Value, Self::Error&gt;
where
V: Visitor&lt;'de&gt;;
// 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&lt;'de&gt;: Sized {
type Value;
fn visit_bool&lt;E&gt;(self, v: bool) -&gt; Result&lt;Self::Value, E&gt;
where
E: Error;
fn visit_u64&lt;E&gt;(self, v: u64) -&gt; Result&lt;Self::Value, E&gt;
where
E: Error;
fn visit_str&lt;E&gt;(self, v: &amp;str) -&gt; Result&lt;Self::Value, E&gt;
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 &quot;lenses&quot; work across
a set of other generic types for &quot;observation&quot;.
It is a <em>prism</em>.</p>
<p>For example, consider the identity record from earlier but simplified:</p>
<pre><code class="language-json">{ &quot;name&quot;: &quot;Jane Doe&quot;,
&quot;customer_id&quot;: 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 &quot;name&quot;.</li>
<li>Visit a string value, which will go into the <code>name</code> field.</li>
<li>Visit a string key called &quot;customer_id&quot;.</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 &quot;observation&quot; 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&lt;'de&gt;: Sized {
fn deserialize&lt;D&gt;(deserializer: D) -&gt; Result&lt;Self, D::Error&gt;
where
D: Deserializer&lt;'de&gt;;
}</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
&quot;refracted&quot; 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 &quot;refracts&quot; 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 &quot;top layer&quot; of
the API, and file formats only need to implement the &quot;bottom layer&quot;.
Each piece can then &quot;just work&quot; 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>