// Populate the sidebar // // This is a script, and not included directly in the page, to control the total size of the book. // The TOC contains an entry for each page, so if each page includes a copy of the TOC, // the total size of the page becomes O(n**2). class MDBookSidebarScrollbox extends HTMLElement { constructor() { super(); } connectedCallback() { this.innerHTML = '
  1. Rust 程序设计语言
  2. 前言
  3. 简介
  4. 入门指南
    1. 安装
    2. Hello, World!
    3. Hello, Cargo!
  5. 编写一个猜数字游戏
  6. 常见编程概念
    1. 变量与可变性
    2. 数据类型
    3. 函数
    4. 注释
    5. 控制流
  7. 认识所有权
    1. 什么是所有权?
    2. 引用与借用
    3. Slice 类型
  8. 使用结构体组织相关联的数据
    1. 结构体的定义和实例化
    2. 结构体示例程序
    3. 方法语法
  9. 枚举和模式匹配
    1. 枚举的定义
    2. match 控制流结构
    3. if let 和 let else 简洁控制流
  10. 使用包、Crate 和模块管理不断增长的项目
    1. 包和 Crate
    2. 定义模块来控制作用域与私有性
    3. 引用模块树中项的路径
    4. 使用 use 关键字将路径引入作用域
    5. 将模块拆分成多个文件
  11. 常见集合
    1. 使用 Vector 储存列表
    2. 使用字符串储存 UTF-8 编码的文本
    3. 使用 Hash Map 储存键值对
  12. 错误处理
    1. 用 panic! 处理不可恢复的错误
    2. 用 Result 处理可恢复的错误
    3. 要不要 panic!
  13. 泛型、Trait 和生命周期
    1. 泛型数据类型
    2. Trait:定义共同行为
    3. 生命周期确保引用有效
  14. 编写自动化测试
    1. 如何编写测试
    2. 控制测试如何运行
    3. 测试的组织结构
  15. 一个 I/O 项目:构建命令行程序
    1. 接受命令行参数
    2. 读取文件
    3. 重构以改进模块化与错误处理
    4. 采用测试驱动开发完善库的功能
    5. 处理环境变量
    6. 将错误信息输出到标准错误而不是标准输出
  16. 函数式语言特性:迭代器与闭包
    1. 闭包:可以捕获其环境的匿名函数
    2. 使用迭代器处理元素序列
    3. 改进之前的 I/O 项目
    4. 性能比较:循环对迭代器
  17. 更多关于 Cargo 和 Crates.io 的内容
    1. 采用发布配置自定义构建
    2. 将 crate 发布到 Crates.io
    3. Cargo 工作空间
    4. 使用 cargo install 安装二进制文件
    5. Cargo 自定义扩展命令
  18. 智能指针
    1. 使用 Box<T> 指向堆上数据
    2. 使用 Deref Trait 将智能指针当作常规引用处理
    3. 使用 Drop Trait 运行清理代码
    4. Rc<T> 引用计数智能指针
    5. RefCell<T> 与内部可变性模式
    6. 引用循环会导致内存泄漏
  19. 无畏并发
    1. 使用线程同时地运行代码
    2. 使用消息传递在线程间通信
    3. 共享状态并发
    4. 使用 Sync 与 Send Traits 的可扩展并发
  20. Async 和 await
    1. Futures 和 async 语法
    2. 并发与 async
    3. 使用任意数量的 futures
    4. 流(Streams)
    5. 深入理解 async 相关的 traits
    6. future、任务和线程
  21. 面向对象编程特性
    1. 面向对象语言的特征
    2. 顾及不同类型值的 trait 对象
    3. 面向对象设计模式的实现
  22. 模式与模式匹配
    1. 所有可能会用到模式的位置
    2. Refutability(可反驳性): 模式是否会匹配失效
    3. 模式语法
  23. 高级特性
    1. 不安全 Rust
    2. 高级 trait
    3. 高级类型
    4. 高级函数与闭包
  24. 最后的项目:构建多线程 web server
    1. 建立单线程 web server
    2. 将单线程 server 变为多线程 server
    3. 优雅停机与清理
  25. 附录
    1. A - 关键字
    2. B - 运算符与符号
    3. C - 可派生的 trait
    4. D - 实用开发工具
    5. E - 版本
    6. F - 本书译本
    7. G - Rust 是如何开发的与 “Nightly Rust”
