You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/ch10-01-syntax.html

513 lines
49 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>泛型数据类型 - Rust 程序设计语言 简体中文版</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" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="ferris.css">
<link rel="stylesheet" href="theme/2018-edition.css">
<link rel="stylesheet" href="theme/semantic-notes.css">
<link rel="stylesheet" href="theme/listing.css">
</head>
<body class="sidebar-visible no-js">
<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" : "light";
</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('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var 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';
body.classList.remove('sidebar-visible');
body.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 affix "><a href="title-page.html">Rust 程序设计语言</a></li><li class="chapter-item expanded affix "><a href="foreword.html">前言</a></li><li class="chapter-item expanded affix "><a href="ch00-00-introduction.html">简介</a></li><li class="chapter-item expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> 入门指南</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> 安装</a></li><li class="chapter-item expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="chapter-item expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> 写个猜数字游戏</a></li><li class="chapter-item expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> 常见编程概念</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> 变量与可变性</a></li><li class="chapter-item expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> 数据类型</a></li><li class="chapter-item expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> 注释</a></li><li class="chapter-item expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> 控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> 认识所有权</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> 什么是所有权?</a></li><li class="chapter-item expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> 引用与借用</a></li><li class="chapter-item expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> Slice 类型</a></li></ol></li><li class="chapter-item expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> 使用结构体组织相关联的数据</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> 结构体的定义和实例化</a></li><li class="chapter-item expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> 结构体示例程序</a></li><li class="chapter-item expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> 方法语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> 枚举和模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> 枚举的定义</a></li><li class="chapter-item expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> match 控制流结构</a></li><li class="chapter-item expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> if let 简洁控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> 使用包、Crate 和模块管理不断增长的项目</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> 包和 Crate</a></li><li class="chapter-item expanded "><a href="ch07-02-defining-modules-to-control-scope-and-privacy.html"><strong aria-hidden="true">7.2.</strong> 定义模块来控制作用域与私有性</a></li><li class="chapter-item expanded "><a href="ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html"><strong aria-hidden="true">7.3.</strong> 引用模块项目的路径</a></li><li class="chapter-item expanded "><a href="ch07-04-bringing-paths-into-scope-with-the-use-keyword.html"><strong aria-hidden="true">7.4.</strong> 使用 use 关键字将路径引入作用域</a></li><li class="chapter-item expanded "><a href="ch07-05-separating-modules-into-different-files.html"><strong aria-hidden="true">7.5.</strong> 将模块拆分成多个文件</a></li></ol></li><li class="chapter-item expanded "><a href="ch08-00-common-collections.html"><strong aria-hidden="true">8.</strong> 常见集合</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch08-01-vectors.html"><strong aria-hidden="true">8.1.</strong> 使用 Vector 储存列表</a></li><li class="chapter-item expanded "><a href="ch08-02-strings.html"><strong aria-hidden="true">8.2.</strong> 使用字符串储存 UTF-8 编码的文本</a></li><li class="chapter-item expanded "><a href="ch08-03-hash-maps.html"><strong aria-hidden="true">8.3.</strong> 使用 Hash Map 储存键值对</a></li></ol></li><li class="chapter-item expanded "><a href="ch09-00-error-handling.html"><strong aria-hidden="true">9.</strong> 错误处理</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong aria-hidden="true">9.1.</strong> 用 panic! 处理不可恢复的错误</a></li><li class="chapter-item expanded "><a href="ch09-02-recoverable-errors-with-result.html"><strong aria-hidden="true">9.2.</strong> 用 Result 处理可恢复的错误</a></li><li class="chapter-item expanded "><a href="ch09-03-to-panic-or-not-to-panic.html"><strong aria-hidden="true">9.3.</strong> 要不要 panic!</a></li></ol></li><li class="chapter-item expanded "><a href="ch10-00-generics.html"><strong aria-hidden="true">10.</strong> 泛型、Trait 和生命周期</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch10-01-syntax.html" class="active"><strong aria-hidden="true">10.1.</strong> 泛型数据类型</a></li><li class="chapter-item expanded "><a href="ch10-02-traits.html"><strong aria-hidden="true">10.2.</strong> Trait定义共同行为</a></li><li class="chapter-item expanded "><a href="ch10-03-lifetime-syntax.html"><strong aria-hidden="true">10.3.</strong> 生命周期确保引用有效</a></li></ol></li><li class="chapter-item expanded "><a href="ch11-00-testing.html"><strong aria-hidden="true">11.</strong> 编写自动化测试</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch11-01-writing-tests.html"><strong aria-hidden="true">11.1.</strong> 如何编写测试</a></li><li class="chapter-item expanded "><a href="ch11-02-running-tests.html"><strong aria-hidden="true">11.2.</strong> 控制测试如何运行</a></li><li class="chapter-item expanded "><a href="ch11-03-test-organization.html"><strong aria-hidden="true">11.3.</strong> 测试的组织结构</a></li></ol></li><li class="chapter-item expanded "><a href="ch12-00-an-io-project.html"><strong aria-hidden="true">12.</strong> 一个 I/O 项目:构建命令行程序</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch12-01-accepting-command-line-arguments.html"><strong aria-hidden="true">12.1.</strong> 接受命令行参数</a></li><li class="chapter-item expanded "><a href="ch12-02-reading-a-file.html"><strong aria-hidden="true">12.2.</strong> 读取文件</a></li><li class="chapter-item expanded "><a href="ch12-03-improving-error-handling-and-modularity.html"><strong aria-hidden="true">12.3.</strong> 重构以改进模块化与错误处理</a></li><li class="chapter-item expanded "><a href="ch12-04-testing-the-librarys-functionality.html"><strong aria-hidden="true">12.4.</strong> 采用测试驱动开发完善库的功能</a></li><li class="chapter-item expanded "><a href="ch12-05-working-with-environment-variables.html"><strong aria-hidden="true">12.5.</strong> 处理环境变量</a></li><li class="chapter-item expanded "><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong aria-hidden="true">12.6.</strong> 将错误信息输出到标准错误而不是标准输出</a></li></ol></li><li class="chapter-item expanded "><a href="ch13-00-functional-features.html"><strong aria-hidden="true">13.</strong> Rust 中的函数式语言功能:迭代器与闭包</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch13-01-closures.html"><strong aria-hidden="true">13.1.</strong> 闭包:可以捕获其环境的匿名函数</a></li><li class="chapter-item expanded "><a href="ch13-02-iterators.html"><strong aria-hidden="true">13.2.</strong> 使用迭代器处理元素序列</a></li><li class="chapter-item expanded "><a href="ch13-03-improving-our-io-project.html"><strong aria-hidden="true">13.3.</strong> 改进之前的 I/O 项目</a></li><li class="chapter-item expanded "><a href="ch13-04-performance.html"><strong aria-hidden="true">13.4.</strong> 性能比较:循环对迭代器</a></li></ol></li><li class="chapter-item expanded "><a href="ch14-00-more-about-cargo.html"><strong aria-hidden="true">14.</strong> 更多关于 Cargo 和 Crates.io 的内容</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch14-01-release-profiles.html"><strong aria-hidden="true">14.1.</strong> 采用发布配置自定义构建</a></li><li class="chapter-item expanded "><a href="ch14-02-publishing-to-crates-io.html"><strong aria-hidden="true">14.2.</strong> 将 crate 发布到 Crates.io</a></li><li class="chapter-item expanded "><a href="ch14-03-cargo-workspaces.html"><strong aria-hidden="true">14.3.</strong> Cargo 工作空间</a></li><li class="chapter-item expanded "><a href="ch14-04-installing-binaries.html"><strong aria-hidden="true">14.4.</strong> 使用 cargo install 安装二进制文件</a></li><li class="chapter-item expanded "><a href="ch14-05-extending-cargo.html"><strong aria-hidden="true">14.5.</strong> Cargo 自定义扩展命令</a></li></ol></li><li class="chapter-item expanded "><a href="ch15-00-smart-pointers.html"><strong aria-hidden="true">15.</strong> 智能指针</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch15-01-box.html"><strong aria-hidden="true">15.1.</strong> 使用 Box&lt;T&gt; 指向堆上数据</a></li><li class="chapter-item expanded "><a href="ch15-02-deref.html"><strong aria-hidden="true">15.2.</strong> 使用 Deref Trait 将智能指针当作常规引用处理</a></li><li class="chapter-item expanded "><a href="ch15-03-drop.html"><strong aria-hidden="true">15.3.</strong> 使用 Drop Trait 运行清理代码</a></li><li class="chapter-item expanded "><a href="ch15-04-rc.html"><strong aria-hidden="true">15.4.</strong> Rc&lt;T&gt; 引用计数智能指针</a></li><li class="chapter-item expanded "><a href="ch15-05-interior-mutability.html"><strong aria-hidden="true">15.5.</strong> RefCell&lt;T&gt; 与内部可变性模式</a></li><li class="chapter-item expanded "><a href="ch15-06-reference-cycles.html"><strong aria-hidden="true">15.6.</strong> 引用循环会导致内存泄漏</a></li></ol></li><li class="chapter-item expanded "><a href="ch16-00-concurrency.html"><strong aria-hidden="true">16.</strong> 无畏并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch16-01-threads.html"><strong aria-hidden="true">16.1.</strong> 使用线程同时地运行代码</a></li><li class="chapter-item expanded "><a href="ch16-02-message-passing.html"><strong aria-hidden="true">16.2.</strong> 使用消息传递在线程间通信</a></li><li class="chapter-item expanded "><a href="ch16-03-shared-state.html"><strong aria-hidden="true">16.3.</strong> 共享状态并发</a></li><li class="chapter-item expanded "><a href="ch16-04-extensible-concurrency-sync-and-send.html"><strong aria-hidden="true">16.4.</strong> 使用 Sync 与 Send Traits 的可扩展并发</a></li></ol></li><li class="chapter-item expanded "><a href="ch17-00-async-await.html"><strong aria-hidden="true">17.</strong> Async 和 await</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch17-01-futures-and-syntax.html"><strong aria-hidden="true">17.1.</strong> Futures 和 async 语法</a></li><li class="chapter-item expanded "><a href="ch17-02-concurrency-with-async.html"><strong aria-hidden="true">17.2.</strong> 并发与 async</a></li><li class="chapter-item expanded "><a href="ch17-03-more-futures.html"><strong aria-hidden="true">17.3.</strong> 使用任意数量的 futures</a></li><li class="chapter-item expanded "><a href="ch17-04-streams.html"><strong aria-hidden="true">17.4.</strong>Streams</a></li><li class="chapter-item expanded "><a href="ch17-05-traits-for-async.html"><strong aria-hidden="true">17.5.</strong> 深入理解 async 相关的 traits</a></li><li class="chapter-item expanded "><a href="ch17-06-futures-tasks-threads.html"><strong aria-hidden="true">17.6.</strong> Futures任务tasks和线程threads</a></li></ol></li><li class="chapter-item expanded "><a href="ch18-00-oop.html"><strong aria-hidden="true">18.</strong> Rust 的面向对象编程特性</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch18-01-what-is-oo.html"><strong aria-hidden="true">18.1.</strong> 面向对象语言的特点</a></li><li class="chapter-item expanded "><a href="ch18-02-trait-objects.html"><strong aria-hidden="true">18.2.</strong> 顾及不同类型值的 trait 对象</a></li><li class="chapter-item expanded "><a href="ch18-03-oo-design-patterns.html"><strong aria-hidden="true">18.3.</strong> 面向对象设计模式的实现</a></li></ol></li><li class="chapter-item expanded "><a href="ch19-00-patterns.html"><strong aria-hidden="true">19.</strong> 模式与模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch19-01-all-the-places-for-patterns.html"><strong aria-hidden="true">19.1.</strong> 所有可能会用到模式的位置</a></li><li class="chapter-item expanded "><a href="ch19-02-refutability.html"><strong aria-hidden="true">19.2.</strong> Refutability可反驳性: 模式是否会匹配失效</a></li><li class="chapter-item expanded "><a href="ch19-03-pattern-syntax.html"><strong aria-hidden="true">19.3.</strong> 模式语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch20-00-advanced-features.html"><strong aria-hidden="true">20.</strong> 高级特征</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch20-01-unsafe-rust.html"><strong aria-hidden="true">20.1.</strong> 不安全的 Rust</a></li><li class="chapter-item expanded "><a href="ch20-03-advanced-traits.html"><strong aria-hidden="true">20.2.</strong> 高级 trait</a></li><li class="chapter-item expanded "><a href="ch20-04-advanced-types.html"><strong aria-hidden="true">20.3.</strong> 高级类型</a></li><li class="chapter-item expanded "><a href="ch20-05-advanced-functions-and-closures.html"><strong aria-hidden="true">20.4.</strong> 高级函数与闭包</a></li><li class="chapter-item expanded "><a href="ch20-06-macros.html"><strong aria-hidden="true">20.5.</strong></a></li></ol></li><li class="chapter-item expanded "><a href="ch21-00-final-project-a-web-server.html"><strong aria-hidden="true">21.</strong> 最后的项目:构建多线程 web server</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch21-01-single-threaded.html"><strong aria-hidden="true">21.1.</strong> 建立单线程 web server</a></li><li class="chapter-item expanded "><a href="ch21-02-multithreaded.html"><strong aria-hidden="true">21.2.</strong> 将单线程 server 变为多线程 server</a></li><li class="chapter-item expanded "><a href="ch21-03-graceful-shutdown-and-cleanup.html"><strong aria-hidden="true">21.3.</strong> 优雅停机与清理</a></li></ol></li><li class="chapter-item expanded "><a href="appendix-00.html"><strong aria-hidden="true">22.</strong> 附录</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="appendix-01-keywords.html"><strong aria-hidden="true">22.1.</strong> A - 关键字</a></li><li class="chapter-item expanded "><a href="appendix-02-operators.html"><strong aria-hidden="true">22.2.</strong> B - 运算符与符号</a></li><li class="chapter-item expanded "><a href="appendix-03-derivable-traits.html"><strong aria-hidden="true">22.3.</strong> C - 可派生的 trait</a></li><li class="chapter-item expanded "><a href="appendix-04-useful-development-tools.html"><strong aria-hidden="true">22.4.</strong> D - 实用开发工具</a></li><li class="chapter-item expanded "><a href="appendix-05-editions.html"><strong aria-hidden="true">22.5.</strong> E - 版本</a></li><li class="chapter-item expanded "><a href="appendix-06-translation.html"><strong aria-hidden="true">22.6.</strong> F - 本书译本</a></li><li class="chapter-item expanded "><a href="appendix-07-nightly-rust.html"><strong aria-hidden="true">22.7.</strong> G - Rust 是如何开发的与 “Nightly Rust”</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<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="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 程序设计语言 简体中文版</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/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/edit/main/src/ch10-01-syntax.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>
<h2 id="泛型数据类型"><a class="header" href="#泛型数据类型">泛型数据类型</a></h2>
<blockquote>
<p><a href="https://github.com/rust-lang/book/blob/main/src/ch10-01-syntax.md">ch10-01-syntax.md</a>
<br>
commit f2a78f64b668f63f581203c6bac509903f7c00ee</p>
</blockquote>
<p>我们可以使用泛型为像函数签名或结构体这样的项创建定义,这样它们就可以用于多种不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型如何影响代码性能。</p>
<h3 id="在函数定义中使用泛型"><a class="header" href="#在函数定义中使用泛型">在函数定义中使用泛型</a></h3>
<p>当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。</p>
<p>回到 <code>largest</code> 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。接着我们使用泛型将其合并为一个函数。</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">fn largest_i32(list: &amp;[i32]) -&gt; &amp;i32 {
let mut largest = &amp;list[0];
for item in list {
if item &gt; largest {
largest = item;
}
}
largest
}
fn largest_char(list: &amp;[char]) -&gt; &amp;char {
let mut largest = &amp;list[0];
for item in list {
if item &gt; largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest_i32(&amp;number_list);
println!("The largest number is {result}");
<span class="boring"> assert_eq!(*result, 100);
</span>
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&amp;char_list);
println!("The largest char is {result}");
<span class="boring"> assert_eq!(*result, 'y');
</span>}</code></pre></pre>
<p><span class="caption">示例 10-4两个函数不同点只是名称和签名类型</span></p>
<p><code>largest_i32</code> 函数是从示例 10-3 中摘出来的,它用来寻找 slice 中最大的 <code>i32</code><code>largest_char</code> 函数寻找 slice 中最大的 <code>char</code>。因为两者函数体的代码是一样的,我们可以定义一个函数,再引进泛型参数来消除这种重复。</p>
<p>为了参数化这个新函数中的这些类型,我们需要为类型参数命名,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 <code>T</code>因为传统上来说Rust 的类型参数名字都比较短通常仅为一个字母同时Rust 类型名的命名规范是首字母大写驼峰式命名法UpperCamelCase<code>T</code> 作为 “type” 的缩写是大部分 Rust 程序员的首选。</p>
<p>如果要在函数体中使用参数,就必须在函数签名中声明它的名字,好让编译器知道这个名字指代的是什么。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 <code>largest</code> 函数,类型参数声明位于函数名称与参数列表中间的尖括号 <code>&lt;&gt;</code> 中,像这样:</p>
<pre><code class="language-rust ignore">fn largest&lt;T&gt;(list: &amp;[T]) -&gt; &amp;T {</code></pre>
<p>可以这样理解这个定义:函数 <code>largest</code> 有泛型类型 <code>T</code>。它有个参数 <code>list</code>,其类型是元素为 <code>T</code> 的 slice。<code>largest</code> 函数会返回一个与 <code>T</code> 相同类型的引用。</p>
<p>示例 10-5 中的 <code>largest</code> 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 <code>largest</code> 函数,把 <code>i32</code> 值的 slice 或 <code>char</code> 值的 slice 传给它。请注意这些代码还不能编译,不过稍后在本章会解决这个问题。</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore does_not_compile">fn largest&lt;T&gt;(list: &amp;[T]) -&gt; &amp;T {
let mut largest = &amp;list[0];
for item in list {
if item &gt; largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&amp;number_list);
println!("The largest number is {result}");
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&amp;char_list);
println!("The largest char is {result}");
}</code></pre>
<p><span class="caption">示例 10-5一个使用泛型参数的 <code>largest</code> 函数定义,尚不能编译</span></p>
<p>如果现在就编译这个代码,会出现如下错误:</p>
<pre><code class="language-console">$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `&gt;` cannot be applied to type `&amp;T`
--&gt; src/main.rs:5:17
|
5 | if item &gt; largest {
| ---- ^ ------- &amp;T
| |
| &amp;T
|
help: consider restricting type parameter `T`
|
1 | fn largest&lt;T: std::cmp::PartialOrd&gt;(list: &amp;[T]) -&gt; &amp;T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
</code></pre>
<p>帮助说明中提到了 <code>std::cmp::PartialOrd</code>,这是一个 <em>trait</em>。下一部分会讲到 trait。不过简单来说这个错误表明 <code>largest</code> 的函数体不能适用于 <code>T</code> 的所有可能的类型。因为在函数体需要比较 <code>T</code> 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 <code>std::cmp::PartialOrd</code> trait 可以实现类型的比较功能(查看附录 C 获取该 trait 的更多信息)。依照帮助说明中的建议,我们限制 <code>T</code> 只对实现了 <code>PartialOrd</code> 的类型有效后代码就可以编译了,因为标准库为 <code>i32</code><code>char</code> 实现了 <code>PartialOrd</code></p>
<h3 id="结构体定义中的泛型"><a class="header" href="#结构体定义中的泛型">结构体定义中的泛型</a></h3>
<p>同样也可以用 <code>&lt;&gt;</code> 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 定义了一个可以存放任何类型的 <code>x</code><code>y</code> 坐标值的结构体 <code>Point</code></p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">struct Point&lt;T&gt; {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}</code></pre></pre>
<p><span class="caption">示例 10-6<code>Point</code> 结构体存放了两个 <code>T</code> 类型的值 <code>x</code><code>y</code></span></p>
<p>其语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。</p>
<p>注意 <code>Point&lt;T&gt;</code> 的定义中只使用了一个泛型类型,这个定义表明结构体 <code>Point&lt;T&gt;</code> 对于一些类型 <code>T</code> 是泛型的,而且字段 <code>x</code><code>y</code> <strong>都是</strong> 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 <code>Point&lt;T&gt;</code> 的实例,像示例 10-7 中的代码就不能编译:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore does_not_compile">struct Point&lt;T&gt; {
x: T,
y: T,
}
fn main() {
let wont_work = Point { x: 5, y: 4.0 };
}</code></pre>
<p><span class="caption">示例 10-7字段 <code>x</code><code>y</code> 的类型必须相同,因为它们都有相同的泛型类型 <code>T</code></span></p>
<p>在这个例子中,当把整型值 5 赋值给 <code>x</code> 时,就告诉了编译器这个 <code>Point&lt;T&gt;</code> 实例中的泛型 <code>T</code> 全是整型。接着指定 <code>y</code> 为浮点值 4.0,因为它<code>y</code>被定义为与 <code>x</code> 相同类型,所以将会得到一个像这样的类型不匹配错误:</p>
<pre><code class="language-console">$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0308]: mismatched types
--&gt; src/main.rs:7:38
|
7 | let wont_work = Point { x: 5, y: 4.0 };
| ^^^ expected integer, found floating-point number
For more information about this error, try `rustc --explain E0308`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
</code></pre>
<p>如果想要定义一个 <code>x</code><code>y</code> 可以有不同类型且仍然是泛型的 <code>Point</code> 结构体,我们可以使用多个泛型类型参数。在示例 10-8 中,我们修改 <code>Point</code> 的定义为拥有两个泛型类型 <code>T</code><code>U</code>。其中字段 <code>x</code><code>T</code> 类型的,而字段 <code>y</code><code>U</code> 类型的:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">struct Point&lt;T, U&gt; {
x: T,
y: U,
}
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
}</code></pre></pre>
<p><span class="caption">示例 10-8使用两个泛型的 <code>Point</code>,这样 <code>x</code><code>y</code> 可能是不同类型</span></p>
<p>现在所有这些 <code>Point</code> 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你发现代码中需要很多泛型时,这可能表明你的代码需要重构分解成更小的结构。</p>
<h3 id="枚举定义中的泛型"><a class="header" href="#枚举定义中的泛型">枚举定义中的泛型</a></h3>
<p>和结构体类似,枚举也可以在成员中存放泛型数据类型。第六章我们曾用过标准库提供的 <code>Option&lt;T&gt;</code> 枚举,这里再回顾一下:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>enum Option&lt;T&gt; {
Some(T),
None,
}
<span class="boring">}</span></code></pre></pre>
<p>现在这个定义应该更容易理解了。如你所见 <code>Option&lt;T&gt;</code> 是一个拥有泛型 <code>T</code> 的枚举,它有两个成员:<code>Some</code>,它存放了一个类型 <code>T</code> 的值,和不存在任何值的<code>None</code>。通过 <code>Option&lt;T&gt;</code> 枚举可以表达有一个可能的值的抽象概念,同时因为 <code>Option&lt;T&gt;</code> 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。</p>
<p>枚举也可以拥有多个泛型类型。第九章使用过的 <code>Result</code> 枚举定义就是一个这样的例子:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>enum Result&lt;T, E&gt; {
Ok(T),
Err(E),
}
<span class="boring">}</span></code></pre></pre>
<p><code>Result</code> 枚举有两个泛型类型,<code>T</code><code>E</code><code>Result</code> 有两个成员:<code>Ok</code>,它存放一个类型 <code>T</code> 的值,而 <code>Err</code> 则存放一个类型 <code>E</code> 的值。这个定义使得 <code>Result</code> 枚举能很方便的表达任何可能成功(返回 <code>T</code> 类型的值)也可能失败(返回 <code>E</code> 类型的值)的操作。实际上,这就是我们在示例 9-3 用来打开文件的方式:当成功打开文件的时候,<code>T</code> 对应的是 <code>std::fs::File</code> 类型;而当打开文件出现问题时,<code>E</code> 的值则是 <code>std::io::Error</code> 类型。</p>
<p>当你意识到代码中定义了多个结构体或枚举,它们不一样的地方只是其中的值的类型的时候,不妨通过泛型类型来避免重复。</p>
<h3 id="方法定义中的泛型"><a class="header" href="#方法定义中的泛型">方法定义中的泛型</a></h3>
<p>在为结构体和枚举实现方法时(像第五章那样),一样也可以用泛型。示例 10-9 中展示了示例 10-6 中定义的结构体 <code>Point&lt;T&gt;</code>,和在其上实现的名为 <code>x</code> 的方法。</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">struct Point&lt;T&gt; {
x: T,
y: T,
}
impl&lt;T&gt; Point&lt;T&gt; {
fn x(&amp;self) -&gt; &amp;T {
&amp;self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}</code></pre></pre>
<p><span class="caption">示例 10-9<code>Point&lt;T&gt;</code> 结构体上实现方法 <code>x</code>,它返回 <code>T</code> 类型的字段 <code>x</code> 的引用</span></p>
<p>这里在 <code>Point&lt;T&gt;</code> 上定义了一个叫做 <code>x</code> 的方法来返回字段 <code>x</code> 中数据的引用:</p>
<p>注意必须在 <code>impl</code> 后面声明 <code>T</code>,这样就可以在 <code>Point&lt;T&gt;</code> 上实现的方法中使用 <code>T</code> 了。通过在 <code>impl</code> 之后声明泛型 <code>T</code>Rust 就知道 <code>Point</code> 的尖括号中的类型是泛型而不是具体类型。我们可以为泛型参数选择一个与结构体定义中声明的泛型参数所不同的名称,不过依照惯例使用了相同的名称。在声明泛型类型参数的 <code>impl</code> 中编写的方法将会定义在该类型的任何实例上,无论最终替换泛型类型参数的是何具体类型。(译者注:以示例 10-9 为例,<code>impl</code> 中声明了泛型类型参数 <code>T</code><code>x</code> 是编写在 <code>impl</code> 中的方法,<code>x</code> 方法将会定义在 <code>Point&lt;T&gt;</code> 的任何实例上,无论最终替换泛型类型参数 <code>T</code> 的是何具体类型)。</p>
<p>定义方法时也可以为泛型指定限制constraint。例如可以选择为 <code>Point&lt;f32&gt;</code> 实例实现方法,而不是为泛型 <code>Point</code> 实例。示例 10-10 展示了一个没有在 <code>impl</code> 之后(的尖括号)声明泛型的例子,这里使用了一个具体类型,<code>f32</code></p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">struct Point&lt;T&gt; {
</span><span class="boring"> x: T,
</span><span class="boring"> y: T,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl&lt;T&gt; Point&lt;T&gt; {
</span><span class="boring"> fn x(&amp;self) -&gt; &amp;T {
</span><span class="boring"> &amp;self.x
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span>impl Point&lt;f32&gt; {
fn distance_from_origin(&amp;self) -&gt; f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
<span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> let p = Point { x: 5, y: 10 };
</span><span class="boring">
</span><span class="boring"> println!("p.x = {}", p.x());
</span><span class="boring">}</span></code></pre></pre>
<p><span class="caption">示例 10-10构建一个只用于拥有泛型参数 <code>T</code> 的结构体的具体类型的 <code>impl</code></span></p>
<p>这段代码意味着 <code>Point&lt;f32&gt;</code> 类型会有一个方法 <code>distance_from_origin</code>,而其他 <code>T</code> 不是 <code>f32</code> 类型的 <code>Point&lt;T&gt;</code> 实例则没有定义此方法。这个方法计算点实例与坐标 (0.0, 0.0) 之间的距离,并使用了只能用于浮点型的数学运算符。</p>
<p>结构体定义中的泛型类型参数并不总是与结构体方法签名中使用的泛型是同一类型。示例 10-11 中为 <code>Point</code> 结构体使用了泛型类型 <code>X1</code><code>Y1</code>,为 <code>mixup</code> 方法签名使用了 <code>X2</code><code>Y2</code> 来使得示例更加清楚。这个方法用 <code>self</code><code>Point</code> 类型的 <code>x</code> 值(类型 <code>X1</code>)和参数的 <code>Point</code> 类型的 <code>y</code> 值(类型 <code>Y2</code>)来创建一个新 <code>Point</code> 类型的实例:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">struct Point&lt;X1, Y1&gt; {
x: X1,
y: Y1,
}
impl&lt;X1, Y1&gt; Point&lt;X1, Y1&gt; {
fn mixup&lt;X2, Y2&gt;(self, other: Point&lt;X2, Y2&gt;) -&gt; Point&lt;X1, Y2&gt; {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}</code></pre></pre>
<p><span class="caption">示例 10-11方法使用了与结构体定义中不同类型的泛型</span></p>
<p><code>main</code> 函数中,定义了一个有 <code>i32</code> 类型的 <code>x</code>(其值为 <code>5</code>)和 <code>f64</code><code>y</code>(其值为 <code>10.4</code>)的 <code>Point</code><code>p2</code> 则是一个有着字符串 slice 类型的 <code>x</code>(其值为 <code>"Hello"</code>)和 <code>char</code> 类型的 <code>y</code>(其值为<code>c</code>)的 <code>Point</code>。在 <code>p1</code> 上以 <code>p2</code> 作为参数调用 <code>mixup</code> 会返回一个 <code>p3</code>,它会有一个 <code>i32</code> 类型的 <code>x</code>,因为 <code>x</code> 来自 <code>p1</code>,并拥有一个 <code>char</code> 类型的 <code>y</code>,因为 <code>y</code> 来自 <code>p2</code><code>println!</code> 会打印出 <code>p3.x = 5, p3.y = c</code></p>
<p>这个例子的目的是展示一些泛型通过 <code>impl</code> 声明而另一些通过方法定义声明的情况。这里泛型参数 <code>X1</code><code>Y1</code> 声明于 <code>impl</code> 之后,因为它们与结构体定义相对应。而泛型参数 <code>X2</code><code>Y2</code> 声明于 <code>fn mixup</code> 之后,因为它们只是相对于方法本身的。</p>
<h3 id="泛型代码的性能"><a class="header" href="#泛型代码的性能">泛型代码的性能</a></h3>
<p>在阅读本部分内容的同时,你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是泛型并不会使程序比具体类型运行得慢。</p>
<p>Rust 通过在编译时进行泛型代码的 <strong>单态化</strong><em>monomorphization</em>)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。</p>
<p>在这个过程中,编译器所做的工作正好与示例 10-5 中我们创建泛型函数的步骤相反。编译器寻找所有泛型代码被调用的位置并使用泛型代码针对具体类型生成代码。</p>
<p>让我们看看这如何用于标准库中的 <code>Option</code> 枚举:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let integer = Some(5);
let float = Some(5.0);
<span class="boring">}</span></code></pre></pre>
<p>当 Rust 编译这些代码的时候,它会进行单态化。编译器会读取传递给 <code>Option&lt;T&gt;</code> 的值并发现有两种 <code>Option&lt;T&gt;</code>:一个对应 <code>i32</code> 另一个对应 <code>f64</code>。为此,它会将泛型定义 <code>Option&lt;T&gt;</code> 展开为两个针对 <code>i32</code><code>f64</code> 的定义,接着将泛型定义替换为这两个具体的定义。</p>
<p>编译器生成的单态化版本的代码看起来像这样(编译器会使用不同于如下假想的名字):</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}</code></pre></pre>
<p>泛型 <code>Option&lt;T&gt;</code> 被编译器替换为了具体的定义。因为 Rust 会将每种情况下的泛型代码编译为具体类型,使用泛型没有运行时开销。当代码运行时,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch10-00-generics.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="ch10-02-traits.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="ch10-00-generics.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="ch10-02-traits.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 -->
<script src="ferris.js"></script>
</div>
</body>
</html>