|
|
|
|
<!DOCTYPE HTML>
|
|
|
|
|
<html lang="zh-CN" class="light" dir="ltr">
|
|
|
|
|
<head>
|
|
|
|
|
<!-- Book generated using mdBook -->
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<title>结构体 - Rust语言圣经(Rust Course)</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="../../theme/style.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 affix "><a href="../../about-book.html">关于本书</a></li><li class="chapter-item affix "><a href="../../into-rust.html">进入 Rust 编程世界</a></li><li class="chapter-item affix "><a href="../../first-try/sth-you-should-not-do.html">避免从入门到放弃</a></li><li class="chapter-item affix "><a href="../../community.html">社区和锈书</a></li><li class="spacer"></li><li class="chapter-item affix "><a href="../../some-thoughts.html">Xobserve: 一切皆可观测</a></li><li class="chapter-item affix "><a href="../../beat-ai.html">BeatAI: 工程师 AI 入门圣经</a></li><li class="chapter-item affix "><li class="part-title">Rust 语言基础学习</li><li class="spacer"></li><li class="chapter-item "><a href="../../first-try/intro.html"><strong aria-hidden="true">1.</strong> 寻找牛刀,以便小试</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../first-try/installation.html"><strong aria-hidden="true">1.1.</strong> 安装 Rust 环境</a></li><li class="chapter-item "><a href="../../first-try/editor.html"><strong aria-hidden="true">1.2.</strong> 墙推 VSCode!</a></li><li class="chapter-item "><a href="../../first-try/cargo.html"><strong aria-hidden="true">1.3.</strong> 认识 Cargo</a></li><li class="chapter-item "><a href="../../first-try/hello-world.html"><strong aria-hidden="true">1.4.</strong> 不仅仅是 Hello world</a></li><li class="chapter-item "><a href="../../first-try/slowly-downloading.html"><strong aria-hidden="true">1.5.</strong> 下载依赖太慢了?</a></li></ol></li><li class="chapter-item expanded "><a href="../../basic/intro.html"><strong aria-hidden="true">2.</strong> Rust 基础入门</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/variable.html"><strong aria-hidden="true">2.1.</strong> 变量绑定与解构</a></li><li class="chapter-item "><a href="../../basic/base-type/index.html"><strong aria-hidden="true">2.2.</strong> 基本类型</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/base-type/numbers.html"><strong aria-hidden="true">2.2.1.</strong> 数值类型</a></li><li class="chapter-item "><a href="../../basic/base-type/char-bool.html"><strong aria-hidden="true">2.2.2.</strong> 字符、布尔、单元类型</a></li><li class="chapter-item "><a href="../../basic/base-type/statement-expression.html"><strong aria-hidden="true">2.2.3.</strong> 语句与表达式</a></li><li class="chapter-item "><a href="../../basic/base-type/function.html"><strong aria-hidden="true">2.2.4.</strong> 函数</a></li></ol></li><li class="chapter-item "><a href="../../basic/ownership/index.html"><strong aria-hidden="true">2.3.</strong> 所有权和借用</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/ownership/ownership.html"><strong aria-hidden="true">2.3.1.</strong> 所有权</a></li><li class="chapter-item "><a href="../../basic/ownership/borrowing.html"><strong aria-hidden="true">2.3.2.</strong> 引用与借用</a></li></ol></li><li class="chapter-item expanded "><a href="../../basic/compound-type/intro.html"><strong aria-hidden="true">2.4.</strong> 复合类型</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/compound-type/string-slice.html"><strong aria-hidden="true">2.4.1.</strong> 字符串与切片</a></li><li class="chapter-item "><a href="../../basic/compound-type/tuple.html"><strong aria-hidden="true">2.4.2.</strong> 元组</a></li><li class="chapter-item expanded "><a href="../../basic/compound-type/struct.html" class="active"><strong aria-hidden="true">2.4.3.</strong> 结构体</a></li><li class="chapter-item "><a href="../../basic/compound-type/enum.html"><strong aria-hidden="true">2.4.4.</strong> 枚举</a></li><li class="chapter-item "><a href="../../basic/compound-type/array.html"><strong aria-h
|
|
|
|
|
</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语言圣经(Rust Course)</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/sunface/rust-course" title="Git repository" aria-label="Git repository">
|
|
|
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|
|
|
|
</a>
|
|
|
|
|
<a href="https://github.com/sunface/rust-course/edit/main/src/basic/compound-type/struct.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">
|
|
|
|
|
<!-- Page table of contents -->
|
|
|
|
|
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
|
|
|
|
<main>
|
|
|
|
|
<h1 id="结构体"><a class="header" href="#结构体">结构体</a></h1>
|
|
|
|
|
<p>上一节中提到需要一个更高级的数据结构来帮助我们更好的抽象问题,结构体 <code>struct</code> 恰恰就是这样的复合数据结构,它是由其它数据类型组合而来。 其它语言也有类似的数据结构,不过可能有不同的名称,例如 <code>object</code>、 <code>record</code> 等。</p>
|
|
|
|
|
<p>结构体跟之前讲过的<a href="https://course.rs/basic/compound-type/tuple.html">元组</a>有些相像:都是由多种类型组合而成。但是与元组不同的是,结构体可以为内部的每个字段起一个富有含义的名称。因此结构体更加灵活更加强大,你无需依赖这些字段的顺序来访问和解析它们。</p>
|
|
|
|
|
<h2 id="结构体语法"><a class="header" href="#结构体语法">结构体语法</a></h2>
|
|
|
|
|
<p>天下无敌的剑士往往也因为他有一柄无双之剑,既然结构体这么强大,那么我们就需要给它配套一套强大的语法,让用户能更好的驾驭。</p>
|
|
|
|
|
<h4 id="定义结构体"><a class="header" href="#定义结构体">定义结构体</a></h4>
|
|
|
|
|
<p>一个结构体由几部分组成:</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>通过关键字 <code>struct</code> 定义</li>
|
|
|
|
|
<li>一个清晰明确的结构体 <code>名称</code></li>
|
|
|
|
|
<li>几个有名字的结构体 <code>字段</code></li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p>例如, 以下结构体定义了某网站的用户:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>struct User {
|
|
|
|
|
active: bool,
|
|
|
|
|
username: String,
|
|
|
|
|
email: String,
|
|
|
|
|
sign_in_count: u64,
|
|
|
|
|
}
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>该结构体名称是 <code>User</code>,拥有 4 个字段,且每个字段都有对应的字段名及类型声明,例如 <code>username</code> 代表了用户名,是一个可变的 <code>String</code> 类型。</p>
|
|
|
|
|
<h4 id="创建结构体实例"><a class="header" href="#创建结构体实例">创建结构体实例</a></h4>
|
|
|
|
|
<p>为了使用上述结构体,我们需要创建 <code>User</code> 结构体的<strong>实例</strong>:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span> let user1 = User {
|
|
|
|
|
email: String::from("someone@example.com"),
|
|
|
|
|
username: String::from("someusername123"),
|
|
|
|
|
active: true,
|
|
|
|
|
sign_in_count: 1,
|
|
|
|
|
};
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>有几点值得注意:</p>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>初始化实例时,<strong>每个字段</strong>都需要进行初始化</li>
|
|
|
|
|
<li>初始化时的字段顺序<strong>不需要</strong>和结构体定义时的顺序一致</li>
|
|
|
|
|
</ol>
|
|
|
|
|
<h4 id="访问结构体字段"><a class="header" href="#访问结构体字段">访问结构体字段</a></h4>
|
|
|
|
|
<p>通过 <code>.</code> 操作符即可访问结构体实例内部的字段值,也可以修改它们:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span> let mut user1 = User {
|
|
|
|
|
email: String::from("someone@example.com"),
|
|
|
|
|
username: String::from("someusername123"),
|
|
|
|
|
active: true,
|
|
|
|
|
sign_in_count: 1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
user1.email = String::from("anotheremail@example.com");
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>需要注意的是,必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。</p>
|
|
|
|
|
<h4 id="简化结构体创建"><a class="header" href="#简化结构体创建">简化结构体创建</a></h4>
|
|
|
|
|
<p>下面的函数类似一个构建函数,返回了 <code>User</code> 结构体的实例:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>fn build_user(email: String, username: String) -> User {
|
|
|
|
|
User {
|
|
|
|
|
email: email,
|
|
|
|
|
username: username,
|
|
|
|
|
active: true,
|
|
|
|
|
sign_in_count: 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>它接收两个字符串参数: <code>email</code> 和 <code>username</code>,然后使用它们来创建一个 <code>User</code> 结构体,并且返回。可以注意到这两行: <code>email: email</code> 和 <code>username: username</code>,非常的扎眼,因为实在有些啰嗦,如果你从 TypeScript 过来,肯定会鄙视 Rust 一番,不过好在,它也不是无可救药:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>fn build_user(email: String, username: String) -> User {
|
|
|
|
|
User {
|
|
|
|
|
email,
|
|
|
|
|
username,
|
|
|
|
|
active: true,
|
|
|
|
|
sign_in_count: 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>如上所示,当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化,跟 TypeScript 中一模一样。</p>
|
|
|
|
|
<h4 id="结构体更新语法"><a class="header" href="#结构体更新语法">结构体更新语法</a></h4>
|
|
|
|
|
<p>在实际场景中,有一种情况很常见:根据已有的结构体实例,创建新的结构体实例,例如根据已有的 <code>user1</code> 实例来构建 <code>user2</code>:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span> let user2 = User {
|
|
|
|
|
active: user1.active,
|
|
|
|
|
username: user1.username,
|
|
|
|
|
email: String::from("another@example.com"),
|
|
|
|
|
sign_in_count: user1.sign_in_count,
|
|
|
|
|
};
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>老话重提,如果你从 TypeScript 过来,肯定觉得啰嗦爆了:竟然手动把 <code>user1</code> 的三个字段逐个赋值给 <code>user2</code>,好在 Rust 为我们提供了 <code>结构体更新语法</code>:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span> let user2 = User {
|
|
|
|
|
email: String::from("another@example.com"),
|
|
|
|
|
..user1
|
|
|
|
|
};
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>因为 <code>user2</code> 仅仅在 <code>email</code> 上与 <code>user1</code> 不同,因此我们只需要对 <code>email</code> 进行赋值,剩下的通过结构体更新语法 <code>..user1</code> 即可完成。</p>
|
|
|
|
|
<p><code>..</code> 语法表明凡是我们没有显式声明的字段,全部从 <code>user1</code> 中自动获取。需要注意的是 <code>..user1</code> 必须在结构体的尾部使用。</p>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>结构体更新语法跟赋值语句 <code>=</code> 非常相像,因此在上面代码中,<code>user1</code> 的部分字段所有权被转移到 <code>user2</code> 中:<code>username</code> 字段发生了所有权转移,作为结果,<code>user1</code> 无法再被使用。</p>
|
|
|
|
|
<p>聪明的读者肯定要发问了:明明有三个字段进行了自动赋值,为何只有 <code>username</code> 发生了所有权转移?</p>
|
|
|
|
|
<p>仔细回想一下<a href="https://course.rs/basic/ownership/ownership.html#%E6%8B%B7%E8%B4%9D%E6%B5%85%E6%8B%B7%E8%B4%9D">所有权</a>那一节的内容,我们提到了 <code>Copy</code> 特征:实现了 <code>Copy</code> 特征的类型无需所有权转移,可以直接在赋值时进行
|
|
|
|
|
数据拷贝,其中 <code>bool</code> 和 <code>u64</code> 类型就实现了 <code>Copy</code> 特征,因此 <code>active</code> 和 <code>sign_in_count</code> 字段在赋值给 <code>user2</code> 时,仅仅发生了拷贝,而不是所有权转移。</p>
|
|
|
|
|
<p>值得注意的是:<code>username</code> 所有权被转移给了 <code>user2</code>,导致了 <code>user1</code> 无法再被使用,但是并不代表 <code>user1</code> 内部的其它字段不能被继续使用,例如:</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#[derive(Debug)]
|
|
|
|
|
</span><span class="boring">struct User {
|
|
|
|
|
</span><span class="boring"> active: bool,
|
|
|
|
|
</span><span class="boring"> username: String,
|
|
|
|
|
</span><span class="boring"> email: String,
|
|
|
|
|
</span><span class="boring"> sign_in_count: u64,
|
|
|
|
|
</span><span class="boring">}
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>let user1 = User {
|
|
|
|
|
email: String::from("someone@example.com"),
|
|
|
|
|
username: String::from("someusername123"),
|
|
|
|
|
active: true,
|
|
|
|
|
sign_in_count: 1,
|
|
|
|
|
};
|
|
|
|
|
let user2 = User {
|
|
|
|
|
active: user1.active,
|
|
|
|
|
username: user1.username,
|
|
|
|
|
email: String::from("another@example.com"),
|
|
|
|
|
sign_in_count: user1.sign_in_count,
|
|
|
|
|
};
|
|
|
|
|
println!("{}", user1.active);
|
|
|
|
|
// 下面这行会报错
|
|
|
|
|
println!("{:?}", user1);
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<h2 id="结构体的内存排列"><a class="header" href="#结构体的内存排列">结构体的内存排列</a></h2>
|
|
|
|
|
<p>先来看以下代码:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">#[derive(Debug)]
|
|
|
|
|
struct File {
|
|
|
|
|
name: String,
|
|
|
|
|
data: Vec<u8>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let f1 = File {
|
|
|
|
|
name: String::from("f1.txt"),
|
|
|
|
|
data: Vec::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let f1_name = &f1.name;
|
|
|
|
|
let f1_length = &f1.data.len();
|
|
|
|
|
|
|
|
|
|
println!("{:?}", f1);
|
|
|
|
|
println!("{} is {} bytes long", f1_name, f1_length);
|
|
|
|
|
}</code></pre></pre>
|
|
|
|
|
<p>上面定义的 <code>File</code> 结构体在内存中的排列如下图所示:
|
|
|
|
|
<img alt="" src="https://pic3.zhimg.com/80/v2-8cc4ed8cd06d60f974d06ca2199b8df5_1440w.png" class="center" /></p>
|
|
|
|
|
<p>从图中可以清晰地看出 <code>File</code> 结构体两个字段 <code>name</code> 和 <code>data</code> 分别拥有底层两个 <code>[u8]</code> 数组的所有权(<code>String</code> 类型的底层也是 <code>[u8]</code> 数组),通过 <code>ptr</code> 指针指向底层数组的内存地址,这里你可以把 <code>ptr</code> 指针理解为 Rust 中的引用类型。</p>
|
|
|
|
|
<p>该图片也侧面印证了:<strong>把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段</strong>。</p>
|
|
|
|
|
<h2 id="元组结构体tuple-struct"><a class="header" href="#元组结构体tuple-struct">元组结构体(Tuple Struct)</a></h2>
|
|
|
|
|
<p>结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体,例如:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span> struct Color(i32, i32, i32);
|
|
|
|
|
struct Point(i32, i32, i32);
|
|
|
|
|
|
|
|
|
|
let black = Color(0, 0, 0);
|
|
|
|
|
let origin = Point(0, 0, 0);
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。例如上面的 <code>Point</code> 元组结构体,众所周知 3D 点是 <code>(x, y, z)</code> 形式的坐标点,因此我们无需再为内部的字段逐一命名为:<code>x</code>, <code>y</code>, <code>z</code>。</p>
|
|
|
|
|
<h2 id="单元结构体unit-like-struct"><a class="header" href="#单元结构体unit-like-struct">单元结构体(Unit-like Struct)</a></h2>
|
|
|
|
|
<p>还记得之前讲过的基本没啥用的<a href="https://course.rs/basic/base-type/char-bool.html#%E5%8D%95%E5%85%83%E7%B1%BB%E5%9E%8B">单元类型</a>吧?单元结构体就跟它很像,没有任何字段和属性,但是好在,它还挺有用。</p>
|
|
|
|
|
<p>如果你定义一个类型,但是不关心该类型的内容,只关心它的行为时,就可以使用 <code>单元结构体</code>:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>struct AlwaysEqual;
|
|
|
|
|
|
|
|
|
|
let subject = AlwaysEqual;
|
|
|
|
|
|
|
|
|
|
// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
|
|
|
|
|
impl SomeTrait for AlwaysEqual {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<h2 id="结构体数据的所有权"><a class="header" href="#结构体数据的所有权">结构体数据的所有权</a></h2>
|
|
|
|
|
<p>在之前的 <code>User</code> 结构体的定义中,有一处细节:我们使用了自身拥有所有权的 <code>String</code> 类型而不是基于引用的 <code>&str</code> 字符串切片类型。这是一个有意而为之的选择:因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。</p>
|
|
|
|
|
<p>你也可以让 <code>User</code> 结构体从其它对象借用数据,不过这么做,就需要引入<a href="https://course.rs/basic/lifetime.html">生命周期(lifetimes)</a>这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。</p>
|
|
|
|
|
<p>总之,如果你想在结构体中使用一个引用,就必须加上生命周期,否则就会报错:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">struct User {
|
|
|
|
|
username: &str,
|
|
|
|
|
email: &str,
|
|
|
|
|
sign_in_count: u64,
|
|
|
|
|
active: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let user1 = User {
|
|
|
|
|
email: "someone@example.com",
|
|
|
|
|
username: "someusername123",
|
|
|
|
|
active: true,
|
|
|
|
|
sign_in_count: 1,
|
|
|
|
|
};
|
|
|
|
|
}</code></pre></pre>
|
|
|
|
|
<p>编译器会抱怨它需要生命周期标识符:</p>
|
|
|
|
|
<pre><code class="language-console">error[E0106]: missing lifetime specifier
|
|
|
|
|
--> src/main.rs:2:15
|
|
|
|
|
|
|
|
|
|
|
2 | username: &str,
|
|
|
|
|
| ^ expected named lifetime parameter // 需要一个生命周期
|
|
|
|
|
|
|
|
|
|
|
help: consider introducing a named lifetime parameter // 考虑像下面的代码这样引入一个生命周期
|
|
|
|
|
|
|
|
|
|
|
1 ~ struct User<'a> {
|
|
|
|
|
2 ~ username: &'a str,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
error[E0106]: missing lifetime specifier
|
|
|
|
|
--> src/main.rs:3:12
|
|
|
|
|
|
|
|
|
|
|
3 | email: &str,
|
|
|
|
|
| ^ expected named lifetime parameter
|
|
|
|
|
|
|
|
|
|
|
help: consider introducing a named lifetime parameter
|
|
|
|
|
|
|
|
|
|
|
1 ~ struct User<'a> {
|
|
|
|
|
2 | username: &str,
|
|
|
|
|
3 ~ email: &'a str,
|
|
|
|
|
|
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>未来在<a href="https://course.rs/basic/lifetime.html">生命周期</a>中会讲到如何修复这个问题以便在结构体中存储引用,不过在那之前,我们会避免在结构体中使用引用类型。</p>
|
|
|
|
|
<h2 id="使用-derivedebug-来打印结构体的信息"><a class="header" href="#使用-derivedebug-来打印结构体的信息">使用 <code>#[derive(Debug)]</code> 来打印结构体的信息</a></h2>
|
|
|
|
|
<p>在前面的代码中我们使用 <code>#[derive(Debug)]</code> 对结构体进行了标记,这样才能使用 <code>println!("{:?}", s);</code> 的方式对其进行打印输出,如果不加,看看会发生什么:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">struct Rectangle {
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let rect1 = Rectangle {
|
|
|
|
|
width: 30,
|
|
|
|
|
height: 50,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
println!("rect1 is {}", rect1);
|
|
|
|
|
}</code></pre></pre>
|
|
|
|
|
<p>首先可以观察到,上面使用了 <code>{}</code> 而不是之前的 <code>{:?}</code>,运行后报错:</p>
|
|
|
|
|
<pre><code class="language-shell">error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>提示我们结构体 <code>Rectangle</code> 没有实现 <code>Display</code> 特征,这是因为如果我们使用 <code>{}</code> 来格式化输出,那对应的类型就必须实现 <code>Display</code> 特征,以前学习的基本类型,都默认实现了该特征:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
|
|
|
|
|
let v = 1;
|
|
|
|
|
let b = true;
|
|
|
|
|
|
|
|
|
|
println!("{}, {}", v, b);
|
|
|
|
|
}</code></pre></pre>
|
|
|
|
|
<p>上面代码不会报错,那么结构体为什么不默认实现 <code>Display</code> 特征呢?原因在于结构体较为复杂,例如考虑以下问题:你想要逗号对字段进行分割吗?需要括号吗?加在什么地方?所有的字段都应该显示?类似的还有很多,由于这种复杂性,Rust 不希望猜测我们想要的是什么,而是把选择权交给我们自己来实现:如果要用 <code>{}</code> 的方式打印结构体,那就自己实现 <code>Display</code> 特征。</p>
|
|
|
|
|
<p>接下来继续阅读报错:</p>
|
|
|
|
|
<pre><code class="language-shell">= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
|
|
|
|
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>上面提示我们使用 <code>{:?}</code> 来试试,这个方式我们在本文的前面也见过,下面来试试:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>println!("rect1 is {:?}", rect1);
|
|
|
|
|
<span class="boring">}</span></code></pre></pre>
|
|
|
|
|
<p>可是依然无情报错了:</p>
|
|
|
|
|
<pre><code class="language-shell">error[E0277]: `Rectangle` doesn't implement `Debug`
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>好在,聪明的编译器又一次给出了提示:</p>
|
|
|
|
|
<pre><code class="language-shell">= help: the trait `Debug` is not implemented for `Rectangle`
|
|
|
|
|
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>让我们实现 <code>Debug</code> 特征,Oh No,就是不想实现 <code>Display</code> 特征,才用的 <code>{:?}</code>,怎么又要实现 <code>Debug</code>,但是仔细看,提示中有一行: <code>add #[derive(Debug)] to Rectangle</code>, 哦?这不就是我们前文一直在使用的吗?</p>
|
|
|
|
|
<p>首先,Rust 默认不会为我们实现 <code>Debug</code>,为了实现,有两种方式可以选择:</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>手动实现</li>
|
|
|
|
|
<li>使用 <code>derive</code> 派生实现</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p>后者简单的多,但是也有限制,具体见<a href="https://course.rs/appendix/derive.html">附录 D</a>,这里我们就不再深入讲解,来看看该如何使用:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">#[derive(Debug)]
|
|
|
|
|
struct Rectangle {
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let rect1 = Rectangle {
|
|
|
|
|
width: 30,
|
|
|
|
|
height: 50,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
println!("rect1 is {:?}", rect1);
|
|
|
|
|
}</code></pre></pre>
|
|
|
|
|
<p>此时运行程序,就不再有错误,输出如下:</p>
|
|
|
|
|
<pre><code class="language-shell">$ cargo run
|
|
|
|
|
rect1 is Rectangle { width: 30, height: 50 }
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>这个输出格式看上去也不赖嘛,虽然未必是最好的。这种格式是 Rust 自动为我们提供的实现,看上基本就跟结构体的定义形式一样。</p>
|
|
|
|
|
<p>当结构体较大时,我们可能希望能够有更好的输出表现,此时可以使用 <code>{:#?}</code> 来替代 <code>{:?}</code>,输出如下:</p>
|
|
|
|
|
<pre><code class="language-shell">rect1 is Rectangle {
|
|
|
|
|
width: 30,
|
|
|
|
|
height: 50,
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>此时结构体的输出跟我们创建时候的代码几乎一模一样了!当然,如果大家还是不满足,那最好还是自己实现 <code>Display</code> 特征,以向用户更美的展示你的私藏结构体。关于格式化输出的更多内容,我们强烈推荐看看这个<a href="https://course.rs/basic/formatted-output.html#debug-%E7%89%B9%E5%BE%81">章节</a>。</p>
|
|
|
|
|
<p>还有一个简单的输出 debug 信息的方法,那就是使用 <a href="https://doc.rust-lang.org/std/macro.dbg.html"><code>dbg!</code> 宏</a>,它会拿走表达式的所有权,然后打印出相应的文件名、行号等 debug 信息,当然还有我们需要的表达式的求值结果。<strong>除此之外,它最终还会把表达式值的所有权返回!</strong></p>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p><code>dbg!</code> 输出到标准错误输出 <code>stderr</code>,而 <code>println!</code> 输出到标准输出 <code>stdout</code>。</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<p>下面的例子中清晰的展示了 <code>dbg!</code> 如何在打印出信息的同时,还把表达式的值赋给了 <code>width</code>:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">#[derive(Debug)]
|
|
|
|
|
struct Rectangle {
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let scale = 2;
|
|
|
|
|
let rect1 = Rectangle {
|
|
|
|
|
width: dbg!(30 * scale),
|
|
|
|
|
height: 50,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dbg!(&rect1);
|
|
|
|
|
}</code></pre></pre>
|
|
|
|
|
<p>最终的 debug 输出如下:</p>
|
|
|
|
|
<pre><code class="language-shell">$ cargo run
|
|
|
|
|
[src/main.rs:10] 30 * scale = 60
|
|
|
|
|
[src/main.rs:14] &rect1 = Rectangle {
|
|
|
|
|
width: 60,
|
|
|
|
|
height: 50,
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>可以看到,我们想要的 debug 信息几乎都有了:代码所在的文件名、行号、表达式以及表达式的值,简直完美!</p>
|
|
|
|
|
<h2 id="课后练习"><a class="header" href="#课后练习">课后练习</a></h2>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p><a href="https://practice-zh.course.rs/compound-types/struct.html">Rust By Practice</a>,支持代码在线编辑和运行,并提供详细的<a href="https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/struct.md">习题解答</a>。</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
|
|
|
|
|
<div id="giscus-container"></div>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
|
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
<a rel="prev" href="../../basic/compound-type/tuple.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="../../basic/compound-type/enum.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="../../basic/compound-type/tuple.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="../../basic/compound-type/enum.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="../../ace.js"></script>
|
|
|
|
|
<script src="../../editor.js"></script>
|
|
|
|
|
<script src="../../mode-rust.js"></script>
|
|
|
|
|
<script src="../../theme-dawn.js"></script>
|
|
|
|
|
<script src="../../theme-tomorrow_night.js"></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>
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript" charset="utf-8">
|
|
|
|
|
var pagePath = "basic/compound-type/struct.md"
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
<script src="../../assets/custom.js"></script>
|
|
|
|
|
<script src="../../assets/bigPicture.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|