'; // Set the current, active page, and reveal it if it's hidden let current_page = document.location.href.toString().split('#')[0].split('?')[0]; if (current_page.endsWith('/')) { current_page += 'index.html'; } const links = Array.prototype.slice.call(this.querySelectorAll('a')); const l = links.length; for (let i = 0; i < l; ++i) { const link = links[i]; const href = link.getAttribute('href'); if (href && !href.startsWith('#') && !/^(?:[a-z+]+:)?\/\//.test(href)) { link.href = path_to_root + href; } // The 'index' page is supposed to alias the first chapter in the book. if (link.href === current_page || i === 0 && path_to_root === '' && current_page.endsWith('/index.html')) { link.classList.add('active'); let parent = link.parentElement; while (parent) { if (parent.tagName === 'LI' && parent.classList.contains('chapter-item')) { parent.classList.add('expanded'); } parent = parent.parentElement; } } } // Track and set sidebar scroll position this.addEventListener('click', e => { if (e.target.tagName === 'A') { const clientRect = e.target.getBoundingClientRect(); const sidebarRect = this.getBoundingClientRect(); sessionStorage.setItem('sidebar-scroll-offset', clientRect.top - sidebarRect.top); } }, { passive: true }); const sidebarScrollOffset = sessionStorage.getItem('sidebar-scroll-offset'); sessionStorage.removeItem('sidebar-scroll-offset'); if (sidebarScrollOffset !== null) { // preserve sidebar scroll position when navigating via links within sidebar const activeSection = this.querySelector('.active'); if (activeSection) { const clientRect = activeSection.getBoundingClientRect(); const sidebarRect = this.getBoundingClientRect(); const currentOffset = clientRect.top - sidebarRect.top; this.scrollTop += currentOffset - parseFloat(sidebarScrollOffset); } } else { // scroll sidebar to current active section when navigating via // 'next/previous chapter' buttons const activeSection = document.querySelector('#mdbook-sidebar .active'); if (activeSection) { activeSection.scrollIntoView({ block: 'center' }); } } // Toggle buttons const sidebarAnchorToggles = document.querySelectorAll('.chapter-fold-toggle'); function toggleSection(ev) { ev.currentTarget.parentElement.parentElement.classList.toggle('expanded'); } Array.from(sidebarAnchorToggles).forEach(el => { el.addEventListener('click', toggleSection); }); } } window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox); // --------------------------------------------------------------------------- // Support for dynamically adding headers to the sidebar. (function() { // This is used to detect which direction the page has scrolled since the // last scroll event. let lastKnownScrollPosition = 0; // This is the threshold in px from the top of the screen where it will // consider a header the "current" header when scrolling down. const defaultDownThreshold = 150; // Same as defaultDownThreshold, except when scrolling up. const defaultUpThreshold = 300; // The threshold is a virtual horizontal line on the screen where it // considers the "current" header to be above the line. The threshold is // modified dynamically to handle headers that are near the bottom of the // screen, and to slightly offset the behavior when scrolling up vs down. let threshold = defaultDownThreshold; // This is used to disable updates while scrolling. This is needed when // clicking the header in the sidebar, which triggers a scroll event. It // is somewhat finicky to detect when the scroll has finished, so this // uses a relatively dumb system of disabling scroll updates for a short // time after the click. let disableScroll = false; // Array of header elements on the page. let headers; // Array of li elements that are initially collapsed headers in the sidebar. // I'm not sure why eslint seems to have a false positive here. // eslint-disable-next-line prefer-const let headerToggles = []; // This is a debugging tool for the threshold which you can enable in the console. let thresholdDebug = false; // Updates the threshold based on the scroll position. function updateThreshold() { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const windowHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight; // The number of pixels below the viewport, at most documentHeight. // This is used to push the threshold down to the bottom of the page // as the user scrolls towards the bottom. const pixelsBelow = Math.max(0, documentHeight - (scrollTop + windowHeight)); // The number of pixels above the viewport, at least defaultDownThreshold. // Similar to pixelsBelow, this is used to push the threshold back towards // the top when reaching the top of the page. const pixelsAbove = Math.max(0, defaultDownThreshold - scrollTop); // How much the threshold should be offset once it gets close to the // bottom of the page. const bottomAdd = Math.max(0, windowHeight - pixelsBelow - defaultDownThreshold); let adjustedBottomAdd = bottomAdd; // Adjusts bottomAdd for a small document. The calculation above // assumes the document is at least twice the windowheight in size. If // it is less than that, then bottomAdd needs to be shrunk // proportional to the difference in size. if (documentHeight < windowHeight * 2) { const maxPixelsBelow = documentHeight - windowHeight; const t = 1 - pixelsBelow / Math.max(1, maxPixelsBelow); const clamp = Math.max(0, Math.min(1, t)); adjustedBottomAdd *= clamp; } let scrollingDown = true; if (scrollTop < lastKnownScrollPosition) { scrollingDown = false; } if (scrollingDown) { // When scrolling down, move the threshold up towards the default // downwards threshold position. If near the bottom of the page, // adjustedBottomAdd will offset the threshold towards the bottom // of the page. const amountScrolledDown = scrollTop - lastKnownScrollPosition; const adjustedDefault = defaultDownThreshold + adjustedBottomAdd; threshold = Math.max(adjustedDefault, threshold - amountScrolledDown); } else { // When scrolling up, move the threshold down towards the default // upwards threshold position. If near the bottom of the page, // quickly transition the threshold back up where it normally // belongs. const amountScrolledUp = lastKnownScrollPosition - scrollTop; const adjustedDefault = defaultUpThreshold - pixelsAbove + Math.max(0, adjustedBottomAdd - defaultDownThreshold); threshold = Math.min(adjustedDefault, threshold + amountScrolledUp); } if (documentHeight <= windowHeight) { threshold = 0; } if (thresholdDebug) { const id = 'mdbook-threshold-debug-data'; let data = document.getElementById(id); if (data === null) { data = document.createElement('div'); data.id = id; data.style.cssText = ` position: fixed; top: 50px; right: 10px; background-color: 0xeeeeee; z-index: 9999; pointer-events: none; `; document.body.appendChild(data); } data.innerHTML = `
documentHeight${documentHeight.toFixed(1)}
windowHeight${windowHeight.toFixed(1)}
scrollTop${scrollTop.toFixed(1)}
pixelsAbove${pixelsAbove.toFixed(1)}
pixelsBelow${pixelsBelow.toFixed(1)}
bottomAdd${bottomAdd.toFixed(1)}
adjustedBottomAdd${adjustedBottomAdd.toFixed(1)}
scrollingDown${scrollingDown}
threshold${threshold.toFixed(1)}
`; drawDebugLine(); } lastKnownScrollPosition = scrollTop; } function drawDebugLine() { if (!document.body) { return; } const id = 'mdbook-threshold-debug-line'; const existingLine = document.getElementById(id); if (existingLine) { existingLine.remove(); } const line = document.createElement('div'); line.id = id; line.style.cssText = ` position: fixed; top: ${threshold}px; left: 0; width: 100vw; height: 2px; background-color: red; z-index: 9999; pointer-events: none; `; document.body.appendChild(line); } function mdbookEnableThresholdDebug() { thresholdDebug = true; updateThreshold(); drawDebugLine(); } window.mdbookEnableThresholdDebug = mdbookEnableThresholdDebug; // Updates which headers in the sidebar should be expanded. If the current // header is inside a collapsed group, then it, and all its parents should // be expanded. function updateHeaderExpanded(currentA) { // Add expanded to all header-item li ancestors. let current = currentA.parentElement; while (current) { if (current.tagName === 'LI' && current.classList.contains('header-item')) { current.classList.add('expanded'); } current = current.parentElement; } } // Updates which header is marked as the "current" header in the sidebar. // This is done with a virtual Y threshold, where headers at or below // that line will be considered the current one. function updateCurrentHeader() { if (!headers || !headers.length) { return; } // Reset the classes, which will be rebuilt below. const els = document.getElementsByClassName('current-header'); for (const el of els) { el.classList.remove('current-header'); } for (const toggle of headerToggles) { toggle.classList.remove('expanded'); } // Find the last header that is above the threshold. let lastHeader = null; for (const header of headers) { const rect = header.getBoundingClientRect(); if (rect.top <= threshold) { lastHeader = header; } else { break; } } if (lastHeader === null) { lastHeader = headers[0]; const rect = lastHeader.getBoundingClientRect(); const windowHeight = window.innerHeight; if (rect.top >= windowHeight) { return; } } // Get the anchor in the summary. const href = '#' + lastHeader.id; const a = [...document.querySelectorAll('.header-in-summary')] .find(element => element.getAttribute('href') === href); if (!a) { return; } a.classList.add('current-header'); updateHeaderExpanded(a); } // Updates which header is "current" based on the threshold line. function reloadCurrentHeader() { if (disableScroll) { return; } updateThreshold(); updateCurrentHeader(); } // When clicking on a header in the sidebar, this adjusts the threshold so // that it is located next to the header. This is so that header becomes // "current". function headerThresholdClick(event) { // See disableScroll description why this is done. disableScroll = true; setTimeout(() => { disableScroll = false; }, 100); // requestAnimationFrame is used to delay the update of the "current" // header until after the scroll is done, and the header is in the new // position. requestAnimationFrame(() => { requestAnimationFrame(() => { // Closest is needed because if it has child elements like . const a = event.target.closest('a'); const href = a.getAttribute('href'); const targetId = href.substring(1); const targetElement = document.getElementById(targetId); if (targetElement) { threshold = targetElement.getBoundingClientRect().bottom; updateCurrentHeader(); } }); }); } // Takes the nodes from the given head and copies them over to the // destination, along with some filtering. function filterHeader(source, dest) { const clone = source.cloneNode(true); clone.querySelectorAll('mark').forEach(mark => { mark.replaceWith(...mark.childNodes); }); dest.append(...clone.childNodes); } // Scans page for headers and adds them to the sidebar. document.addEventListener('DOMContentLoaded', function() { const activeSection = document.querySelector('#mdbook-sidebar .active'); if (activeSection === null) { return; } const main = document.getElementsByTagName('main')[0]; headers = Array.from(main.querySelectorAll('h2, h3, h4, h5, h6')) .filter(h => h.id !== '' && h.children.length && h.children[0].tagName === 'A'); if (headers.length === 0) { return; } // Build a tree of headers in the sidebar. const stack = []; const firstLevel = parseInt(headers[0].tagName.charAt(1)); for (let i = 1; i < firstLevel; i++) { const ol = document.createElement('ol'); ol.classList.add('section'); if (stack.length > 0) { stack[stack.length - 1].ol.appendChild(ol); } stack.push({level: i + 1, ol: ol}); } // The level where it will start folding deeply nested headers. const foldLevel = 3; for (let i = 0; i < headers.length; i++) { const header = headers[i]; const level = parseInt(header.tagName.charAt(1)); const currentLevel = stack[stack.length - 1].level; if (level > currentLevel) { // Begin nesting to this level. for (let nextLevel = currentLevel + 1; nextLevel <= level; nextLevel++) { const ol = document.createElement('ol'); ol.classList.add('section'); const last = stack[stack.length - 1]; const lastChild = last.ol.lastChild; // Handle the case where jumping more than one nesting // level, which doesn't have a list item to place this new // list inside of. if (lastChild) { lastChild.appendChild(ol); } else { last.ol.appendChild(ol); } stack.push({level: nextLevel, ol: ol}); } } else if (level < currentLevel) { while (stack.length > 1 && stack[stack.length - 1].level > level) { stack.pop(); } } const li = document.createElement('li'); li.classList.add('header-item'); li.classList.add('expanded'); if (level < foldLevel) { li.classList.add('expanded'); } const span = document.createElement('span'); span.classList.add('chapter-link-wrapper'); const a = document.createElement('a'); span.appendChild(a); a.href = '#' + header.id; a.classList.add('header-in-summary'); filterHeader(header.children[0], a); a.addEventListener('click', headerThresholdClick); const nextHeader = headers[i + 1]; if (nextHeader !== undefined) { const nextLevel = parseInt(nextHeader.tagName.charAt(1)); if (nextLevel > level && level >= foldLevel) { const toggle = document.createElement('a'); toggle.classList.add('chapter-fold-toggle'); toggle.classList.add('header-toggle'); toggle.addEventListener('click', () => { li.classList.toggle('expanded'); }); const toggleDiv = document.createElement('div'); toggleDiv.textContent = '❱'; toggle.appendChild(toggleDiv); span.appendChild(toggle); headerToggles.push(li); } } li.appendChild(span); const currentParent = stack[stack.length - 1]; currentParent.ol.appendChild(li); } const onThisPage = document.createElement('div'); onThisPage.classList.add('on-this-page'); onThisPage.append(stack[0].ol); const activeItemSpan = activeSection.parentElement; activeItemSpan.after(onThisPage); }); document.addEventListener('DOMContentLoaded', reloadCurrentHeader); document.addEventListener('scroll', reloadCurrentHeader, { passive: true }); })();