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/idioms/priv-extend.html

312 lines
22 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js rust">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Privacy For Extensibility - 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>
<!-- 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 = '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 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" class="active"><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
</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/./idioms/priv-extend.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="non_exhaustive-and-private-fields-for-extensibility"><a class="header" href="#non_exhaustive-and-private-fields-for-extensibility"><code>#[non_exhaustive]</code> and private fields for extensibility</a></h1>
<h2 id="description"><a class="header" href="#description">Description</a></h2>
<p>A small set of scenarios exist where a library author may want to add public
fields to a public struct or new variants to an enum without breaking backwards
compatibility.</p>
<p>Rust offers two solutions to this problem:</p>
<ul>
<li>
<p>Use <code>#[non_exhaustive]</code> on <code>struct</code>s, <code>enum</code>s, and <code>enum</code> variants.
For extensive documentation on all the places where <code>#[non_exhaustive]</code> can be
used, see <a href="https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute">the docs</a>.</p>
</li>
<li>
<p>You may add a private field to a struct to prevent it from being directly
instantiated or matched against (see Alternative)</p>
</li>
</ul>
<h2 id="example"><a class="header" href="#example">Example</a></h2>
<pre><pre class="playground"><code class="language-rust edition2018"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>mod a {
// Public struct.
#[non_exhaustive]
pub struct S {
pub foo: i32,
}
#[non_exhaustive]
pub enum AdmitMoreVariants {
VariantA,
VariantB,
#[non_exhaustive]
VariantC { a: String }
}
}
fn print_matched_variants(s: a::S) {
// Because S is `#[non_exhaustive]`, it cannot be named here and
// we must use `..` in the pattern.
let a::S { foo: _, ..} = s;
let some_enum = a::AdmitMoreVariants::VariantA;
match some_enum {
a::AdmitMoreVariants::VariantA =&gt; println!(&quot;it's an A&quot;),
a::AdmitMoreVariants::VariantB =&gt; println!(&quot;it's a b&quot;),
// .. required because this variant is non-exhaustive as well
a::AdmitMoreVariants::VariantC { a, .. } =&gt; println!(&quot;it's a c&quot;),
// The wildcard match is required because more variants may be
// added in the future
_ =&gt; println!(&quot;it's a new variant&quot;)
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="alternative-private-fields-for-structs"><a class="header" href="#alternative-private-fields-for-structs">Alternative: <code>Private fields</code> for structs</a></h2>
<p><code>#[non_exhaustive]</code> only works across crate boundaries.
Within a crate, the private field method may be used.</p>
<p>Adding a field to a struct is a mostly backwards compatible change.
However, if a client uses a pattern to deconstruct a struct instance, they
might name all the fields in the struct and adding a new one would break that
pattern.
The client could name some fields and use <code>..</code> in the pattern, in which case adding
another field is backwards compatible.
Making at least one of the struct's fields private forces clients to use the latter
form of patterns, ensuring that the struct is future-proof.</p>
<p>The downside of this approach is that you might need to add an otherwise unneeded
field to the struct.
You can use the <code>()</code> type so that there is no runtime overhead and prepend <code>_</code> to
the field name to avoid the unused field warning.</p>
<pre><pre class="playground"><code class="language-rust edition2018"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct S {
pub a: i32,
// Because `b` is private, you cannot match on `S` without using `..` and `S`
// cannot be directly instantiated or matched against
_b: ()
}
<span class="boring">}</span></code></pre></pre>
<h2 id="discussion"><a class="header" href="#discussion">Discussion</a></h2>
<p>On <code>struct</code>s, <code>#[non_exhaustive]</code> allows adding additional fields in a backwards
compatible way.
It will also prevent clients from using the struct constructor, even if all the
fields are public.
This may be helpful, but it's worth considering if you <em>want</em> an additional field
to be found by clients as a compiler error rather than something that may be silently
undiscovered.</p>
<p><code>#[non_exhaustive]</code> can be applied to enum variants as well.
A <code>#[non_exhaustive]</code> variant behaves in the same way as a <code>#[non_exhaustive]</code> struct.</p>
<p>Use this deliberately and with caution: incrementing the major version when adding
fields or variants is often a better option.
<code>#[non_exhaustive]</code> may be appropriate in scenarios where you're modeling an external
resource that may change out-of-sync with your library, but is not a general purpose
tool.</p>
<h3 id="disadvantages"><a class="header" href="#disadvantages">Disadvantages</a></h3>
<p><code>#[non_exhaustive]</code> can make your code much less ergonomic to use, especially when
forced to handle unknown enum variants.
It should only be used when these sorts of evolutions are required <strong>without</strong>
incrementing the major version.</p>
<p>When <code>#[non_exhaustive]</code> is applied to <code>enum</code>s, it forces clients to handle a
wildcard variant.
If there is no sensible action to take in this case, this may lead to awkward
code and code paths that are only executed in extremely rare circumstances.
If a client decides to <code>panic!()</code> in this scenario, it may have been better to
expose this error at compile time.
In fact, <code>#[non_exhaustive]</code> forces clients to handle the &quot;Something else&quot; case;
there is rarely a sensible action to take in this scenario.</p>
<h2 id="see-also"><a class="header" href="#see-also">See also</a></h2>
<ul>
<li><a href="https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md">RFC introducing #[non_exhaustive] attribute for enums and structs</a></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../idioms/pass-var-to-closure.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="../idioms/rustdoc-init.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="../idioms/pass-var-to-closure.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="../idioms/rustdoc-init.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 -->
</body>
</html>