| <!DOCTYPE HTML> |
| <html lang="en" class="light sidebar-visible" dir="ltr"> |
| <head> |
| <!-- Book generated using mdBook --> |
| <meta charset="UTF-8"> |
| <title>Design Contracts - The Embedded Rust Book</title> |
| |
| |
| <!-- Custom HTML head --> |
| |
| <meta name="description" content=""> |
| <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" id="highlight-css" href="../highlight.css"> |
| <link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css"> |
| <link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css"> |
| |
| <!-- Custom theme stylesheets --> |
| |
| |
| <!-- Provide site root and default themes to javascript --> |
| <script> |
| const path_to_root = "../"; |
| const default_light_theme = "light"; |
| const default_dark_theme = "navy"; |
| </script> |
| <!-- Start loading toc.js asap --> |
| <script src="../toc.js"></script> |
| </head> |
| <body> |
| <div id="mdbook-help-container"> |
| <div id="mdbook-help-popup"> |
| <h2 class="mdbook-help-title">Keyboard shortcuts</h2> |
| <div> |
| <p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p> |
| <p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p> |
| <p>Press <kbd>?</kbd> to show this help</p> |
| <p>Press <kbd>Esc</kbd> to hide this help</p> |
| </div> |
| </div> |
| </div> |
| <div id="body-container"> |
| <!-- Work around some values being stored in localStorage wrapped in quotes --> |
| <script> |
| try { |
| let theme = localStorage.getItem('mdbook-theme'); |
| let 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> |
| const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme; |
| let theme; |
| try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } |
| if (theme === null || theme === undefined) { theme = default_theme; } |
| const html = document.documentElement; |
| html.classList.remove('light') |
| html.classList.add(theme); |
| html.classList.add("js"); |
| </script> |
| |
| <input type="checkbox" id="sidebar-toggle-anchor" class="hidden"> |
| |
| <!-- Hide / unhide sidebar before it is displayed --> |
| <script> |
| let sidebar = null; |
| const sidebar_toggle = document.getElementById("sidebar-toggle-anchor"); |
| if (document.body.clientWidth >= 1080) { |
| try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } |
| sidebar = sidebar || 'visible'; |
| } else { |
| sidebar = 'hidden'; |
| } |
| sidebar_toggle.checked = sidebar === 'visible'; |
| html.classList.remove('sidebar-visible'); |
| html.classList.add("sidebar-" + sidebar); |
| </script> |
| |
| <nav id="sidebar" class="sidebar" aria-label="Table of contents"> |
| <!-- populated by js --> |
| <mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox> |
| <noscript> |
| <iframe class="sidebar-iframe-outer" src="../toc.html"></iframe> |
| </noscript> |
| <div id="sidebar-resize-handle" class="sidebar-resize-handle"> |
| <div class="sidebar-resize-indicator"></div> |
| </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"> |
| <div class="left-buttons"> |
| <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar"> |
| <i class="fa fa-bars"></i> |
| </label> |
| <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="default_theme">Auto</button></li> |
| <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 (`/`)" 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">The Embedded Rust Book</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-embedded/book" title="Git repository" aria-label="Git repository"> |
| <i id="git-repository-button" class="fa fa-github"></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="design-contracts"><a class="header" href="#design-contracts">Design Contracts</a></h1> |
| <p>In our last chapter, we wrote an interface that <em>didn't</em> enforce design contracts. Let's take another look at our imaginary GPIO configuration register:</p> |
| <div class="table-wrapper"><table><thead><tr><th style="text-align: right">Name</th><th style="text-align: right">Bit Number(s)</th><th style="text-align: right">Value</th><th style="text-align: right">Meaning</th><th style="text-align: right">Notes</th></tr></thead><tbody> |
| <tr><td style="text-align: right">enable</td><td style="text-align: right">0</td><td style="text-align: right">0</td><td style="text-align: right">disabled</td><td style="text-align: right">Disables the GPIO</td></tr> |
| <tr><td style="text-align: right"></td><td style="text-align: right"></td><td style="text-align: right">1</td><td style="text-align: right">enabled</td><td style="text-align: right">Enables the GPIO</td></tr> |
| <tr><td style="text-align: right">direction</td><td style="text-align: right">1</td><td style="text-align: right">0</td><td style="text-align: right">input</td><td style="text-align: right">Sets the direction to Input</td></tr> |
| <tr><td style="text-align: right"></td><td style="text-align: right"></td><td style="text-align: right">1</td><td style="text-align: right">output</td><td style="text-align: right">Sets the direction to Output</td></tr> |
| <tr><td style="text-align: right">input_mode</td><td style="text-align: right">2..3</td><td style="text-align: right">00</td><td style="text-align: right">hi-z</td><td style="text-align: right">Sets the input as high resistance</td></tr> |
| <tr><td style="text-align: right"></td><td style="text-align: right"></td><td style="text-align: right">01</td><td style="text-align: right">pull-low</td><td style="text-align: right">Input pin is pulled low</td></tr> |
| <tr><td style="text-align: right"></td><td style="text-align: right"></td><td style="text-align: right">10</td><td style="text-align: right">pull-high</td><td style="text-align: right">Input pin is pulled high</td></tr> |
| <tr><td style="text-align: right"></td><td style="text-align: right"></td><td style="text-align: right">11</td><td style="text-align: right">n/a</td><td style="text-align: right">Invalid state. Do not set</td></tr> |
| <tr><td style="text-align: right">output_mode</td><td style="text-align: right">4</td><td style="text-align: right">0</td><td style="text-align: right">set-low</td><td style="text-align: right">Output pin is driven low</td></tr> |
| <tr><td style="text-align: right"></td><td style="text-align: right"></td><td style="text-align: right">1</td><td style="text-align: right">set-high</td><td style="text-align: right">Output pin is driven high</td></tr> |
| <tr><td style="text-align: right">input_status</td><td style="text-align: right">5</td><td style="text-align: right">x</td><td style="text-align: right">in-val</td><td style="text-align: right">0 if input is < 1.5v, 1 if input >= 1.5v</td></tr> |
| </tbody></table> |
| </div> |
| <p>If we instead checked the state before making use of the underlying hardware, enforcing our design contracts at runtime, we might write code that looks like this instead:</p> |
| <pre><code class="language-rust ignore">/// GPIO interface |
| struct GpioConfig { |
| /// GPIO Configuration structure generated by svd2rust |
| periph: GPIO_CONFIG, |
| } |
| |
| impl GpioConfig { |
| pub fn set_enable(&mut self, is_enabled: bool) { |
| self.periph.modify(|_r, w| { |
| w.enable().set_bit(is_enabled) |
| }); |
| } |
| |
| pub fn set_direction(&mut self, is_output: bool) -> Result<(), ()> { |
| if self.periph.read().enable().bit_is_clear() { |
| // Must be enabled to set direction |
| return Err(()); |
| } |
| |
| self.periph.modify(|r, w| { |
| w.direction().set_bit(is_output) |
| }); |
| |
| Ok(()) |
| } |
| |
| pub fn set_input_mode(&mut self, variant: InputMode) -> Result<(), ()> { |
| if self.periph.read().enable().bit_is_clear() { |
| // Must be enabled to set input mode |
| return Err(()); |
| } |
| |
| if self.periph.read().direction().bit_is_set() { |
| // Direction must be input |
| return Err(()); |
| } |
| |
| self.periph.modify(|_r, w| { |
| w.input_mode().variant(variant) |
| }); |
| |
| Ok(()) |
| } |
| |
| pub fn set_output_status(&mut self, is_high: bool) -> Result<(), ()> { |
| if self.periph.read().enable().bit_is_clear() { |
| // Must be enabled to set output status |
| return Err(()); |
| } |
| |
| if self.periph.read().direction().bit_is_clear() { |
| // Direction must be output |
| return Err(()); |
| } |
| |
| self.periph.modify(|_r, w| { |
| w.output_mode.set_bit(is_high) |
| }); |
| |
| Ok(()) |
| } |
| |
| pub fn get_input_status(&self) -> Result<bool, ()> { |
| if self.periph.read().enable().bit_is_clear() { |
| // Must be enabled to get status |
| return Err(()); |
| } |
| |
| if self.periph.read().direction().bit_is_set() { |
| // Direction must be input |
| return Err(()); |
| } |
| |
| Ok(self.periph.read().input_status().bit_is_set()) |
| } |
| }</code></pre> |
| <p>Because we need to enforce the restrictions on the hardware, we end up doing a lot of runtime checking which wastes time and resources, and this code will be much less pleasant for the developer to use.</p> |
| <h2 id="type-states"><a class="header" href="#type-states">Type States</a></h2> |
| <p>But what if instead, we used Rust's type system to enforce the state transition rules? Take this example:</p> |
| <pre><code class="language-rust ignore">/// GPIO interface |
| struct GpioConfig<ENABLED, DIRECTION, MODE> { |
| /// GPIO Configuration structure generated by svd2rust |
| periph: GPIO_CONFIG, |
| enabled: ENABLED, |
| direction: DIRECTION, |
| mode: MODE, |
| } |
| |
| // Type states for MODE in GpioConfig |
| struct Disabled; |
| struct Enabled; |
| struct Output; |
| struct Input; |
| struct PulledLow; |
| struct PulledHigh; |
| struct HighZ; |
| struct DontCare; |
| |
| /// These functions may be used on any GPIO Pin |
| impl<EN, DIR, IN_MODE> GpioConfig<EN, DIR, IN_MODE> { |
| pub fn into_disabled(self) -> GpioConfig<Disabled, DontCare, DontCare> { |
| self.periph.modify(|_r, w| w.enable.disabled()); |
| GpioConfig { |
| periph: self.periph, |
| enabled: Disabled, |
| direction: DontCare, |
| mode: DontCare, |
| } |
| } |
| |
| pub fn into_enabled_input(self) -> GpioConfig<Enabled, Input, HighZ> { |
| self.periph.modify(|_r, w| { |
| w.enable.enabled() |
| .direction.input() |
| .input_mode.high_z() |
| }); |
| GpioConfig { |
| periph: self.periph, |
| enabled: Enabled, |
| direction: Input, |
| mode: HighZ, |
| } |
| } |
| |
| pub fn into_enabled_output(self) -> GpioConfig<Enabled, Output, DontCare> { |
| self.periph.modify(|_r, w| { |
| w.enable.enabled() |
| .direction.output() |
| .input_mode.set_high() |
| }); |
| GpioConfig { |
| periph: self.periph, |
| enabled: Enabled, |
| direction: Output, |
| mode: DontCare, |
| } |
| } |
| } |
| |
| /// This function may be used on an Output Pin |
| impl GpioConfig<Enabled, Output, DontCare> { |
| pub fn set_bit(&mut self, set_high: bool) { |
| self.periph.modify(|_r, w| w.output_mode.set_bit(set_high)); |
| } |
| } |
| |
| /// These methods may be used on any enabled input GPIO |
| impl<IN_MODE> GpioConfig<Enabled, Input, IN_MODE> { |
| pub fn bit_is_set(&self) -> bool { |
| self.periph.read().input_status.bit_is_set() |
| } |
| |
| pub fn into_input_high_z(self) -> GpioConfig<Enabled, Input, HighZ> { |
| self.periph.modify(|_r, w| w.input_mode().high_z()); |
| GpioConfig { |
| periph: self.periph, |
| enabled: Enabled, |
| direction: Input, |
| mode: HighZ, |
| } |
| } |
| |
| pub fn into_input_pull_down(self) -> GpioConfig<Enabled, Input, PulledLow> { |
| self.periph.modify(|_r, w| w.input_mode().pull_low()); |
| GpioConfig { |
| periph: self.periph, |
| enabled: Enabled, |
| direction: Input, |
| mode: PulledLow, |
| } |
| } |
| |
| pub fn into_input_pull_up(self) -> GpioConfig<Enabled, Input, PulledHigh> { |
| self.periph.modify(|_r, w| w.input_mode().pull_high()); |
| GpioConfig { |
| periph: self.periph, |
| enabled: Enabled, |
| direction: Input, |
| mode: PulledHigh, |
| } |
| } |
| }</code></pre> |
| <p>Now let's see what the code using this would look like:</p> |
| <pre><code class="language-rust ignore">/* |
| * Example 1: Unconfigured to High-Z input |
| */ |
| let pin: GpioConfig<Disabled, _, _> = get_gpio(); |
| |
| // Can't do this, pin isn't enabled! |
| // pin.into_input_pull_down(); |
| |
| // Now turn the pin from unconfigured to a high-z input |
| let input_pin = pin.into_enabled_input(); |
| |
| // Read from the pin |
| let pin_state = input_pin.bit_is_set(); |
| |
| // Can't do this, input pins don't have this interface! |
| // input_pin.set_bit(true); |
| |
| /* |
| * Example 2: High-Z input to Pulled Low input |
| */ |
| let pulled_low = input_pin.into_input_pull_down(); |
| let pin_state = pulled_low.bit_is_set(); |
| |
| /* |
| * Example 3: Pulled Low input to Output, set high |
| */ |
| let output_pin = pulled_low.into_enabled_output(); |
| output_pin.set_bit(true); |
| |
| // Can't do this, output pins don't have this interface! |
| // output_pin.into_input_pull_down();</code></pre> |
| <p>This is definitely a convenient way to store the state of the pin, but why do it this way? Why is this better than storing the state as an <code>enum</code> inside of our <code>GpioConfig</code> structure?</p> |
| <h2 id="compile-time-functional-safety"><a class="header" href="#compile-time-functional-safety">Compile Time Functional Safety</a></h2> |
| <p>Because we are enforcing our design constraints entirely at compile time, this incurs no runtime cost. It is impossible to set an output mode when you have a pin in an input mode. Instead, you must walk through the states by converting it to an output pin, and then setting the output mode. Because of this, there is no runtime penalty due to checking the current state before executing a function.</p> |
| <p>Also, because these states are enforced by the type system, there is no longer room for errors by consumers of this interface. If they try to perform an illegal state transition, the code will not compile!</p> |
| |
| </main> |
| |
| <nav class="nav-wrapper" aria-label="Page navigation"> |
| <!-- Mobile navigation buttons --> |
| <a rel="prev" href="../static-guarantees/state-machines.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 prefetch" href="../static-guarantees/zero-cost-abstractions.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="../static-guarantees/state-machines.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 prefetch" href="../static-guarantees/zero-cost-abstractions.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> |