mirror of
https://github.com/sunface/new-rusty-book.git
synced 2025-06-22 19:59:42 +00:00
first commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
book
|
||||
.DS_Store
|
395
LICENSE
Normal file
395
LICENSE
Normal file
@ -0,0 +1,395 @@
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the "Licensor." The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
60
README.md
Normal file
60
README.md
Normal file
@ -0,0 +1,60 @@
|
||||
<h1 align="center">Rusty Book( 锈书 )</h1>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/studyrs/rusty-book/blob/main/assets/banner.gif?raw=true" />
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
在线阅读: https://rusty.course.rs
|
||||
</div>
|
||||
|
||||
在 Rust 元宇宙,夸奖别人的最高境界就是 `rusty`: 今天你"锈"了吗? 你的 Rust 代码好锈啊!而本书,就是精选了各种开源库和代码片段,帮助大家打造优"锈"的 Rust 项目。
|
||||
|
||||
总之,如果有以下需求,那看锈书就对了:
|
||||
|
||||
- 想要知道现在优秀的、关注度高的 Rust 项目有哪些
|
||||
|
||||
- 发现一些好玩、有趣、酷炫的开源库
|
||||
|
||||
- 需要寻找某个类型的库,例如,一个 HTTP 客户端或 ProtoBuffer 编码库,要求是好用、更新活跃、高质量
|
||||
|
||||
- 想要寻找常用操作的代码片段,用于熟悉 Rust 或者直接复制粘贴到自己的项目中,例如文件操作、数据库操作、HTTP 请求、排序算法、正则等
|
||||
|
||||
|
||||
细心的同学可能会发现,1-3 对应的是 [awesome-rust](https://github.com/rust-unofficial/awesome-rust), 4 对应的是 [rust-cookbook](https://github.com/rust-lang-nursery/rust-cookbook),那么锈书除了整合两块内容,合二为一外,还有其它的优点吗?
|
||||
|
||||
## 它们存在的问题
|
||||
|
||||
**awesome-rust,最大的问题就是里面的内容鱼龙混**。低质量的、几年不更新的、分类不准的比比皆是,而且缺乏对项目的详细分析介绍,更多的就是把所有东西给你列出来,告诉你:诺,这几十个都是你想要的,自己挑吧。至于为什么会这样,根源在于它允许任何人去提交自己的库的链接,几乎没有做任何筛选。
|
||||
|
||||
所以,个人觉得不应该叫 awesome-rust,叫 all-rust 更为合适。如果存在疑义的同学,可以自己调研一番,再跟锈书的项目对比下,就明白我所言是否有虚了。
|
||||
|
||||
|
||||
对于开发者而言,Cookbook 非常实用,几乎每一门编程语言都是如此。原因无他:聪明的开发者大部分时间不是在复制粘贴就是在复制粘贴的路上。而 CookBook 恰恰为各种实用场景提供了可供直接复制粘贴的代码,例如网络协议、数据库和文件操作、随机数生成、命令行解析等。
|
||||
|
||||
**但目前的 Rust Cookbook 更新非常不活跃,里面缺少了大量实用库,还有一些过时的老库**。
|
||||
|
||||
哦对了,还有一点,它们都不是中文的,而锈书是基于中文写的,很快还将翻译成英文。
|
||||
|
||||
## 我们的优势
|
||||
|
||||
既然列出了别人的缺点,那我们自然是为了解决这些问题而来的。总的来说,锈书有以下特点:
|
||||
|
||||
- 分类更加清晰、实用,也更方便于用户找到想要的库
|
||||
|
||||
- 所有的库都是精选的,就算通过 PR 提交的,也必须经过我们的严格审核后,再能加入
|
||||
|
||||
- 长期不更新的库( 一些已经稳定的工具库除外 )要坚决的移除
|
||||
|
||||
- 每一个库都有较为详细的介绍甚至是配图,让大家争取不用进到项目里调查一番,就知道该项目的详细用途
|
||||
|
||||
## 社区贡献
|
||||
|
||||
欢迎大家提交任何类型的 PR:好的库、实用的代码片段、内容和文字勘误等等,一切的一切对我们来说都特别宝贵,对于贡献高的用户,我们还将在未来送上神秘礼物和惊喜。
|
||||
|
||||
|
||||
|
||||
|
||||
|
1
assets/CNAME
Normal file
1
assets/CNAME
Normal file
@ -0,0 +1 @@
|
||||
rusty.rs
|
BIN
assets/banner.gif
Normal file
BIN
assets/banner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
assets/banner1.png
Normal file
BIN
assets/banner1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
1
assets/bigPicture.js
Normal file
1
assets/bigPicture.js
Normal file
File diff suppressed because one or more lines are too long
158
assets/custom1.js
Normal file
158
assets/custom1.js
Normal file
@ -0,0 +1,158 @@
|
||||
var initAll = function () {
|
||||
var path = window.location.pathname;
|
||||
if (path.endsWith("/print.html")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var images = document.querySelectorAll("main img")
|
||||
Array.prototype.forEach.call(images, function (img) {
|
||||
img.addEventListener("click", function () {
|
||||
BigPicture({
|
||||
el: img,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Un-active everything when you click it
|
||||
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
|
||||
el.addEventHandler("click", function () {
|
||||
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
el.classList.add("active");
|
||||
});
|
||||
});
|
||||
|
||||
var updateFunction = function () {
|
||||
var id = null;
|
||||
var elements = document.getElementsByClassName("header");
|
||||
Array.prototype.forEach.call(elements, function (el) {
|
||||
if (window.pageYOffset >= el.offsetTop) {
|
||||
id = el;
|
||||
}
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
|
||||
el.classList.remove("active");
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
if (id.href.localeCompare(el.href) == 0) {
|
||||
el.classList.add("active");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var pagetoc = document.getElementsByClassName("pagetoc")[0];
|
||||
var elements = document.getElementsByClassName("header");
|
||||
Array.prototype.forEach.call(elements, function (el) {
|
||||
var link = document.createElement("a");
|
||||
|
||||
// Indent shows hierarchy
|
||||
var indent = "";
|
||||
switch (el.parentElement.tagName) {
|
||||
case "H1":
|
||||
return;
|
||||
case "H3":
|
||||
indent = "20px";
|
||||
break;
|
||||
case "H4":
|
||||
indent = "40px";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
link.appendChild(document.createTextNode(el.text));
|
||||
link.style.paddingLeft = indent;
|
||||
link.href = el.href;
|
||||
pagetoc.appendChild(link);
|
||||
});
|
||||
updateFunction.call();
|
||||
|
||||
// Handle active elements on scroll
|
||||
window.addEventListener("scroll", updateFunction);
|
||||
|
||||
document.getElementById("theme-list").addEventListener("click", function (e) {
|
||||
var iframe = document.querySelector('.giscus-frame');
|
||||
if (!iframe) return;
|
||||
var theme;
|
||||
if (e.target.className === "theme") {
|
||||
theme = e.target.id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// 若当前 mdbook 主题不是 Light 或 Rust ,则将 giscuz 主题设置为 transparent_dark
|
||||
var giscusTheme = "light"
|
||||
if (theme != "light" && theme != "rust") {
|
||||
giscusTheme = "transparent_dark";
|
||||
}
|
||||
|
||||
var msg = {
|
||||
setConfig: {
|
||||
theme: giscusTheme
|
||||
}
|
||||
};
|
||||
iframe.contentWindow.postMessage({ giscus: msg }, 'https://giscus.app');
|
||||
});
|
||||
|
||||
pagePath = pagePath.replace("index.md", "");
|
||||
pagePath = pagePath.replace(".md", "");
|
||||
if (pagePath.length > 0) {
|
||||
if (pagePath.charAt(pagePath.length-1) == "/"){
|
||||
pagePath = pagePath.substring(0, pagePath.length-1)
|
||||
}
|
||||
}else {
|
||||
pagePath = "index"
|
||||
}
|
||||
|
||||
// add vistors count
|
||||
var ele = document.createElement("div");
|
||||
ele.setAttribute("align","center");
|
||||
var count = document.createElement("img")
|
||||
count.setAttribute("src", "https://visitor-badge.glitch.me/badge?page_id=" + path);
|
||||
ele.appendChild(count);
|
||||
var divider =document.createElement("hr")
|
||||
|
||||
document.getElementById("giscus-container").appendChild(ele);
|
||||
document.getElementById("giscus-container").appendChild(divider);
|
||||
|
||||
// 选取浏览器默认使用的语言
|
||||
const lang = navigator.language || navigator.userLanguage
|
||||
|
||||
// 若当前 mdbook 主题为 Light 或 Rust ,则将 giscuz 主题设置为 light
|
||||
var theme = "transparent_dark";
|
||||
const themeClass = document.getElementsByTagName("html")[0].className;
|
||||
if (themeClass.indexOf("light") != -1 || themeClass.indexOf("rust") != -1) {
|
||||
theme = "light"
|
||||
}
|
||||
|
||||
var script = document.createElement("script")
|
||||
script.type = "text/javascript";
|
||||
script.src = "https://giscus.app/client.js";
|
||||
script.async = true;
|
||||
script.crossOrigin = "anonymous";
|
||||
script.setAttribute("data-repo", "studyrs/rusty-book");
|
||||
script.setAttribute("data-repo-id", "R_kgDOGmKA_Q");
|
||||
script.setAttribute("data-category", "giscus");
|
||||
script.setAttribute("data-category-id", "DIC_kwDOHH0skM4COa8c");
|
||||
script.setAttribute("data-mapping", "specific");
|
||||
script.setAttribute("data-term", pagePath);
|
||||
script.setAttribute("data-reactions-enabled", "1");
|
||||
script.setAttribute("data-emit-metadata", "0");
|
||||
script.setAttribute("data-input-position", "top");
|
||||
script.setAttribute("data-theme", theme);
|
||||
script.setAttribute("data-lang", lang);
|
||||
// 预先加载评论会更好,这样用户读到那边时,评论就加载好了
|
||||
// script.setAttribute("data-loading", "lazy");
|
||||
document.getElementById("giscus-container").appendChild(script);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
window.addEventListener('load', initAll);
|
24
book.toml
Normal file
24
book.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[book]
|
||||
authors = ["sunface"]
|
||||
language = "zh-CN"
|
||||
title = "Rusty Book(锈书)"
|
||||
src = "src"
|
||||
|
||||
[output.html]
|
||||
no-section-label = true
|
||||
additional-css = ["theme/style1.css"]
|
||||
additional-js = ["assets/custom1.js", "assets/bigPicture.js"]
|
||||
git-repository-url = "https://github.com/studyrs/rusty-book"
|
||||
edit-url-template = "https://github.com/studyrs/rusty-book/edit/main/{path}"
|
||||
|
||||
[output.html.playground]
|
||||
editable = true
|
||||
copy-js = true
|
||||
# line-numbers = true
|
||||
|
||||
[output.html.fold]
|
||||
enable = true
|
||||
level = 1
|
||||
|
||||
[rust]
|
||||
edition = "2021" #在线运行用2021版本的
|
20
deploy
Executable file
20
deploy
Executable file
@ -0,0 +1,20 @@
|
||||
## this script deploys the static website of course.rs to github pages
|
||||
|
||||
## build static website for book
|
||||
mdbook build
|
||||
## copy CNAME info to book dir
|
||||
cp ./assets/CNAME ./book/
|
||||
|
||||
|
||||
## init git repo
|
||||
cd book
|
||||
git init
|
||||
git config user.name "sunface"
|
||||
git config user.email "cto@188.com"
|
||||
git add .
|
||||
git commit -m 'deploy'
|
||||
git branch -M gh-pages
|
||||
git remote add origin https://github.com/rustlang-cn/new-rusty-book
|
||||
|
||||
## push to github pages
|
||||
git push -u -f origin gh-pages
|
89
src/SUMMARY.md
Normal file
89
src/SUMMARY.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Summary
|
||||
|
||||
[Rusty Book](about.md)
|
||||
|
||||
<!-- [Awesome 目录索引](index-awesome.md)
|
||||
[Cookbook 目录索引](index-cookbook.md) -->
|
||||
|
||||
|
||||
# Awesome
|
||||
---
|
||||
- [日常开发常用库](awesome-daily-dev.md)
|
||||
- [Rust 明星项目](awesome-superstar.md)
|
||||
- [使用 Rust 增强 JS](awesome-empowering-js.md)
|
||||
- [Rust开发的游戏](awesome-games.md)
|
||||
- [游戏引擎](awesome-gamedev.md)
|
||||
|
||||
|
||||
# Awesome + Cookbook
|
||||
---
|
||||
- [实用算法](algos/awesome.md)
|
||||
- [数据结构]()
|
||||
- [位字段](algos/datastructures/bitfield.md)
|
||||
- [生成随机值](algos/randomness.md)
|
||||
- [Vec 排序](algos/sorting.md)
|
||||
- [压缩算法]()
|
||||
- [使用.tar包](algos/compression/tar.md)
|
||||
- [密码学]()
|
||||
- [哈希](algos/cryptography/hashing.md)
|
||||
- [加密](algos/cryptography/encryption.md)
|
||||
- [数学计算]()
|
||||
- [线性代数](algos/math/linear-algebra.md)
|
||||
- [三角函数](algos/math/trigonometry.md)
|
||||
- [复数](algos/math/complex.md)
|
||||
- [统计学](algos/math/statistics.md)
|
||||
- [杂项](algos/math/misc.md)
|
||||
|
||||
- [命令行](cmd/awesome.md)
|
||||
- [参数解析](cmd/parsing.md)
|
||||
- [终端输出格式化](cmd/ansi.md)
|
||||
|
||||
- [操作系统](os/awesome.md)
|
||||
- [处理器](os/processor.md)
|
||||
- [调用系统命令](os/command.md)
|
||||
|
||||
- [并发和并行]()
|
||||
- [多线程](cocurrency/threads.md)
|
||||
- [使用rayon并行处理数据](cocurrency/parallel.md)
|
||||
|
||||
- [数据库]()
|
||||
- [SQLite](database/sqlite.md)
|
||||
- [Postgres](database/postgres.md)
|
||||
|
||||
|
||||
- [日期和时间]()
|
||||
- [时间计算和转换](datetime/duration.md)
|
||||
- [解析和显示](datetime/parsing.md)
|
||||
|
||||
|
||||
- [开发者工具]()
|
||||
- [日志](devtools/log.md)
|
||||
- [配置日志](devtools/config-log.md)
|
||||
- [版本号](devtools/version.md)
|
||||
- [构建时工具](devtools/build-tools.md)
|
||||
|
||||
|
||||
- [编解码]()
|
||||
- [字符编码](encoding/strings.md)
|
||||
- [CSV](encoding/csv.md)
|
||||
- [结构化数据](encoding/structured.md)
|
||||
|
||||
|
||||
- [错误处理]()
|
||||
|
||||
- [文件操作]()
|
||||
- [文件读写](files/read-write.md)
|
||||
- [目录访问](files/dir.md)
|
||||
|
||||
- [内存管理]()
|
||||
- [全局变量](memory/global-vars.md)
|
||||
|
||||
- [网络协议]()
|
||||
- [TCP/IP](protocols/tcpip.md)
|
||||
|
||||
- [文本处理]()
|
||||
- [正则表达式](text/regex.md)
|
||||
- [字符串解析](text/string.md)
|
||||
|
||||
- [Web编程]()
|
||||
- [提取网络链接( 爬虫 )](web/scraping.md)
|
41
src/about.md
Normal file
41
src/about.md
Normal file
@ -0,0 +1,41 @@
|
||||
<img width="100%" src="https://github.com/studyrs/rusty-book/blob/main/assets/banner.gif?raw=true" />
|
||||
|
||||
每个同学可能都遇到过以下疑惑:
|
||||
|
||||
- 学完 Rust 后,还做了些题,接下可以做些什么?
|
||||
- 需要找一个依赖,但是去哪里找?哪些比较好用?哪些有坑?愁啊
|
||||
- 要访问一个文件,哎,但记不住代码,要不百度或谷歌一下吧,最后发现结果往往不尽如人意
|
||||
|
||||
而 Rusty Book 就是帮助大家解决这些问题的。
|
||||
|
||||
在 Rust 元宇宙,夸奖别人的最高境界就是 `rusty`: 今天你"锈"了吗? 你的 Rust 代码好锈啊!
|
||||
|
||||
而本书,就是精选了各种开源库和代码片段,帮助大家打造优"锈"的 Rust 项目。
|
||||
|
||||
## 以往的锈
|
||||
以往,想要锈起来,你需要做到以下两步:
|
||||
|
||||
1. 为项目挑选 Awesome 依赖库
|
||||
但是目前已有的 awesome-rust项目有非常大的问题:里面鱼龙混杂,因为它的目的是列出所有项目,但对用户而言,更想看到的是可以在生产中使用的、稳定更新的优秀项目。
|
||||
|
||||
2. 在 Cookbook 中查询实用的代码片段,直接复制到项目中
|
||||
对于开发者而言,Cookbook 非常实用,几乎每一门编程语言都是如此。原因无他:聪明的开发者大部分时间不是在复制粘贴就是在复制粘贴的路上。而 CookBook 恰恰为各种实用场景提供了可供直接复制粘贴的代码,例如网络协议、数据库和文件操作、随机数生成、命令行解析等。
|
||||
|
||||
但目前的 Rust Cookbook 更新非常不活跃,里面缺少了大量实用库,还有一些过时的老库。
|
||||
|
||||
## 现在的锈
|
||||
鉴于以上痛点,我们决定打造一本真正的锈书:一本足够"锈"但是又不会锈的书。
|
||||
|
||||
这本书其实就是 Awesome Rust + Rust Cookbook 的结合体,但是我们不是简单粗暴的对内容进行了合并,而是从深层次将两者进行了融合,希望大家能喜欢。
|
||||
|
||||
## 这本书的读者
|
||||
本书适合所有程度的 Rust 开发者使用:
|
||||
|
||||
- 新手用来了解 Rust 的常用库和常用代码片段
|
||||
- 老手在写代码时,可以直接用来复制粘贴,大幅提升工作效率
|
||||
|
||||
毕竟咱不是在面试造飞机,谁脑袋中能记住文件操作的各种细节,对不?
|
||||
|
||||
|
||||
|
||||
|
37
src/algos/awesome.md
Normal file
37
src/algos/awesome.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Awesome 算法
|
||||
|
||||
## 通用算法
|
||||
---
|
||||
### rust-algorithms
|
||||
|
||||
[rust-algorithms](https://github.com/EbTech/rust-algorithms)收集了一些经典的算法和数据结构,更强调算法实现的美观性,因此该库更适用于教学目的,请不要把它当成一个实用算法库在生产环境使用。
|
||||
|
||||
### TheAlgorithms/Rust
|
||||
[TheAlgorithms/Rust](https://github.com/TheAlgorithms/Rust)项目所属的[组织](https://github.com/TheAlgorithms)使用各种语言实现了多种算法,但是仅适用于演示的目的。
|
||||
|
||||
## Leetcode
|
||||
---
|
||||
|
||||
### rustgym
|
||||
[rustgym](https://github.com/warycat/rustgym) 实现了相当多的 leetcode 和 Avent of Code 题解。
|
||||
|
||||
## 分布式算法
|
||||
---
|
||||
|
||||
### raft-rs
|
||||
|
||||
[raft-rs](https://github.com/tikv/raft-rs) 是由 Tikv 提供的 Raft 分布式算法实现。[Raft](https://raft.github.io)是一个强一致性的分布式算法,比 Paxos 协议更简单、更好理解
|
||||
|
||||
## 密码学
|
||||
---
|
||||
|
||||
### Rust Crypto
|
||||
|
||||
[Rust Crypto](https://github.com/RustCrypto)提供了一些常用的密码学算法实现,更新较为活跃。
|
||||
|
||||
## 专用算法
|
||||
---
|
||||
|
||||
### rust-bio
|
||||
|
||||
[rust-bio](https://github.com/rust-bio/rust-bio) 有常用的生物信息学所需的算法和数据结构。
|
77
src/algos/compression/tar.md
Normal file
77
src/algos/compression/tar.md
Normal file
@ -0,0 +1,77 @@
|
||||
# 使用tar包
|
||||
|
||||
## 解压 tar 包
|
||||
以下代码将解压缩( [GzDecoder](https://docs.rs/flate2/*/flate2/read/struct.GzDecoder.html) )当前目录中的 `archive.tar.gz` ,并将所有文件抽取出( [Archive::unpack](https://docs.rs/tar/*/tar/struct.Archive.html#method.unpack) )来后当入到当前目录中。
|
||||
|
||||
```rust,editable
|
||||
use std::fs::File;
|
||||
use flate2::read::GzDecoder;
|
||||
use tar::Archive;
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let path = "archive.tar.gz";
|
||||
|
||||
let tar_gz = File::open(path)?;
|
||||
let tar = GzDecoder::new(tar_gz);
|
||||
let mut archive = Archive::new(tar);
|
||||
archive.unpack(".")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## 将目录压缩成 tar 包
|
||||
以下代码将 `/var/log` 目录压缩成 `archive.tar.gz`:
|
||||
|
||||
- 创建一个 [File](https://doc.rust-lang.org/std/fs/struct.File.html) 文件,并使用 [GzEncoder](https://docs.rs/flate2/*/flate2/write/struct.GzEncoder.html) 和 [tar::Builder](https://docs.rs/tar/*/tar/struct.Builder.html) 对其进行包裹
|
||||
- 通过 [Builder::append_dir_all](https://docs.rs/tar/*/tar/struct.Builder.html#method.append_dir_all) 将 `/var/log` 目录下的所有内容添加到压缩文件中,该文件在 `backup/logs` 目录下。
|
||||
- [GzEncoder](https://docs.rs/flate2/*/flate2/write/struct.GzEncoder.html) 负责在写入压缩文件 `archive.tar.gz` 之前对数据进行压缩。
|
||||
|
||||
```rust,editable
|
||||
use std::fs::File;
|
||||
use flate2::Compression;
|
||||
use flate2::write::GzEncoder;
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let tar_gz = File::create("archive.tar.gz")?;
|
||||
let enc = GzEncoder::new(tar_gz, Compression::default());
|
||||
let mut tar = tar::Builder::new(enc);
|
||||
tar.append_dir_all("backup/logs", "/var/log")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## 解压的同时删除指定的文件前缀
|
||||
遍历目录中的文件 [Archive::entries](https://docs.rs/tar/*/tar/struct.Archive.html#method.entries),若解压前的文件名包含 `bundle/logs` 前缀,需要将前缀从文件名移除( [Path::strip_prefix](https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix) )后,再解压。
|
||||
|
||||
|
||||
|
||||
```rust,editable
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use flate2::read::GzDecoder;
|
||||
use tar::Archive;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let file = File::open("archive.tar.gz")?;
|
||||
let mut archive = Archive::new(GzDecoder::new(file));
|
||||
let prefix = "bundle/logs";
|
||||
|
||||
println!("Extracted the following files:");
|
||||
archive
|
||||
.entries()? // 获取压缩档案中的文件条目列表
|
||||
.filter_map(|e| e.ok())
|
||||
// 对每个文件条目进行 map 处理
|
||||
.map(|mut entry| -> Result<PathBuf> {
|
||||
// 将文件路径名中的前缀移除,获取一个新的路径名
|
||||
let path = entry.path()?.strip_prefix(prefix)?.to_owned();
|
||||
// 将内容解压到新的路径名中
|
||||
entry.unpack(&path)?;
|
||||
Ok(path)
|
||||
})
|
||||
.filter_map(|e| e.ok())
|
||||
.for_each(|x| println!("> {}", x.display()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
57
src/algos/cryptography/encryption.md
Normal file
57
src/algos/cryptography/encryption.md
Normal file
@ -0,0 +1,57 @@
|
||||
# 加密
|
||||
|
||||
### 使用 PBKDF2 对密码进行哈希和加盐( salt )
|
||||
[ring::pbkdf2](https://briansmith.org/rustdoc/ring/pbkdf2/index.html) 可以对一个加盐密码进行哈希。
|
||||
|
||||
```rust,editable
|
||||
|
||||
use data_encoding::HEXUPPER;
|
||||
use ring::error::Unspecified;
|
||||
use ring::rand::SecureRandom;
|
||||
use ring::{digest, pbkdf2, rand};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
fn main() -> Result<(), Unspecified> {
|
||||
const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
|
||||
let n_iter = NonZeroU32::new(100_000).unwrap();
|
||||
let rng = rand::SystemRandom::new();
|
||||
|
||||
let mut salt = [0u8; CREDENTIAL_LEN];
|
||||
// 生成 salt: 将安全生成的随机数填入到字节数组中
|
||||
rng.fill(&mut salt)?;
|
||||
|
||||
let password = "Guess Me If You Can!";
|
||||
let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN];
|
||||
pbkdf2::derive(
|
||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
||||
n_iter,
|
||||
&salt,
|
||||
password.as_bytes(),
|
||||
&mut pbkdf2_hash,
|
||||
);
|
||||
println!("Salt: {}", HEXUPPER.encode(&salt));
|
||||
println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash));
|
||||
|
||||
// `verify` 检查哈希是否正确
|
||||
let should_succeed = pbkdf2::verify(
|
||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
||||
n_iter,
|
||||
&salt,
|
||||
password.as_bytes(),
|
||||
&pbkdf2_hash,
|
||||
);
|
||||
let wrong_password = "Definitely not the correct password";
|
||||
let should_fail = pbkdf2::verify(
|
||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
||||
n_iter,
|
||||
&salt,
|
||||
wrong_password.as_bytes(),
|
||||
&pbkdf2_hash,
|
||||
);
|
||||
|
||||
assert!(should_succeed.is_ok());
|
||||
assert!(!should_fail.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
71
src/algos/cryptography/hashing.md
Normal file
71
src/algos/cryptography/hashing.md
Normal file
@ -0,0 +1,71 @@
|
||||
# 哈希
|
||||
|
||||
### 计算文件的 SHA-256 摘要
|
||||
写入一些数据到文件中,然后使用 [digest::Context](https://briansmith.org/rustdoc/ring/digest/struct.Context.html) 来计算文件内容的 SHA-256 摘要 [digest::Digest](https://briansmith.org/rustdoc/ring/digest/struct.Digest.html)。
|
||||
|
||||
```rust,editable
|
||||
# use error_chain::error_chain;
|
||||
use data_encoding::HEXUPPER;
|
||||
use ring::digest::{Context, Digest, SHA256};
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read, Write};
|
||||
|
||||
# error_chain! {
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# Decode(data_encoding::DecodeError);
|
||||
# }
|
||||
# }
|
||||
|
||||
fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> {
|
||||
let mut context = Context::new(&SHA256);
|
||||
let mut buffer = [0; 1024];
|
||||
|
||||
loop {
|
||||
let count = reader.read(&mut buffer)?;
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
context.update(&buffer[..count]);
|
||||
}
|
||||
|
||||
Ok(context.finish())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let path = "file.txt";
|
||||
|
||||
let mut output = File::create(path)?;
|
||||
write!(output, "We will generate a digest of this text")?;
|
||||
|
||||
let input = File::open(path)?;
|
||||
let reader = BufReader::new(input);
|
||||
let digest = sha256_digest(reader)?;
|
||||
|
||||
println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 HMAC 摘要来签名和验证消息
|
||||
使用 [ring::hmac](https://briansmith.org/rustdoc/ring/hmac/) 创建一个字符串签名并检查该签名的正确性。
|
||||
|
||||
```rust,editable
|
||||
use ring::{hmac, rand};
|
||||
use ring::rand::SecureRandom;
|
||||
use ring::error::Unspecified;
|
||||
|
||||
fn main() -> Result<(), Unspecified> {
|
||||
let mut key_value = [0u8; 48];
|
||||
let rng = rand::SystemRandom::new();
|
||||
rng.fill(&mut key_value)?;
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value);
|
||||
|
||||
let message = "Legitimate and important message.";
|
||||
let signature = hmac::sign(&key, message.as_bytes());
|
||||
hmac::verify(&key, message.as_bytes(), signature.as_ref())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
2
src/algos/datastructures/awesome.md
Normal file
2
src/algos/datastructures/awesome.md
Normal file
@ -0,0 +1,2 @@
|
||||
# 数据结构
|
||||
|
47
src/algos/datastructures/bitfield.md
Normal file
47
src/algos/datastructures/bitfield.md
Normal file
@ -0,0 +1,47 @@
|
||||
# 位字段
|
||||
|
||||
### 定义和操作位字段
|
||||
使用 [`bitflags!`](https://docs.rs/bitflags/1.3.2/bitflags/macro.bitflags.html) 宏可以帮助我们创建安全的位字段类型 `MyFlags`,然后为其实现基本的 `clear` 操作。以下代码展示了基本的位操作和格式化:
|
||||
```rust,editable
|
||||
use bitflags::bitflags;
|
||||
use std::fmt;
|
||||
|
||||
bitflags! {
|
||||
struct MyFlags: u32 {
|
||||
const FLAG_A = 0b00000001;
|
||||
const FLAG_B = 0b00000010;
|
||||
const FLAG_C = 0b00000100;
|
||||
const FLAG_ABC = Self::FLAG_A.bits
|
||||
| Self::FLAG_B.bits
|
||||
| Self::FLAG_C.bits;
|
||||
}
|
||||
}
|
||||
|
||||
impl MyFlags {
|
||||
pub fn clear(&mut self) -> &mut MyFlags {
|
||||
self.bits = 0;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MyFlags {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:032b}", self.bits)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C;
|
||||
let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C;
|
||||
assert_eq!((e1 | e2), MyFlags::FLAG_ABC);
|
||||
assert_eq!((e1 & e2), MyFlags::FLAG_C);
|
||||
assert_eq!((e1 - e2), MyFlags::FLAG_A);
|
||||
assert_eq!(!e2, MyFlags::FLAG_A);
|
||||
|
||||
let mut flags = MyFlags::FLAG_ABC;
|
||||
assert_eq!(format!("{}", flags), "00000000000000000000000000000111");
|
||||
assert_eq!(format!("{}", flags.clear()), "00000000000000000000000000000000");
|
||||
assert_eq!(format!("{:?}", MyFlags::FLAG_B), "FLAG_B");
|
||||
assert_eq!(format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "FLAG_A | FLAG_B");
|
||||
}
|
||||
```
|
43
src/algos/math/complex.md
Normal file
43
src/algos/math/complex.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 复数
|
||||
|
||||
### 创建复数
|
||||
[num::complex::Complex](https://autumnai.github.io/cuticula/num/complex/struct.Complex.html) 可以帮助我们创建复数,其中实部和虚部必须是一样的类型。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let complex_integer = num::complex::Complex::new(10, 20);
|
||||
let complex_float = num::complex::Complex::new(10.1, 20.1);
|
||||
|
||||
println!("Complex integer: {}", complex_integer);
|
||||
println!("Complex float: {}", complex_float);
|
||||
}
|
||||
```
|
||||
|
||||
### 复数相加
|
||||
复数计算和 Rust 基本类型的计算并无区别。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let complex_num1 = num::complex::Complex::new(10.0, 20.0); // Must use floats
|
||||
let complex_num2 = num::complex::Complex::new(3.1, -4.2);
|
||||
|
||||
let sum = complex_num1 + complex_num2;
|
||||
|
||||
println!("Sum: {}", sum);
|
||||
}
|
||||
```
|
||||
|
||||
### 数学函数
|
||||
在 [num::complex::Complex](https://autumnai.github.io/cuticula/num/complex/struct.Complex.html) 中定义了一些内置的数学函数,可用于对复数进行数学运算。
|
||||
|
||||
```rust,editable
|
||||
use std::f64::consts::PI;
|
||||
use num::complex::Complex;
|
||||
|
||||
fn main() {
|
||||
let x = Complex::new(0.0, 2.0*PI);
|
||||
|
||||
println!("e^(2i * pi) = {}", x.exp()); // =~1
|
||||
}
|
||||
```
|
||||
|
176
src/algos/math/linear-algebra.md
Normal file
176
src/algos/math/linear-algebra.md
Normal file
@ -0,0 +1,176 @@
|
||||
# 线性代数
|
||||
|
||||
### 矩阵相加
|
||||
|
||||
使用 [ndarray::arr2](https://docs.rs/ndarray/*/ndarray/fn.arr2.html) 可以创建二阶矩阵,并计算它们的和。
|
||||
|
||||
```rust,editable
|
||||
use ndarray::arr2;
|
||||
|
||||
fn main() {
|
||||
let a = arr2(&[[1, 2, 3],
|
||||
[4, 5, 6]]);
|
||||
|
||||
let b = arr2(&[[6, 5, 4],
|
||||
[3, 2, 1]]);
|
||||
|
||||
// 借用 a 和 b,求和后生成新的矩阵 sum
|
||||
let sum = &a + &b;
|
||||
|
||||
println!("{}", a);
|
||||
println!("+");
|
||||
println!("{}", b);
|
||||
println!("=");
|
||||
println!("{}", sum);
|
||||
}
|
||||
```
|
||||
|
||||
### 矩阵相乘
|
||||
|
||||
[ndarray::ArrayBase::dot](https://docs.rs/ndarray/0.15.4/ndarray/struct.ArrayBase.html#method.dot-1) 可以用于计算矩阵乘法。
|
||||
|
||||
```rust,editable
|
||||
use ndarray::arr2;
|
||||
|
||||
fn main() {
|
||||
let a = arr2(&[[1, 2, 3],
|
||||
[4, 5, 6]]);
|
||||
|
||||
let b = arr2(&[[6, 3],
|
||||
[5, 2],
|
||||
[4, 1]]);
|
||||
|
||||
println!("{}", a.dot(&b));
|
||||
}
|
||||
```
|
||||
|
||||
### 标量、向量、矩阵相乘
|
||||
|
||||
在 `ndarry`中,1 阶数组根据上下文既可以作为行向量也可以作为列向量。如果对你来说,这个行或列的方向很重要,可以考虑使用一行或一列的 2 阶数组来表示。
|
||||
|
||||
在下面例子中,由于 1 阶数组处于乘号的右边位置,因此 `dot` 会把它当成列向量来处理。
|
||||
|
||||
```rust,editable
|
||||
use ndarray::{arr1, arr2, Array1};
|
||||
|
||||
fn main() {
|
||||
let scalar = 4;
|
||||
|
||||
let vector = arr1(&[1, 2, 3]);
|
||||
|
||||
let matrix = arr2(&[[4, 5, 6],
|
||||
[7, 8, 9]]);
|
||||
|
||||
let new_vector: Array1<_> = scalar * vector;
|
||||
println!("{}", new_vector);
|
||||
|
||||
let new_matrix = matrix.dot(&new_vector);
|
||||
println!("{}", new_matrix);
|
||||
}
|
||||
```
|
||||
|
||||
### 向量比较
|
||||
|
||||
浮点数通常是不精确的,因此比较浮点数不是一件简单的事。[approx](https://docs.rs/approx/*/approx/index.html) 提供的 [assert_abs_diff_eq!](https://docs.rs/approx/0.5.1/approx/macro.assert_abs_diff_eq.html) 宏提供了方便的按元素比较的方式。为了使用 `approx` ,你需要在 `ndarray` 的依赖中开启相应的 feature:例如,在 `Cargo.toml` 中修改 `ndarray` 的依赖引入为 `ndarray = { version = "0.13", features = ["approx"] }`。
|
||||
|
||||
```rust,editable
|
||||
use approx::assert_abs_diff_eq;
|
||||
use ndarray::Array;
|
||||
|
||||
fn main() {
|
||||
let a = Array::from(vec![1., 2., 3., 4., 5.]);
|
||||
let b = Array::from(vec![5., 4., 3., 2., 1.]);
|
||||
let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
|
||||
let mut d = Array::from(vec![5., 4., 3., 2., 1.]);
|
||||
|
||||
// 消耗 a 和 b 的所有权
|
||||
let z = a + b;
|
||||
// 借用 c 和 d
|
||||
let w = &c + &d;
|
||||
|
||||
assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));
|
||||
|
||||
println!("c = {}", c);
|
||||
c[0] = 10.;
|
||||
d[1] = 10.;
|
||||
|
||||
assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 向量范数( norm )
|
||||
|
||||
需要注意的是 `Array` 和 `ArrayView` 都是 `ArrayBase` 的别名。因此一个更通用的参数应该是 `&ArrayBase<S, Ix1> where S: Data`,特别是在你提供一个公共 API 给其它用户时,但由于咱们是内部使用,因此更精准的 `ArrayView1<f64>` 会更适合。
|
||||
|
||||
```rust,editable
|
||||
use ndarray::{array, Array1, ArrayView1};
|
||||
|
||||
fn l1_norm(x: ArrayView1<f64>) -> f64 {
|
||||
x.fold(0., |acc, elem| acc + elem.abs())
|
||||
}
|
||||
|
||||
fn l2_norm(x: ArrayView1<f64>) -> f64 {
|
||||
x.dot(&x).sqrt()
|
||||
}
|
||||
|
||||
fn normalize(mut x: Array1<f64>) -> Array1<f64> {
|
||||
let norm = l2_norm(x.view());
|
||||
x.mapv_inplace(|e| e/norm);
|
||||
x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = array![1., 2., 3., 4., 5.];
|
||||
println!("||x||_2 = {}", l2_norm(x.view()));
|
||||
println!("||x||_1 = {}", l1_norm(x.view()));
|
||||
println!("Normalizing x yields {:?}", normalize(x));
|
||||
}
|
||||
```
|
||||
|
||||
### 矩阵的逆变换
|
||||
例子中使用 [nalgebra::Matrix3](https://docs.rs/nalgebra/*/nalgebra/base/type.Matrix3.html) 创建一个 3x3 的矩阵,然后尝试对其进行逆变换,获取一个逆矩阵。
|
||||
|
||||
```rust,editable
|
||||
use nalgebra::Matrix3;
|
||||
|
||||
fn main() {
|
||||
let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
|
||||
println!("m1 = {}", m1);
|
||||
match m1.try_inverse() {
|
||||
Some(inv) => {
|
||||
println!("The inverse of m1 is: {}", inv);
|
||||
}
|
||||
None => {
|
||||
println!("m1 is not invertible!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 序列/反序列化一个矩阵
|
||||
|
||||
下面将展示如何将矩阵序列化为 JSON ,然后再反序列化为原矩阵。
|
||||
|
||||
```rust,editable
|
||||
extern crate nalgebra;
|
||||
extern crate serde_json;
|
||||
|
||||
use nalgebra::DMatrix;
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let row_slice: Vec<i32> = (1..5001).collect();
|
||||
let matrix = DMatrix::from_row_slice(50, 100, &row_slice);
|
||||
|
||||
// 序列化矩阵
|
||||
let serialized_matrix = serde_json::to_string(&matrix)?;
|
||||
|
||||
// 反序列化
|
||||
let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?;
|
||||
|
||||
// 验证反序列化后的矩阵跟原始矩阵相等
|
||||
assert!(deserialized_matrix == matrix);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
24
src/algos/math/misc.md
Normal file
24
src/algos/math/misc.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 杂项
|
||||
|
||||
### 大整数 Big int
|
||||
使用 [BitInt](https://docs.rs/num/0.2.0/num/struct.BigInt.html) 可以对超过 128bit 的整数进行计算。
|
||||
|
||||
```rust,editable
|
||||
use num::bigint::{BigInt, ToBigInt};
|
||||
|
||||
fn factorial(x: i32) -> BigInt {
|
||||
if let Some(mut factorial) = 1.to_bigint() {
|
||||
for i in 1..=x {
|
||||
factorial = factorial * i;
|
||||
}
|
||||
factorial
|
||||
}
|
||||
else {
|
||||
panic!("Failed to calculate factorial!");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{}! equals {}", 100, factorial(100));
|
||||
}
|
||||
```
|
176
src/algos/math/statistics.md
Normal file
176
src/algos/math/statistics.md
Normal file
@ -0,0 +1,176 @@
|
||||
# 统计
|
||||
|
||||
### 测量中心趋势
|
||||
|
||||
下面的一些例子为 Rust 数组中的数据计算它们的中心趋势。
|
||||
|
||||
#### 平均值
|
||||
首先计算的是平均值。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
|
||||
|
||||
let sum = data.iter().sum::<i32>() as f32;
|
||||
let count = data.len();
|
||||
|
||||
let mean = match count {
|
||||
positive if positive > 0 => Some(sum / count as f32),
|
||||
_ => None
|
||||
};
|
||||
|
||||
println!("Mean of the data is {:?}", mean);
|
||||
}
|
||||
```
|
||||
|
||||
#### 中位数
|
||||
下面使用快速选择算法来计算中位数。该算法只会对可能包含中位数的数据分区进行排序,从而避免了对所有数据进行全排序。
|
||||
|
||||
```rust,editable
|
||||
use std::cmp::Ordering;
|
||||
|
||||
fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> {
|
||||
match data.len() {
|
||||
0 => None,
|
||||
_ => {
|
||||
let (pivot_slice, tail) = data.split_at(1);
|
||||
let pivot = pivot_slice[0];
|
||||
let (left, right) = tail.iter()
|
||||
.fold((vec![], vec![]), |mut splits, next| {
|
||||
{
|
||||
let (ref mut left, ref mut right) = &mut splits;
|
||||
if next < &pivot {
|
||||
left.push(*next);
|
||||
} else {
|
||||
right.push(*next);
|
||||
}
|
||||
}
|
||||
splits
|
||||
});
|
||||
|
||||
Some((left, pivot, right))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select(data: &[i32], k: usize) -> Option<i32> {
|
||||
let part = partition(data);
|
||||
|
||||
match part {
|
||||
None => None,
|
||||
Some((left, pivot, right)) => {
|
||||
let pivot_idx = left.len();
|
||||
|
||||
match pivot_idx.cmp(&k) {
|
||||
Ordering::Equal => Some(pivot),
|
||||
Ordering::Greater => select(&left, k),
|
||||
Ordering::Less => select(&right, k - (pivot_idx + 1)),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn median(data: &[i32]) -> Option<f32> {
|
||||
let size = data.len();
|
||||
|
||||
match size {
|
||||
even if even % 2 == 0 => {
|
||||
let fst_med = select(data, (even / 2) - 1);
|
||||
let snd_med = select(data, even / 2);
|
||||
|
||||
match (fst_med, snd_med) {
|
||||
(Some(fst), Some(snd)) => Some((fst + snd) as f32 / 2.0),
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
odd => select(data, odd / 2).map(|x| x as f32)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
|
||||
|
||||
let part = partition(&data);
|
||||
println!("Partition is {:?}", part);
|
||||
|
||||
let sel = select(&data, 5);
|
||||
println!("Selection at ordered index {} is {:?}", 5, sel);
|
||||
|
||||
let med = median(&data);
|
||||
println!("Median is {:?}", med);
|
||||
}
|
||||
```
|
||||
|
||||
#### 众数( mode )
|
||||
下面使用了 `HashMap` 对不同数字出现的次数进行了分别统计。
|
||||
|
||||
```rust,editable
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
|
||||
|
||||
let frequencies = data.iter().fold(HashMap::new(), |mut freqs, value| {
|
||||
*freqs.entry(value).or_insert(0) += 1;
|
||||
freqs
|
||||
});
|
||||
|
||||
let mode = frequencies
|
||||
.into_iter()
|
||||
.max_by_key(|&(_, count)| count)
|
||||
.map(|(value, _)| *value);
|
||||
|
||||
println!("Mode of the data is {:?}", mode);
|
||||
}
|
||||
```
|
||||
|
||||
### 标准偏差
|
||||
|
||||
下面一起来看看该如何计算一组测量值的标准偏差和 z-score。
|
||||
|
||||
```rust,editable
|
||||
fn mean(data: &[i32]) -> Option<f32> {
|
||||
let sum = data.iter().sum::<i32>() as f32;
|
||||
let count = data.len();
|
||||
|
||||
match count {
|
||||
positive if positive > 0 => Some(sum / count as f32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn std_deviation(data: &[i32]) -> Option<f32> {
|
||||
match (mean(data), data.len()) {
|
||||
(Some(data_mean), count) if count > 0 => {
|
||||
let variance = data.iter().map(|value| {
|
||||
let diff = data_mean - (*value as f32);
|
||||
|
||||
diff * diff
|
||||
}).sum::<f32>() / count as f32;
|
||||
|
||||
Some(variance.sqrt())
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
|
||||
|
||||
let data_mean = mean(&data);
|
||||
println!("Mean is {:?}", data_mean);
|
||||
|
||||
let data_std_deviation = std_deviation(&data);
|
||||
println!("Standard deviation is {:?}", data_std_deviation);
|
||||
|
||||
let zscore = match (data_mean, data_std_deviation) {
|
||||
(Some(mean), Some(std_deviation)) => {
|
||||
let diff = data[4] as f32 - mean;
|
||||
|
||||
Some(diff / std_deviation)
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore);
|
||||
}
|
||||
```
|
59
src/algos/math/trigonometry.md
Normal file
59
src/algos/math/trigonometry.md
Normal file
@ -0,0 +1,59 @@
|
||||
# 三角函数
|
||||
|
||||
### 三角形边长计算
|
||||
计算角为 2 弧度、对边长度为 80 的直角三角形的斜边长度。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let angle: f64 = 2.0;
|
||||
let side_length = 80.0;
|
||||
|
||||
let hypotenuse = side_length / angle.sin();
|
||||
|
||||
println!("Hypotenuse: {}", hypotenuse);
|
||||
}
|
||||
```
|
||||
|
||||
### 验证 tan = sin / cos
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let x: f64 = 6.0;
|
||||
|
||||
let a = x.tan();
|
||||
let b = x.sin() / x.cos();
|
||||
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
```
|
||||
|
||||
### 地球上两点间的距离
|
||||
下面的代码使用 [Haversine 公式](https://blog.csdn.net/Hardict/article/details/105267473) 计算地球上两点之间的公里数。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let earth_radius_kilometer = 6371.0_f64;
|
||||
let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64);
|
||||
let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);
|
||||
|
||||
let paris_latitude = paris_latitude_degrees.to_radians();
|
||||
let london_latitude = london_latitude_degrees.to_radians();
|
||||
|
||||
let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians();
|
||||
let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians();
|
||||
|
||||
let central_angle_inner = (delta_latitude / 2.0).sin().powi(2)
|
||||
+ paris_latitude.cos() * london_latitude.cos() * (delta_longitude / 2.0).sin().powi(2);
|
||||
let central_angle = 2.0 * central_angle_inner.sqrt().asin();
|
||||
|
||||
let distance = earth_radius_kilometer * central_angle;
|
||||
|
||||
println!(
|
||||
"Distance between Paris and London on the surface of Earth is {:.1} kilometers",
|
||||
distance
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
155
src/algos/randomness.md
Normal file
155
src/algos/randomness.md
Normal file
@ -0,0 +1,155 @@
|
||||
# 生成随机值
|
||||
|
||||
### 生成随机数
|
||||
|
||||
使用 [rand::thread_rng](https://docs.rs/rand/*/rand/fn.thread_rng.html) 可以获取一个随机数生成器 [rand::Rng](https://docs.rs/rand/0.8.5/rand/trait.Rng.html) ,该生成器需要在每个线程都初始化一个。
|
||||
|
||||
整数的随机分布范围等于类型的取值范围,但是浮点数只分布在 `[0, 1)` 区间内。
|
||||
|
||||
```rust,editable
|
||||
use rand::Rng;
|
||||
|
||||
fn main() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let n1: u8 = rng.gen();
|
||||
let n2: u16 = rng.gen();
|
||||
println!("Random u8: {}", n1);
|
||||
println!("Random u16: {}", n2);
|
||||
println!("Random u32: {}", rng.gen::<u32>());
|
||||
println!("Random i32: {}", rng.gen::<i32>());
|
||||
println!("Random float: {}", rng.gen::<f64>());
|
||||
}
|
||||
```
|
||||
|
||||
### 指定范围生成随机数
|
||||
|
||||
使用 [Rng::gen_range](https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html) 生成 [0, 10) 区间内的随机数( 右开区间,不包括 `10` )。
|
||||
```rust,editable
|
||||
use rand::Rng;
|
||||
|
||||
fn main() {
|
||||
let mut rng = rand::thread_rng();
|
||||
println!("Integer: {}", rng.gen_range(0..10));
|
||||
println!("Float: {}", rng.gen_range(0.0..10.0));
|
||||
}
|
||||
```
|
||||
|
||||
[Uniform](https://docs.rs/rand/*/rand/distributions/uniform/struct.Uniform.html) 可以用于生成<ruby>均匀分布<rt>uniform distribution</rt></ruby>的随机数。当需要在同一个范围内重复生成随机数时,该方法虽然和之前的方法效果一样,但会更快一些。
|
||||
|
||||
```rust,editable
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
|
||||
fn main() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let die = Uniform::from(1..7);
|
||||
|
||||
loop {
|
||||
let throw = die.sample(&mut rng);
|
||||
println!("Roll the die: {}", throw);
|
||||
if throw == 6 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用指定分布来生成随机数
|
||||
|
||||
默认情况下,`rand` 包使用均匀分布来生成随机数,而 [rand_distr](https://docs.rs/rand_distr/*/rand_distr/index.html) 包提供了其它类型的分布方式。
|
||||
|
||||
首先,你需要获取想要使用的分布的实例,然后在 [rand::Rng](https://docs.rs/rand/*/rand/trait.Rng.html) 的帮助下使用 [Distribution::sample](https://docs.rs/rand/*/rand/distributions/trait.Distribution.html#tymethod.sample) 对该实例进行取样。
|
||||
|
||||
如果想要查询可用的分布列表,可以访问[这里](https://docs.rs/rand_distr/*/rand_distr/index.html),下面的示例中我们将使用 [Normal](https://docs.rs/rand_distr/0.4.3/rand_distr/struct.Normal.html) 分布:
|
||||
```rust,editable
|
||||
use rand_distr::{Distribution, Normal, NormalError};
|
||||
use rand::thread_rng;
|
||||
|
||||
fn main() -> Result<(), NormalError> {
|
||||
let mut rng = thread_rng();
|
||||
let normal = Normal::new(2.0, 3.0)?;
|
||||
let v = normal.sample(&mut rng);
|
||||
println!("{} is from a N(2, 9) distribution", v);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 在自定义类型中生成随机值
|
||||
|
||||
|
||||
使用 [Distribution](https://docs.rs/rand/*/rand/distributions/trait.Distribution.html) 特征包裹我们的自定义类型,并为 [Standard](https://docs.rs/rand/*/rand/distributions/struct.Standard.html) 实现该特征,可以为自定义类型的指定字段生成随机数。
|
||||
|
||||
|
||||
```rust,editable
|
||||
use rand::Rng;
|
||||
use rand::distributions::{Distribution, Standard};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
impl Distribution<Point> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
|
||||
let (rand_x, rand_y) = rng.gen();
|
||||
Point {
|
||||
x: rand_x,
|
||||
y: rand_y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// 生成一个随机的 Point
|
||||
let rand_point: Point = rng.gen();
|
||||
println!("Random Point: {:?}", rand_point);
|
||||
|
||||
// 通过类型暗示( hint )生成一个随机的元组
|
||||
let rand_tuple = rng.gen::<(i32, bool, f64)>();
|
||||
println!("Random tuple: {:?}", rand_tuple);
|
||||
}
|
||||
```
|
||||
|
||||
### 生成随机的字符串(A-Z, a-z, 0-9)
|
||||
通过 [Alphanumeric](https://docs.rs/rand/0.8.5/rand/distributions/struct.Alphanumeric.html) 采样来生成随机的 ASCII 字符串,包含从 `A-Z, a-z, 0-9` 的字符。
|
||||
|
||||
```rust,editble
|
||||
use rand::{thread_rng, Rng};
|
||||
use rand::distributions::Alphanumeric;
|
||||
|
||||
fn main() {
|
||||
let rand_string: String = thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(30)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
println!("{}", rand_string);
|
||||
}
|
||||
```
|
||||
|
||||
### 生成随机的字符串( 用户指定 ASCII 字符 )
|
||||
通过 [gen_string](https://docs.rs/rand/0.8.5/rand/trait.Rng.html#method.gen_range) 生成随机的 ASCII 字符串,包含用户指定的字符。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
use rand::Rng;
|
||||
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
|
||||
abcdefghijklmnopqrstuvwxyz\
|
||||
0123456789)(*&^%$#@!~";
|
||||
const PASSWORD_LEN: usize = 30;
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let password: String = (0..PASSWORD_LEN)
|
||||
.map(|_| {
|
||||
let idx = rng.gen_range(0..CHARSET.len());
|
||||
CHARSET[idx] as char
|
||||
})
|
||||
.collect();
|
||||
|
||||
println!("{:?}", password);
|
||||
}
|
||||
```
|
84
src/algos/sorting.md
Normal file
84
src/algos/sorting.md
Normal file
@ -0,0 +1,84 @@
|
||||
## Vector 排序
|
||||
|
||||
|
||||
### 对整数 Vector 排序
|
||||
|
||||
以下示例使用 [Vec::sort](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort) 来排序,如果大家希望获得更高的性能,可以使用 [Vec::sort_unstable](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_unstable),但是该方法无法保留相等元素的顺序。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut vec = vec![1, 5, 10, 2, 15];
|
||||
|
||||
vec.sort();
|
||||
|
||||
assert_eq!(vec, vec![1, 2, 5, 10, 15]);
|
||||
}
|
||||
```
|
||||
|
||||
### 对浮点数 Vector 排序
|
||||
|
||||
浮点数数组可以使用 [Vec::sort_by](https://doc.rust-lang.org/std/primitive.slice.html#method.sort_by) 和 [PartialOrd::partial_cmp](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html#tymethod.partial_cmp) 进行排序。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];
|
||||
|
||||
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]);
|
||||
}
|
||||
```
|
||||
|
||||
### 对结构体 Vector 排序
|
||||
|
||||
以下示例中的结构体 `Person` 将实现基于字段 `name` 和 `age` 的自然排序。为了让 `Person` 变为可排序的,我们需要为其派生 `Eq、PartialEq、Ord、PartialOrd` 特征,关于这几个特征的详情,请见[这里](https://course.rs/advance/confonding/eq.html)。
|
||||
|
||||
当然,还可以使用 [vec:sort_by](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_by) 方法配合一个自定义比较函数,只按照 `age` 的维度对 `Person` 数组排序。
|
||||
|
||||
```rust,editable
|
||||
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: u32
|
||||
}
|
||||
|
||||
impl Person {
|
||||
pub fn new(name: String, age: u32) -> Self {
|
||||
Person {
|
||||
name,
|
||||
age
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut people = vec![
|
||||
Person::new("Zoe".to_string(), 25),
|
||||
Person::new("Al".to_string(), 60),
|
||||
Person::new("John".to_string(), 1),
|
||||
];
|
||||
|
||||
// 通过派生后的自然顺序(Name and age)排序
|
||||
people.sort();
|
||||
|
||||
assert_eq!(
|
||||
people,
|
||||
vec![
|
||||
Person::new("Al".to_string(), 60),
|
||||
Person::new("John".to_string(), 1),
|
||||
Person::new("Zoe".to_string(), 25),
|
||||
]);
|
||||
|
||||
// 只通过 age 排序
|
||||
people.sort_by(|a, b| b.age.cmp(&a.age));
|
||||
|
||||
assert_eq!(
|
||||
people,
|
||||
vec![
|
||||
Person::new("Al".to_string(), 60),
|
||||
Person::new("Zoe".to_string(), 25),
|
||||
Person::new("John".to_string(), 1),
|
||||
]);
|
||||
|
||||
}
|
||||
```
|
166
src/awesome-daily-dev.md
Normal file
166
src/awesome-daily-dev.md
Normal file
@ -0,0 +1,166 @@
|
||||
# 日常开发常用库
|
||||
|
||||
### 目录索引
|
||||
- [多线程](#多线程)
|
||||
- [Web/HTTP](#webhttp), [SQL客户端](#SQL客户端), [NoSql客户端](#NoSql客户端), [网络通信协议](#网络通信协议), [异步网络编程](#异步网络编程)
|
||||
- [服务发现](#服务发现), [消息队列](#消息队列), [搜索引擎](#搜索引擎)
|
||||
- [编解码](#编解码), [Email](#Email), [常用正则模版](#常用正则模版)
|
||||
- [日志监控](#日志监控), [代码Debug](#代码Debug), [性能优化](#性能优化)
|
||||
|
||||
### Web/HTTP
|
||||
* HTTP客户端
|
||||
* [reqwest](https://github.com/seanmonstar/reqwest) 一个简单又强大的HTTP客户端,`reqwest`是目前使用最多的HTTP库
|
||||
|
||||
* Web框架
|
||||
* [axum](https://github.com/tokio-rs/axum) 基于Tokio和Hyper打造,模块化设计较好,目前口碑很好,值得使用Ergonomic and modular web framework built with Tokio, Tower, and Hyper
|
||||
* [Rocket](https://github.com/SergioBenitez/Rocket) 功能强大,API简单的Web框架,但是主要开发者目前因为个人原因无法进行后续开发,未来存在不确定性
|
||||
* [actix-web](https://github.com/actix/actix-web) 性能极高的Web框架,就是团队内部有些问题,未来存在一定的不确定性
|
||||
* 总体来说,上述三个web框架都有很深的用户基础,其实都可以选用,如果让我推荐,顺序如下: `axum` > `Rocket` > `actix-web`。 不过如果你不需要多么完善的web功能,只需要一个性能极高的http库,那么`actix-web`是非常好的选择,它的性能非常非常非常高!
|
||||
|
||||
### 日志监控
|
||||
* 日志
|
||||
[[crates.io](https://crates.io/keywords/log)] [[github](https://github.com/search?q=rust+log)]
|
||||
* [tokio-rs/tracing](https://github.com/tokio-rs/tracing) 强大的日志框架,同时还支持OpenTelemetry格式,无缝打通未来的监控
|
||||
* [rust-lang/log](https://github.com/rust-lang/log) 官方日志库,事实上的API标准, 但是三方库未必遵循
|
||||
* [estk/log4rs](https://github.com/estk/log4rs) 模仿JAVA `logback`和`log4j`实现的日志库, 可配置性较强
|
||||
* 在其它文章中,也许会推荐slog,但是我们不推荐,一个是因为近半年未更新,一个是`slog`自己也推荐使用`tracing`。
|
||||
* 监控
|
||||
* [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-rust) `OpenTelemetry`是现在非常火的可观测性解决方案,提供了协议、API、SDK等核心工具,用于收集监控数据,最后将这些metrics/logs/traces数据写入到`prometheus`, `jaeger`等监控平台中。最主要是,它后台很硬,后面有各大公司作为背书,未来非常看好!
|
||||
* [vectordotdev/vector](https://github.com/vectordotdev/vector) 一个性能很高的数据采集agent,采集本地的日志、监控等数据,发送到远程的kafka、jaeger等数据下沉端,它最大的优点就是能从多种数据源(包括Opentelemetry)收集数据,然后推送到多个数据处理或者存储等下沉端。
|
||||
|
||||
### SQL客户端
|
||||
* 性能对比
|
||||
* [metrics](https://github.com/diesel-rs/metrics) 该库对Rust现存的数据库连接服务进行性能测试,若大家有性能上的需求,值得一看
|
||||
|
||||
* 通用
|
||||
* [launchbadge/sqlx](https://github.com/launchbadge/sqlx) 异步实现、高性能、纯Rust代码的SQL库,支持`PostgreSQL`, `MySQL`, `SQLite`,和 `MSSQL`.
|
||||
|
||||
* ORM
|
||||
* [rbatis/rbatis](https://github.com/rbatis/rbatis) 国内团队开发的ORM,异步、性能高、简单易上手
|
||||
* [diesel-rs/diesel](https://github.com/diesel-rs/diesel) 安全、扩展性强的Rust ORM库,支持`Mysql`、`Postgre`、`SqlLite`
|
||||
|
||||
|
||||
* Mysql
|
||||
* [blackbeam/rust-mysql-simple](https://github.com/blackbeam/rust-mysql-simple) 纯Rust实现的Mysql驱动,提供连接池
|
||||
* [blackbeam/mysql_async](https://github.com/blackbeam/mysql_async) 基于Tokio实现的异步Mysql驱动
|
||||
* 上面两个都是一个团队出品,前者文档更全、star更多,建议使用前者
|
||||
|
||||
|
||||
* Postgre
|
||||
* [sfackler/rust-postgres](https://github.com/sfackler/rust-postgres) 纯Rust实现的Postgre客户端
|
||||
|
||||
* Sqlite
|
||||
* [rusqlite](https://github.com/rusqlite/rusqlite) 用于[Sqlite3](https://www.sqlite.org/index.html)的Rust客户端
|
||||
|
||||
### NoSql客户端
|
||||
|
||||
* Redis
|
||||
* [mitsuhiko/redis-rs](https://github.com/mitsuhiko/redis-rs) 虽然最近更新不太活跃,但是它依然是最好的redis客户端,说实话,我期待更好的,可能这也是Rust生态的未来可期之处吧
|
||||
|
||||
* Canssandra
|
||||
* [krojew/cdrs-tokio](https://github.com/krojew/cdrs-tokio) [[cdrs-tokio](https://crates.io/crates/cdrs-tokio)] 生产可用的Cassandra客户端,异步、纯Rust实现,就是个人项目 + star较少,未来不确定会不会不维护
|
||||
* [scylla-rust-driver](https://github.com/scylladb/scylla-rust-driver) ScyllaDB提供的官方库,支持cql协议,由于背靠大山,未来非常可期
|
||||
|
||||
|
||||
* MongoDB
|
||||
* [mongodb/mongo-rust-driver](https://github.com/mongodb/mongo-rust-driver) 官方MongoDB客户端,闭着眼睛选就对了
|
||||
|
||||
|
||||
### 分布式
|
||||
#### 服务发现
|
||||
- [luncj/etcd-rs](https://github.com/luncj/etcd-rs) 异步实现的Rust etcd客户端,优点是有一定的文档、作者较为活跃,意味着你提问题他可能会回答,不过,如果你不放心,还是考虑使用HTTP的方式访问ETCD
|
||||
|
||||
#### 消息队列
|
||||
* Kafka
|
||||
* [fede1024/rust-rdkafka](https://github.com/fede1024/rust-rdkafka) Rust Kafka客户端,基于C版本的Kafka库[librdkafka]实现,文档较全、功能较为全面
|
||||
* [kafka-rust/kafka-rust](https://github.com/kafka-rust/kafka-rust) 相比上一个库,它算是纯Rust实现,文档还行,支持Kafka0.8.2及以后的版本,但是对于部分0.9版本的特性还不支持。同时有一个问题:最初的作者不维护了,转给了现在的作者,但是感觉好像也不是很活跃
|
||||
* Nats
|
||||
* [nats-io/nats.rs](https://github.com/nats-io/nats.rs) Nats官方提供的客户端
|
||||
|
||||
### 网络、通信协议
|
||||
* Websocket
|
||||
* [snapview/tokio-tungstenite](https://github.com/snapview/tokio-tungstenite) 更适合Web应用使用的生产级Websocket库,它是异步非阻塞的,基于基于下下面的`tungstenite-rs`库和tokio实现
|
||||
* [rust-websocket](https://github.com/websockets-rs/rust-websocket) 老牌Websocket库,提供了客户端和服务器端实现,但是。。。很久没更新了
|
||||
* [snapview/tungstenite-rs](https://github.com/snapview/tungstenite-rs) 轻量级的Websocket流实现,该库更偏底层,例如,你可以用来构建其它网络库
|
||||
* gRPC
|
||||
* [hyperium/tonic](https://github.com/hyperium/tonic) 纯Rust实现的gRPC客户端和服务器端,支持async/await异步调用,文档和示例较为清晰
|
||||
* [tikv/grpc-rs](https://github.com/tikv/grpc-rs) 国产开源之光Tidb团队出品的gRPC框架, 基于C的代码实现, 就是最近好像不是很活跃
|
||||
* 其实这两个实现都很优秀,把`tonic`放在第一位,主要是因为它是纯Rust实现,同时社区也更为活跃,但是并不代表它比`tikv`的更好!
|
||||
* [tokio-rs/prost](https://github.com/tokio-rs/prost) 纯Rust实现的[Protocol Buffers](https://developers.google.com/protocol-buffers/)类库,Prost 支持从 proto2 和 proto3 文件生成简单、实用的代码。
|
||||
* QUIC
|
||||
* [cloudflare/quiche](https://github.com/cloudflare/quiche) 大名鼎鼎`cloudflare`提供的QUIC实现,据说在公司内部重度使用,有了大规模生产级别的验证,非常值得信任,同时该库还实现了HTTP/3
|
||||
* [quinn-rs/quinn](https://github.com/quinn-rs/quinn) 提供异步API调用,纯Rust实现,同时提供了几个有用的网络库
|
||||
* MQTT
|
||||
* [bytebeamio/rumqtt](https://github.com/bytebeamio/rumqtt) MQTT3.1.1/5协议库,同时实现了客户端与服务器端broker
|
||||
* [ntex-rs/ntex-mqtt](https://github.com/ntex-rs/ntex-mqtt) 客户端与服务端框架,支持MQTT3.1.1与5协议
|
||||
* [eclipse/paho.mqtt.rust](https://github.com/eclipse/paho.mqtt.rust) 老牌MQTT框架,对MQTT支持较全, 其它各语言的实现也有
|
||||
|
||||
### 异步网络编程
|
||||
|
||||
* [tokio-rs/tokio](https://github.com/tokio-rs/tokio) 最火的异步网络库,除了复杂上手难度高一些外,没有其它大的问题。同时tokio团队提供了多个非常优秀的Rust库,整个生态欣欣向荣,用户认可度很高
|
||||
* [async-std](https://async.rs/) 跟标准库API很像的异步网络库,相对简单易用,但是貌似开发有些停滞,还有就是功能上不够完善。但是对于普通用户来说,这个库非常值得一试,它在功能和简单易用上取得了很好的平衡
|
||||
* [actix](https://github.com/actix/actix) 基于Actor模型的异步网络库,但这个库的开发貌似已经停滞,他们团队一直在专注于`actix-web`的开发
|
||||
* [mio](https://github.com/tokio-rs/mio) 严格来说,MIO与之前三个不是同一个用途的,MIO = Meta IO,是一个底层IO库,往往用于构建其它网络库,当然如果你对应用网络性能有非常极限的要求, 可以考虑它,因为它的层次比较低,所带来的抽象负担小,所以性能损耗小
|
||||
* 如果你要开发生产级别的项目,我推荐使用`tokio`,稳定可靠,功能丰富,控制粒度细;自己的学习项目或者没有那么严肃的开源项目,我推荐`async-std`,简单好用,值得学习;当你确切知道需要Actor网络模型时,就用`actix`
|
||||
|
||||
|
||||
### 搜索引擎
|
||||
|
||||
* ElasticSearch客户端
|
||||
* [elastic/elasticsearch](https://github.com/elastic/elasticsearch-rs) 官方es客户端,目前第三方的基本都处于停滞状态,所以不管好坏,用呗
|
||||
|
||||
* Rust搜索引擎
|
||||
* [Tantivy](https://github.com/quickwit-inc/tantivy) Tantivy是Rust实现的本地搜索库,功能对标`lucene`,如果你不需要分布式,那么引入tantivy作为自己本地Rust服务的一个搜索,是相当不错的选择,该库作者一直很活跃,而且最近还创立了搜索引擎公司,感觉大有作为. 该库的优点在于纯Rust实现,性能高(lucene的2-3倍),资源占用低(对比java自然不是一个数量级),社区活跃。
|
||||
|
||||
* Rust搜索平台
|
||||
* [quickwit](https://github.com/quickwit-inc/quickwit) 对标ElasticSearch,一个通用目的的分布式搜索平台,目前还在起步阶段(0.2版本),未来非常可期,目前还不建议使用
|
||||
* [MeiliSearch](https://github.com/meilisearch/MeiliSearch) 虽然也是一个搜索平台,但是并不是通用目的的,`MeiliSearch`目标是为终端用户提供边输入边提示的即刻搜索功能,因此是一个轻量级搜索平台,不适用于数据量大时的搜索目的。总之,如果你需要在网页端或者APP为用户提供一个搜索条,然后支持输入容错、前缀搜索时,就可以使用它。
|
||||
*
|
||||
### 代码Debug
|
||||
* GDB
|
||||
* [gdbgui](https://github.com/cs01/gdbgui) 提供浏览器支持的gdb debug工具,支持C,C++,Rust和Go.
|
||||
* LLDB
|
||||
* [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) — 专门为VSCode设计的LLDB Debug扩展
|
||||
|
||||
### 性能优化
|
||||
* [bheisler/criterion.rs](https://github.com/bheisler/criterion.rs) 比官方提供的benchmark库更好,目前已经成为事实上标准的性能测试工具
|
||||
* [Bytehound](https://github.com/koute/bytehound) Linux下的内存分析工具,可以用来分析:内存泄漏、内存分配、调用栈追踪,甚至它还有一个浏览器UI! 懂的人都懂,性能测试工具的UI服务是多么稀缺和珍贵!
|
||||
* [llogiq/flame](https://github.com/llogiq/flame) 专为Rust打造的火焰图分析工具,可以告诉你程序在哪些代码上花费的时间过多,非常适合用于代码性能瓶颈的分析。与`perf`不同,`flame`库允许你自己定义想要测试的代码片段,只需要在代码前后加上相应的指令即可,非常好用
|
||||
* [sharkdp/hyperfine](https://github.com/sharkdp/hyperfine) 一个命令行benchmark工具,支持任意shell命令,支持缓存清除、预热、多次运行统计分析等,尽量保证结果的准确性
|
||||
|
||||
|
||||
### 多线程
|
||||
* 消息通道channel
|
||||
* [**crossbeam-channel**](https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-channel), 老牌强库,功能较全,性能较强,之前是独立的库,但是后面合并到了`crossbeam`主仓库中
|
||||
* [**flume**](https://github.com/zesterer/flume), 官方给出的性能数据要比crossbeam更好些,但是貌似最近没怎么更新
|
||||
* 并发原语(锁)
|
||||
* [parking_lot](https://crates.io/crates/parking_lot), 社区较为活跃,star较多,更新较为活跃
|
||||
* [spin](https://crates.io/crates/spin), 在多数场景中性能比`parking_lot`高一点,最近没怎么更新
|
||||
* 如果不是追求特别极致的性能,建议选择前者
|
||||
|
||||
### 编解码
|
||||
* [Serde](https://github.com/serde-rs/serde) 一个超高性能的通用序列化/反序列化框架,可以跟多种协议的库联合使用,实现统一编解码格式
|
||||
* CSV
|
||||
* [BurntSushi/rust-csv](https://github.com/BurntSushi/rust-csv) 高性能CSV读写库,支持[Serde](https://github.com/serde-rs/serde)
|
||||
* JSON
|
||||
* [serde-rs/json](https://github.com/serde-rs/json) 快到上天的JSON库,也是Rust事实上的标准JSON库,你也可以使用它的大哥[serde](https://github.com/serde-rs/serde),一个更通用的序列化/反序列化库
|
||||
* MsgPack
|
||||
* [3Hren/msgpack-rust](https://github.com/3Hren/msgpack-rust) 纯Rust实现的MessagePack编解码协议
|
||||
* ProtocolBuffers
|
||||
* [tokio-rs/prost](https://github.com/tokio-rs/prost) tokio出品,基本都属精品,此库也不例外,简单易用,文档详细
|
||||
* [stepancheg/rust-protobuf](https://github.com/stepancheg/rust-protobuf) 纯Rust实现
|
||||
* TOML
|
||||
* [alexcrichton/toml-rs](https://github.com/alexcrichton/toml-rs) TOML编码/解码,可以配合`serde`使用
|
||||
* XML
|
||||
* [tafia/quick-xml](https://github.com/tafia/quick-xml) 高性能XML库,可以配合`serde`使用,文档较为详细
|
||||
* YAML
|
||||
* [dtolnay/serde-yaml](https://github.com/dtolnay/serde-yaml) 使用`serde`编解码`YAML`格式的数据
|
||||
|
||||
### UI 开发框架
|
||||
* 跨平台
|
||||
* [DioxusLabs/Dioxus](https://github.com/DioxusLabs/dioxus) 跨平台 UI 开发框架,支持 `WASM`、`Desktop`、`TUI` 等应用开发,文档较为详细
|
||||
|
||||
### Email
|
||||
* [lettre/lettre](https://github.com/lettre/lettre) — Rust SMTP库
|
||||
|
||||
### 常用正则模版
|
291
src/awesome-empowering-js.md
Normal file
291
src/awesome-empowering-js.md
Normal file
@ -0,0 +1,291 @@
|
||||
# 使用Rust增强Javascript
|
||||
`Javascript`是目前全世界使用最广的语言(TIOBE排行榜比较迷,JS并没有排在第一位,我个人并不认同它的排名)。在过去这么多年中,围绕着`Javascript`已经建立了庞大的基础设施生态:例如使用`webpack`来将多个`js`文件打包成一个;使用`Babel`允许你用现代化的`js`语法编写兼容旧浏览器的代码;使用`Eslint`帮助开发找出代码中潜在的问题,类似`cargo clippy`。
|
||||
|
||||
以上的种种都在帮助`js`成为更好的语言和工具,它们是`Web`应用程序得以顺利、高效的开发和运行的基石。这些工具往往使用`Javascript`语言编写,一般来说,是没有问题的,但是在某些时候,可能会存在性能上的瓶颈或者安全隐患,因此阴差阳错、机缘巧合下,`Rust`成为了一个搅局者。
|
||||
|
||||
## Javascript基建库
|
||||
### deno
|
||||
首先出场的自然是咖位最重的之一,可以说正是因为`deno`和`swc`的横空出世,才让一堆观望的大神对于Rust实现`Javascript`基建有了更强的信心。
|
||||
|
||||
`deno`是`node`半逆转后的字序,从此可以看出`deno`是`Node.js`的替代,它的目标是为`Typescript/Javascript`提供一个更现代化、更安全、更强大 的运行时,同时内置了很多强大的工具,可以用于打包、编译成可执行文件、文档、测试、lint等。
|
||||
|
||||
值得一提的是,`deno`的不少工具都使用了`swc`进行建造,包括代码审查、格式化、文档生成等。
|
||||
|
||||
通过包引入的方式来对比下`deno`和`node`,大家可以自己品味下。
|
||||
|
||||
```js
|
||||
// node
|
||||
const koa = require("koa" );
|
||||
const logger = require("@adesso/logger")
|
||||
|
||||
// deno
|
||||
import { Application } from "https://deno.land/x/oak/mod.ts";
|
||||
import { Logger } from "https://adesso.de/lib/logger.ts"
|
||||
```
|
||||
|
||||
|
||||
### swc
|
||||
[`swc`](https://github.com/swc-project/swc)是`Typescript/Javascript`编译器,它可以用来编译、压缩和打包JS,同时支持使用插件进行扩展,例如做代码变换等。
|
||||
|
||||
`swc`目前正在被一些知名项目所使用,包括`Next.js`,`Parcel`和`Deno`,还有些著名的公司也在使用它,例如`Vercel`、字节跳动、腾讯等。
|
||||
|
||||
它的性能非常非常高,官方号称,在单线程下比`Babel`快20倍,在4核心下比`Babel`快70倍!
|
||||
|
||||
几个使用案例:
|
||||
|
||||
- [nextjs 12](http://nextjs.org/12), 通过使用`swc`获得了更好的扩展性、性能以及wasm的支持,其中性能方面提升了3倍刷新速度、5倍打包速度
|
||||
- [Parcel](https://parceljs.org/),通过使用`swc`改善了10倍的性能
|
||||
|
||||
<img alt="parcel screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/parcel.png?raw=true" class="center" />
|
||||
|
||||
|
||||
|
||||
官方还提供了一个在线运行的[demo](https://swc.rs/playground),功能齐全,可以试试。
|
||||
|
||||
<img alt="swc screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/swc.jpg?raw=true" class="center" />
|
||||
|
||||
### Rome
|
||||
[`Rome`](https://github.com/rome/tools)可以用来对`JavaScript`、`TypeScript`、`HTML`、`JSON`、`Markdown` 和 `CSS` 进行lint、编译、打包等功能,它的目标是替代`Babel`、`ESLint`、`webpack`、`Prettier`、`Jest`等。
|
||||
|
||||
一开始`Rome`是使用`Typescript`开发,目前正在用`Rust`进行重写。有趣的是: `Rome`的作者也是`Babel`的作者, 后者还是他在学习编译原理时做的。
|
||||
|
||||
|
||||
### fnm
|
||||
[`fnm`](https://github.com/Schniz/fnm)是一个简单易用、高性能的`Node`版本管理工具,还支持`.nvmrc`文件(`nvm`的`node`版本描述文件)
|
||||
|
||||
<img alt="fnm screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/fnm.svg?raw=true" class="center" />
|
||||
|
||||
### boa
|
||||
[`boa`](https://github.com/boa-dev/boa)是一个高性能的`javascript`词法分析器,解析器和解释器,目前还是实验性质的。
|
||||
|
||||
<img alt="boa screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/boa.gif?raw=true" class="center" />
|
||||
|
||||
### napi
|
||||
[`napi`](https://github.com/napi-rs/napi-rs)可以用于构建基于`Node API`的`Nodejs`插件,目前由`nextjs`主导开发。
|
||||
|
||||
### volt
|
||||
[`volt`](https://github.com/voltpkg/volt)是一个现代化的、高性能、安全可靠的`Javascript`包管理工具。目前该库正处于活跃开发阶段,只供学习使用。
|
||||
|
||||
<img alt="volt screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/volt.png?raw=true" class="center" />
|
||||
|
||||
### neon
|
||||
[`neon`](https://github.com/neon-bindings/neon)可以用于写安全、高性能的原生`Nodejs`模块。
|
||||
|
||||
```rust
|
||||
fn make_an_array(mut cx: FunctionContext) -> JsResult<JsArray> {
|
||||
// 创建一些值:
|
||||
let n = cx.number(9000);
|
||||
let s = cx.string("hello");
|
||||
let b = cx.boolean(true);
|
||||
|
||||
// 创建一个新数组:
|
||||
let array: Handle<JsArray> = cx.empty_array();
|
||||
|
||||
// 将值推入数组中
|
||||
array.set(&mut cx, 0, n)?;
|
||||
array.set(&mut cx, 1, s)?;
|
||||
array.set(&mut cx, 2, b)?;
|
||||
|
||||
// 返回数组
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
register_module!(mut cx, {
|
||||
cx.export_function("makeAnArray", make_an_array)
|
||||
})
|
||||
```
|
||||
|
||||
### resvg-js
|
||||
[resvg-js](https://github.com/yisibl/resvg-js)是一个高性能`svg`渲染库,使用Rust + Typescript实现。下面的图片通过`svg`实现(羞~~~):
|
||||
|
||||
<img alt="resvg screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/resvg.svg?raw=true" class="center" />
|
||||
|
||||
### deno_lint
|
||||
[deno_lint](https://github.com/denoland/deno_lint), 由`deno`团队出品的`lint`工具,支持`Javascript/Typescript`,支持`Deno`也支持`Node`。
|
||||
|
||||
优点之一就是极致的快:
|
||||
```shell
|
||||
[
|
||||
{
|
||||
"name": "deno_lint",
|
||||
"totalMs": 105.3750100000002,
|
||||
"runsCount": 5,
|
||||
"measuredRunsAvgMs": 21.07500200000004,
|
||||
"measuredRunsMs": [
|
||||
24.79783199999997,
|
||||
19.563640000000078,
|
||||
20.759051999999883,
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eslint",
|
||||
"totalMs": 11845.073306000002,
|
||||
"runsCount": 5,
|
||||
"measuredRunsAvgMs": 2369.0146612000003,
|
||||
"measuredRunsMs": [
|
||||
2686.1039550000005,
|
||||
2281.501061,
|
||||
2298.6185210000003,
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### rslint
|
||||
[rslint](https://github.com/rslint/rslint)是一个高性能、可定制性强、简单易用的`Javascript/Typescript` lint分析工具。
|
||||
|
||||
```shell
|
||||
$ echo "let a = foo.hasOwnProperty('bar');" > foo.js
|
||||
$ rslint ./foo.js
|
||||
error[no-prototype-builtins]: do not access the object property `hasOwnProperty` directly from `foo`
|
||||
┌─ ./foo.js:1:9
|
||||
│
|
||||
1 │ let a = foo.hasOwnProperty('bar');
|
||||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
│
|
||||
help: get the function from the prototype of `Object` and call it
|
||||
│
|
||||
1 │ let a = Object.prototype.hasOwnProperty.call(foo, 'bar');
|
||||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
│
|
||||
╧ note: the method may be shadowed and cause random bugs and denial of service vulnerabilities
|
||||
|
||||
Outcome: 1 fail, 0 warn, 0 success
|
||||
|
||||
help: for more information about the errors try the explain command: `rslint explain <rules>`
|
||||
```
|
||||
|
||||
|
||||
### rusty_v8
|
||||
[rusty_v8](https://github.com/denoland/rusty_v8)是`v8`的Rust语言绑定,底层封装了`c++ API`。
|
||||
|
||||
|
||||
## 用WASM增强JS
|
||||
|
||||
### wasm
|
||||
[wasm(web assembly)](https://webassembly.org/docs/use-cases/)是一种低级语言,它运行在浏览器中,可以和`javascript`相互调用,几乎所有浏览器都支持, 而且目前有多种高级语言都可以直接编译成`wasm`,更是大大增强了它的地位。
|
||||
|
||||
目前来说Rust可以编译成`wasm`,虽然还不够完美,但是它正在以肉眼可见的速度快速发展中。因此同时使用`Rust`和`Javascript`成为了一种可能:将`Rust`编译成`wasm`,再跟`js`进行交互,两者共生共存,各自解决擅长的场景(`wasm`性能高,`js`开发速度快)。
|
||||
|
||||
### yew
|
||||
[`yew`](https://github.com/yewstack/yew)是一个正在活跃开发的`Rust/Wasm`框架,用于构建`Web`客户端应用。
|
||||
|
||||
<img alt="yew screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/yew.jpg?raw=true" class="center" />
|
||||
|
||||
### gloo
|
||||
[gloo]是一个模块化的工具,使用`Rust/WASM`构建快速、可靠的`Web`应用。
|
||||
|
||||
```rust
|
||||
use gloo::{events::EventListener, timers::callback::Timeout};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub struct DelayedHelloButton {
|
||||
button: web_sys::Element,
|
||||
on_click: events::EventListener,
|
||||
}
|
||||
|
||||
impl DelayedHelloButton {
|
||||
pub fn new(document: &web_sys::Document) -> Result<DelayedHelloButton, JsValue> {
|
||||
// 创建 `<button>` 元素.
|
||||
let button = document.create_element("button")?;
|
||||
|
||||
// 监听button上的`click`事件
|
||||
let button2 = button.clone();
|
||||
let on_click = EventListener::new(&button, "click", move |_event| {
|
||||
// 一秒后,更新button中的文本
|
||||
let button3 = button2.clone();
|
||||
Timeout::new(1_000, move || {
|
||||
button3.set_text_content(Some("Hello from one second ago!"));
|
||||
})
|
||||
.forget();
|
||||
});
|
||||
|
||||
Ok(DelayedHelloButton { button, on_click })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### wasm-bindgen
|
||||
[wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)可以让`WASM`模块和`Javascript`模块进行更好的交互。
|
||||
|
||||
```rust
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// 从Web导入 `window.alert` 函数
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
fn alert(s: &str);
|
||||
}
|
||||
|
||||
// 从Rust导出一个`greet`函数到Javascript,该函数会`alert`一条欢迎信息
|
||||
#[wasm_bindgen]
|
||||
pub fn greet(name: &str) {
|
||||
alert(&format!("Hello, {}!", name));
|
||||
}
|
||||
```
|
||||
|
||||
### wasm-pack
|
||||
[wasm-pack](https://github.com/rustwasm/wasm-pack)是一站式的解决方案,用于构建和使用Rust生成的WASM,支持在浏览器中或后台的`Node.js`中与`Javascript`进行交互。
|
||||
|
||||
<img alt="wasm-pack screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/wasm-pack.gif?raw=true" class="center" />
|
||||
|
||||
|
||||
|
||||
### wasmer
|
||||
[wasmer](https://github.com/wasmerio/wasmer)是业界领先的`WASM`运行时,支持`WASI`和`Emscripten`。
|
||||
|
||||
```shell
|
||||
$ wasmer qjs.wasm
|
||||
QuickJS - Type "\h" for help
|
||||
qjs > const i = 1 + 2;
|
||||
qjs > console.log("hello " + i);
|
||||
hello 3
|
||||
```
|
||||
### wasmtime
|
||||
[wasmtime](https://github.com/bytecodealliance/wasmtime)是一个为`WASM`设计的`JIT`风格的独立运行时。
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ rustup target add wasm32-wasi
|
||||
$ rustc hello.rs --target wasm32-wasi
|
||||
$ wasmtime hello.wasm
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
### trunk
|
||||
[trunk](https://github.com/thedodd/trunk)是一个`WASM`构建、打包、Web发布工具。
|
||||
|
||||
### photon
|
||||
[photon]()是高性能的、跨平台的图片处理库,使用`Rust`开发,编译成`WASM`运行,为你的Web应用和`Node.js`应用提供无与伦比的图片处理速度,当然,它既然使用`Rust`开发,也可以作为一个库被你的后台程序所使用。
|
||||
|
||||
<img alt="photon screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/photon.jpg?raw=true" class="center" />
|
||||
|
||||
### tinysearch
|
||||
[tinysearch](https://github.com/tinysearch/tinysearch)是一个搜索工具,用于静态网站中的内容搜索,使用`Rust`和`WASM`构建。优点是体积小(适用于浏览器)、性能高、全文索引。
|
||||
|
||||
<img alt="tinysearch screenshot" width="80%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/tinysearch.gif?raw=true" class="center" />
|
||||
|
||||
### wasm-pdf
|
||||
[wasm-pdf](https://github.com/jussiniinikoski/wasm-pdf)通过`Javascript`和`WASM`来生成`PDF`,可以直接在浏览器中使用。
|
||||
|
||||
|
||||
### makepad
|
||||
[makepad](https://github.com/makepad/makepad)是一个充满创意的Rust开发平台,支持编译成`wasm`,并使用`webGL`进行渲染。
|
||||
|
||||
<img alt="makepad screenshot" width="80%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/makepad.jpg?raw=true" class="center" />
|
||||
|
||||
## Rust + Javascript学习教程
|
||||
### wasm-book
|
||||
[wasm-book](https://github.com/rustwasm/book)是一本讲述`Rust`和`wasm`的书,篇幅不算长,但是值得学习,还包含了几个很酷的例子。
|
||||
|
||||
|
||||
### wasm-learning
|
||||
[`wasm-learning`](https://github.com/second-state/wasm-learning)是一个英文教程,用于学习`Rust`, `wasm`和`Node.js`,你可以学会如何使用`Rust`来为`Nodejs`构建函数,可以同时利用`Rust`的性能、`wasm`的安全性和可移植性、`js`的易用性。
|
||||
|
||||
### rust-js-snake-game
|
||||
[`rust-js-snake-game`](https://github.com/RodionChachura/rust-js-snake-game)是一个用`rust + js + wasm`构建的贪食蛇游戏。
|
142
src/awesome-gamedev.md
Normal file
142
src/awesome-gamedev.md
Normal file
@ -0,0 +1,142 @@
|
||||
# 游戏开发
|
||||
我在这里大胆预言:Rust未来会成为和`C++`同级别的游戏开发语言,特别是在游戏引擎方面,会大放异彩。
|
||||
|
||||
|
||||
## 目录索引
|
||||
|
||||
- [**游戏引擎**](#游戏引擎): [bevy](#bevy), [fyrox](#fyrox前rg3d), [ggez](#ggez), [oxygengine](#oxygengine), [macroquad](#macroquad), [godot-rust](#godot-rust), [piston](#piston), [amethyst](#amethyst)
|
||||
- [**GPU和图形渲染**](#gpu和图形渲染): [wgpu](#wgpu), [rust-gpu](#rust-gpu),[kajiya](#kajiya), [lyon](#lyon), [ash](#ash), [vulkano](#vulkano), [rend3](#rend3), [rafx](#rafx), [gfx](#gfx), [luminance](#luminance), [miniquad](#miniquad), [glow](#glow)
|
||||
- [**学习资料和新闻**](#学习资料)
|
||||
|
||||
## 游戏引擎
|
||||
### Bevy
|
||||
[bevy](https://github.com/bevyengine/bevy)是一个数据驱动的游戏引擎,支持2D和3D图形开发,优点是社区活跃、更新快、模块化设计优秀、性能高,缺点是还处于快速开发中,并不适合生产使用。
|
||||
|
||||
同时`bevy`的文档齐全,官方示例很多,非常适合学习和使用。
|
||||
|
||||
<img alt="fyrox screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/bevy.jpg?raw=true" class="center" />
|
||||
|
||||
### Fyrox(前rg3d)
|
||||
[fyrox](https://github.com/FyroxEngine/Fyrox)是一个`2D`和`3D`游戏图形化引擎,功能丰富,生产可用(官方宣称)。
|
||||
|
||||
该项目前身是`rg3d`,但是被收购后,更名为`fyrox`,潜力应该是相当好的,下面截图来源于基于该引擎开发的游戏[`StationIapetus`](https://github.com/mrDIMAS/StationIapetus)。
|
||||
|
||||
<img alt="fyrox screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/fyrox.jpg?raw=true" class="center" />
|
||||
|
||||
### ggez
|
||||
[ggez](https://github.com/ggez/ggez)是一个轻量级的`2D`游戏图形引擎,它的目标是让游戏开发尽量的简单,因此它的功能并不是很强大,例如如果你想要强大且真实的物理引擎,它可能无能为力,但你可以选择在它的基础上构建自己的更高级的引擎。
|
||||
|
||||
<img alt="ggez screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/ggez.png?raw=true" class="center" />
|
||||
|
||||
### oxygengine
|
||||
[oxygengine](https://github.com/PsichiX/oxygengine)是一个`2D` HTML5游戏引擎,支持编译成WASM在浏览器中运行。
|
||||
|
||||
<img alt="oxygengine screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/oxygengine.gif?raw=true" class="center" />
|
||||
|
||||
### macroquad
|
||||
[macroquad](https://github.com/not-fl3/macroquad)是一个`2D`游戏引擎,特点是简单易用,例如它试图让使用者不会遇到Rust生命周期的难题。
|
||||
|
||||
### godot-rust
|
||||
[godot-rust](https://github.com/godot-rust/godot-rust)是大名鼎鼎的`godot`引擎的`Rust`绑定,[`godot`](https://github.com/godotengine/godot)是`c++`开发的游戏`2D/3D`引擎,但是对Rust语言提供了很好的支持。
|
||||
|
||||
<img alt="godot screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/godot.jpg?raw=true" class="center" />
|
||||
|
||||
|
||||
### piston
|
||||
[piston](https://github.com/PistonDevelopers/piston)是前两年较火的模块化的游戏引擎,但是最近半年开发速度缓慢,我调查了一番,但不清楚发生了什么。
|
||||
|
||||
### Amethyst
|
||||
[Amethyst](https://github.com/amethyst/amethyst), 前几年较火的Rust游戏引擎,但是最近开发已经停滞,经过我调查,是因为作者团队转型Rust游戏开发知识分享,因此项目被[放弃](https://amethyst.rs/posts/amethyst--starting-fresh)。
|
||||
|
||||
## GPU和图形渲染
|
||||
### wgpu
|
||||
[wgpu](https://github.com/gfx-rs/wgpu)是一个纯Rust实现的图形化API库,具有安全、可移植等优点,如果你使用基于`wgpu`构建的库,那该库可以很多平台上运行:Linux, windows, MacOS, Android和IOS。
|
||||
|
||||
它可以原生的运行在`Vulkan`, `Metal`等主流平台上,且可以使用`wasm`的方式运行在`WebGPU`上,同时API兼容`WebGPU`标准。
|
||||
|
||||
总之,如果你要使用`WebGPU`, 选它就对了。
|
||||
|
||||
### rust-gpu
|
||||
[rust-gpu](https://github.com/EmbarkStudios/rust-gpu)的目标是让Rust成为GPU编程的第一梯队语言,由大名鼎鼎的`Embark`公司开发,后台较硬。
|
||||
|
||||
如果需要通用的`GPU`编程,选它就对了。
|
||||
|
||||
### kajiya
|
||||
[kajiya](https://github.com/EmbarkStudios/kajiya)是一个实时的、全局光照渲染系统,由`Embark`公司开发,该公司在秘密研究基于Rust的游戏引擎,据说准备应用在新游戏上,有朝一日它可能会是推动Rust游戏引擎爆发式发展的功臣。
|
||||
|
||||
`kajiya`应用了非常先进的论文和设计理念,因此非常值得有志于游戏引擎开发的同学学习。但目前还不适用于生产级使用,具体见[这里](https://medium.com/embarkstudios/homegrown-rendering-with-rust-1e39068e56a7)。
|
||||
|
||||
<img alt="kajiya screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/kajiya.jpg?raw=true" class="center" />
|
||||
|
||||
### lyon
|
||||
[lyon](https://github.com/nical/lyon)可以使用GPU进行向量路径渲染,例如高效渲染复杂的`svg`等。
|
||||
|
||||
### ash
|
||||
[ash](https://github.com/MaikKlein/ash)是一个轻量级的`Vulkan`绑定。
|
||||
|
||||
<img alt="ash screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/ash.png?raw=true" class="center" />
|
||||
|
||||
### vulkano
|
||||
[vulkano](https://github.com/vulkano-rs/vulkano)是一个安全、特性丰富的`Vulkan`绑定。
|
||||
|
||||
### rend3
|
||||
[rend3](https://github.com/BVE-Reborn/rend3)是一个简单易用、可定制性强、高效的3D渲染库,基于`wgpu`开发。
|
||||
|
||||
<img alt="rend3 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/rend3.jpg?raw=true" class="center" />
|
||||
|
||||
### rafx
|
||||
[rafx](https://github.com/aclysma/rafx)是一个多后端渲染器,目标是性能、扩展性和生产力。
|
||||
|
||||
<img alt="rafx screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/rafx.jpg?raw=true" class="center" />
|
||||
|
||||
### gfx
|
||||
[gfx](https://github.com/gfx-rs/gfx)是一个底层的图形库,目前已经不怎么活跃,主要原因是:它的核心组件`gfx-hal`最开始的目标是为`wgpu`提供功能,但是后面`wgpu`实现了自己的`wgpu-hal`,因此`gfx-hal`目前仅处于维护状态。
|
||||
|
||||
### luminance
|
||||
[luminance](https://github.com/phaazon/luminance-rs)是一个类型安全、无状态的图形框架,目标是让图形渲染变得简单和优雅,最开始是通过`Haskell`语言实现,然后在`2016`年移植到`Rust`上。
|
||||
|
||||
它很简单,功能也不够强大,如果你没有`OpenGL`、`Vulkan`的经验,可以使用它做一些简单的图形渲染项目试试。
|
||||
|
||||
### miniquad
|
||||
[miniquad](https://github.com/not-fl3/miniquad)是一个安全和跨平台的图形渲染库,它提供了较为底层的API,如果需要抽象层次更高的API,可以使用之前提到的[macroquad](https://github.com/not-fl3/macroquad),后者是基于`miniquad`封装实现。
|
||||
|
||||
<img alt="miniquad screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/miniquad.gif?raw=true" class="center" />
|
||||
|
||||
|
||||
|
||||
### glow
|
||||
[glow](https://github.com/grovesNL/glow)提供了各种`GL`绑定(OpenGL, WebGL), 提供了一定的抽象,避免你写平台相关的特定代码实现。
|
||||
|
||||
|
||||
## 学习资料
|
||||
### 游戏开发最新新闻
|
||||
|
||||
- [gamedev](https://gamedev.rs)
|
||||
|
||||
### 一些学习资料(英文)
|
||||
|
||||
- [Hands-on Rust](https://pragprog.com/titles/hwrust/hands-on-rust/)
|
||||
- 使用[bracket-lib](https://github.com/amethyst/bracket-lib)和其[配套书籍](https://bfnightly.bracketproductions.com/rustbook/)进行学习
|
||||
- 想要没有困难的开发一个跨平台的2D游戏?使用[`macroquad`](https://macroquad.rs),并且可以参考用它开发的两个游戏: [fish fight](https://github.com/fishfight/FishFight)和[zemeroth](https://github.com/ozkriff/zemeroth)
|
||||
- 想要开发一个简单的3D游戏并且需要一个编辑器?可以试试[`fyrox(rg3d)`](https://github.com/rg3dengine/rg3d)
|
||||
- 想要开发一个复杂的游戏或者想要做一个demo,未来可以基于该demo继续开发,最终完成一个复杂游戏?可以试试`godot`引擎提供的`Rust`绑定:[godot-rust](https://godot-rust.github.io)
|
||||
- 喜欢钻研前沿技术?试试[`bevy`](https://bevyengine.org),它拥有最好的`ECS`实现和最先进的设计理念(可能)
|
||||
|
||||
### ECS(Entity Component System)和DOD(面向数据设计)资料
|
||||
我们在上面提到的很多系统都使用了`ECS`和`DOD`,因此这两者对于游戏开发是极其重要的,下面是一些相关的英文资料(部分需要翻墙),可以帮助大家理解相关概念。
|
||||
|
||||
- [hecs](https://github.com/Ralith/hecs), 一个用Rust实现的ECS世界
|
||||
- [Understanding data-oriented design for entity component systems - Unity at GDC 2019](https://www.youtube.com/watch?v=0_Byw9UMn9g)
|
||||
- [CppCon 2018: Stoyan Nikolov “OOP Is Dead, Long Live Data-oriented Design”](https://www.youtube.com/watch?v=yy8jQgmhbAU)
|
||||
- [RustConf 2018 - Closing Keynote - Using Rust For Game Development by Catherine West](https://www.youtube.com/watch?v=aKLntZcp27M)
|
||||
- ["Data-Oriented Design" web book by Richard Fabian](https://dataorienteddesign.com/dodbook/)
|
||||
|
||||
|
||||
### 一些游戏开发的生产力工具
|
||||
|
||||
- [Blender](https://www.blender.org)用于3D建模
|
||||
- [Krita](https://krita.org/en)用于创建2D图片
|
||||
|
||||
|
||||
|
||||
|
95
src/awesome-games.md
Normal file
95
src/awesome-games.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 游戏
|
||||
我们精心挑选了一些用Rust写得优秀游戏,希望大家喜欢:)
|
||||
|
||||
### 目录索引
|
||||
| 游戏名 | 描述 |
|
||||
| ------ | ----- |
|
||||
| [veloren](#veloren) | 多人在线3D PRG游戏 |
|
||||
| [citybound](#citybound) | 多人在线城市模拟游戏 |
|
||||
| [sandspiel](#sandspiel) | 创意游戏-落沙世界 |
|
||||
| [fish fight](#fish-fight) | 多人2D射击策略游戏 |
|
||||
| [doukutsu](#doukutsu) | `Cave Story`重制版 |
|
||||
| [rusted ruins](#rusted-ruins) | 开发世界、像素游戏 |
|
||||
| [sulis](#sulis) | 回合制策略游戏 |
|
||||
| [zemeroth](#zemeroth) | 2D棋盘策略游戏 |
|
||||
| [mk48](#mk48) | 2D多人在线海战游戏 |
|
||||
| [theta wave](#theta-wave) | 2D太空射击游戏 |
|
||||
| [rust doom](#rust-doom) | 模仿`Doom`的射击游戏 |
|
||||
|
||||
### veloren
|
||||
[Veloren](https://gitlab.com/veloren/veloren)是一款多人在线3D RPG游戏,该游戏借鉴了`Cube World`、`Minecraft(我的世界)`和`Dwarf fortress`。
|
||||
|
||||
目前游戏开发非常活跃,也是Rust目前游戏中最有前景的之一,值得看好,当前已经可以玩,你可以通过[官方地址](https://veloren.net/account/)在线试玩。
|
||||
|
||||
<img alt="veloren screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/veloren.jpg?raw=true" class="center" />
|
||||
|
||||
### citybound
|
||||
[citybound](https://github.com/citybound/citybound)是一个多人在线模拟游戏,使用Rust + WASM + JS开发。
|
||||
|
||||
<img alt="citybound screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/citybound.png?raw=true" class="center" />
|
||||
|
||||
### sandspiel
|
||||
[sandspiel](https://github.com/MaxBittker/sandspiel)是一款很有创意、很艺术的游戏,通过天上落下的沙子来构建美丽的沙世界。该游戏使用Rust + wasm + webgl + js(胶水,用来粘合前几个)构建。
|
||||
|
||||
你可以[在线试玩](https://sandspiel.club),尝试构建自己的沙世界。
|
||||
|
||||
<img alt="sandspiel screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/sandspiel.png?raw=true" class="center" />
|
||||
|
||||
### fish-fight
|
||||
[Fish Fight](https://github.com/fishfight/FishFight)是一款2D射击策略游戏,支持最多4人一起玩,可以通过在线的方式或共享屏幕的方式玩,总之这是一款相当不错的游戏,[官网](https://fishfight.org)做得也很酷。
|
||||
|
||||
<img alt="fish-fight screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/fish-fight.jpg?raw=true" class="center" />
|
||||
|
||||
### doukutsu
|
||||
[doukutsu](https://github.com/doukutsu-rs/doukutsu-rs)是2004年发行的视频游戏`Cave Story`的重制版,使用Rust开发。
|
||||
|
||||
<img alt="doukutsu1 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/doukutsu1.
|
||||
png?raw=true" class="center" />
|
||||
<img alt="doukutsu2 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/doukutsu2.
|
||||
png?raw=true" class="center" />
|
||||
|
||||
### rusted-ruins
|
||||
[rusted-ruins](https://github.com/garkimasera/rusted-ruins)是一个开放世界2D像素游戏,用户可以在里面探索各种野外和废墟。
|
||||
|
||||
目前游戏还处于较为早期阶段,但是开发活跃。
|
||||
|
||||
<img alt="rusted-ruins1 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/rusted-ruins1.
|
||||
png?raw=true" class="center" />
|
||||
<img alt="rusted-ruins2 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/rusted-ruins2.
|
||||
png?raw=true" class="center" />
|
||||
|
||||
### sulis
|
||||
[sulis](https://github.com/Grokmoo/sulis)是一款回合制策略游戏,包含了一个从零开发的引擎,目前游戏已经具备相当高的可玩性,你还可以选择不同的势力,感兴趣的同学可以一试。
|
||||
|
||||
<img alt="sulis screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/sulis.jpg?raw=true" class="center" />
|
||||
|
||||
### zemeroth
|
||||
[zemeroth](https://github.com/ozkriff/zemeroth)是一个2D棋盘策略游戏,通过Rust + WASM实现。
|
||||
|
||||
你可以通过[在线网址](https://ozkriff.itch.io/zemeroth),使用WASM来体验这个游戏。
|
||||
|
||||
<img alt="zemeroth1 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/zemeroth1.
|
||||
png?raw=true" class="center" />
|
||||
<img alt="zemeroth2 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/zemeroth2.
|
||||
jpeg?raw=true" class="center" />
|
||||
|
||||
### mk48
|
||||
[mk48](https://github.com/SoftbearStudios/mk48)是一个2D海战游戏,支持多人在线和和多语言(包括中文),你可以通过官方网址在线试玩: [https://mk48.io](https://mk48.io)。
|
||||
|
||||
<img alt="mk48 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/mk48.
|
||||
jpg?raw=true" class="center" />
|
||||
|
||||
### theta wave
|
||||
[Theta Wave](https://github.com/thetawavegame/thetawave-legacy)是一款2D太空射击游戏,基于`Amethyst`引擎开发。
|
||||
|
||||
在游戏中,你扮演的是保卫目标的飞船,游戏的目标是通过摧毁敌人来存活,还可以收集货币用于购买对你的生存有帮助的物品,并击败最终BOSS取得胜利。
|
||||
|
||||
<img alt="theta-wave screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/theta.
|
||||
jpg?raw=true" class="center" />
|
||||
|
||||
|
||||
### rust doom
|
||||
[Rust doom](https://github.com/cristicbz/rust-doom)是一款模仿`Doom 1&2`的简单射击游戏,需要注意,它并不是一个`Doom`移植版。
|
||||
|
||||
<img alt="rust-doom screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/rust-doom.
|
||||
jpg?raw=true" class="center" />
|
267
src/awesome-superstar.md
Normal file
267
src/awesome-superstar.md
Normal file
@ -0,0 +1,267 @@
|
||||
# 明星项目
|
||||
> 滚滚长江东逝水,浪花淘尽英雄,是非成败转头空 - 临江仙·滚滚长江东逝水
|
||||
|
||||
经过大浪淘沙留下来的才是真金白银,对于开源项目也是如此。对于明星项目,本文不仅仅以`star`数的多少作为评判维度,还会结合项目规模、影响力、活跃度、社区活跃度等多个方面进行评定,希望大家能喜欢。
|
||||
|
||||
需要注意,本文列出的几乎都是平台级项目,因此并不是star多,就能名列其中,例如很多`star`很多的工具、Rust库、书籍都没有列入,如果大家想要看更多的子类项目,请访问对应的文件进行查看。
|
||||
|
||||
## deno
|
||||
首先出场的自然是咖位最重的之一,可以说正是因为`deno`和`swc`的横空出世,才让一堆观望的大神对于Rust实现`Javascript`基建有了更强的信心。
|
||||
|
||||
[`deno`](https://github.com/topics/rust?l=rust&o=desc&s=stars)是`node`半逆转后的字序,从此可以看出`deno`是`Node.js`的替代,它的目标是为`Typescript/Javascript`提供一个更现代化、更安全、更强大 的运行时,同时内置了很多强大的工具,可以用于打包、编译成可执行文件、文档、测试、lint等。
|
||||
|
||||
## alacritty
|
||||
[alacritty](https://github.com/alacritty/alacritty)是一个跨平台、基于OpenGL的终端,性能极高的同时还支持丰富的自定义和可扩展性,可以说是非常优秀的现代化终端。
|
||||
|
||||
目前已经是`beta`阶段,可以作为日常工具来使用。
|
||||
|
||||
<img alt="alacritty screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/alacritty.png?raw=true" class="center" />
|
||||
|
||||
## starship
|
||||
[starship](https://github.com/starship/starship)是一个命令行提示,支持任何`shell`,包括`zsh`,简单易用、非常快且拥有极高的可配置性。
|
||||
|
||||
<img alt="starship screenshot" width="100%" src="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif" class="center" />
|
||||
|
||||
## MeiliSearch
|
||||
[MeiliSearch](https://github.com/meilisearch/MeiliSearch)是一个搜索平台,但是跟`ElasticSearch`不同,`MeiliSearch`并不是通用目的的,它的目标是为终端用户提供边输入边提示的即刻搜索功能,因此是一个轻量级搜索平台,不适用于数据量大时的搜索目的。
|
||||
|
||||
总之,如果你需要在网页端或者APP为用户提供一个搜索条,然后支持输入容错、前缀搜索时,就可以使用它。
|
||||
|
||||
<img alt="meilisearch screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/meilisearch.gif?raw=true" class="center" />
|
||||
|
||||
## swc 🌟19.5k
|
||||
[`swc`](https://github.com/swc-project/swc)是`Typescript/Javascript`编译器,它可以用来编译、压缩和打包JS,同时支持使用插件进行扩展,例如做代码变换等。
|
||||
|
||||
`swc`目前正在被一些知名项目所使用,包括`Next.js`,`Parcel`和`Deno`,还有些著名的公司也在使用它,例如`Vercel`、字节跳动、腾讯等。
|
||||
|
||||
它的性能非常非常高,官方号称,在单线程下比`Babel`快20倍,在4核心下比`Babel`快70倍!
|
||||
|
||||
几个使用案例:
|
||||
|
||||
- [nextjs 12](http://nextjs.org/12), 通过使用`swc`获得了更好的扩展性、性能以及wasm的支持,其中性能方面提升了3倍刷新速度、5倍打包速度
|
||||
- [Parcel](https://parceljs.org/),通过使用`swc`改善了10倍的性能
|
||||
|
||||
<img alt="parcel screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/parcel.png?raw=true" class="center" />
|
||||
|
||||
## tauri
|
||||
[tauri](https://tauri.studio)可以用来更小、更快、更安全的桌面应用,它想要替代的是`electron.js`。
|
||||
|
||||
下面是援引自[官网](https://tauri.studio/benchmarks)的性能对比图:
|
||||
|
||||
<img alt="tauri1 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/tauri1.png?raw=true" class="center" />
|
||||
|
||||
<img alt="tauri2 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/tauri2.png?raw=true" class="center" />
|
||||
|
||||
## yew
|
||||
[`yew`](https://github.com/yewstack/yew)是一个正在活跃开发的`Rust/Wasm`框架,用于构建`Web`应用。
|
||||
|
||||
<img alt="yew screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/javascript/yew.jpg?raw=true" class="center" />
|
||||
|
||||
|
||||
## firecracker
|
||||
[`firecracker`](https://github.com/firecracker-microvm/firecracker)是一个安全、高性能的无服务计算虚拟机(FaaS),支持多租户、资源隔离等高级特性,由Amazon公司开发,为AWS部分云计算服务提供了强力有的支持。BTW,亚马逊Amazon公司对于Rust语言的喜爱是众所周知的,几乎已经成了Rust的形象大使之一了:)
|
||||
|
||||
## nushell
|
||||
[`nushell`](https://github.com/nushell/nushell)是一个全新的`shell`,使用`Rust`实现。它的目标是创建一个现代化的`shell`:虽然依然基于`Unix`的哲学,但是更适合现在的时代。例如,你可以使用`SQL`语法来选择你想要的内容!
|
||||
|
||||
<img alt="delta screenshot" width="100%" src="https://github.com/nushell/nushell/raw/main/images/nushell-autocomplete5.gif" class="center" />
|
||||
|
||||
|
||||
## tokio
|
||||
[`tokio`](https://github.com/tokio-rs/tokio)的名声可以说是如雷贯耳,如果学过Rust但是没有听说过它,那我觉得可能要回炉重造下:)
|
||||
|
||||
`tokio`是一个异步IO的运行时,提供了`I/O`、网络、调度、定时器等等异步编程所必须的功能和工具,性能和功能都异常强大。
|
||||
|
||||
## AppFlowy
|
||||
[AppFlowy](https://github.com/AppFlowy-IO/appflowy)是[`Notion`](https://www.notion.so/zh-cn)的开源实现,使用`Rust`和`Flutter`进行开发,用于用户文档和数据的管理,支持丰富的自定义特性。
|
||||
|
||||
<img alt="appflowy screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/appflowy.png?raw=true" class="center" />
|
||||
|
||||
## Bevy
|
||||
[bevy](https://github.com/bevyengine/bevy)是一个数据驱动的游戏引擎,支持2D和3D图形开发,优点是社区活跃、更新快、模块化设计优秀、性能高,缺点是还处于快速开发中,并不适合生产使用。
|
||||
|
||||
同时`bevy`的文档齐全,官方示例很多,非常适合学习和使用。
|
||||
|
||||
<img alt="bevy screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/gamedev/bevy.jpg?raw=true" class="center" />
|
||||
|
||||
## actix-web
|
||||
[actix-web]()是全世界最快的web框架之一,甚至可以把之一去掉,因为排在它前面的看上去像是一个专为跑分而生的轻量级框架,而`actix-web`可是功能相当多的!
|
||||
|
||||
下面给出`actix`和Go语言`Gin`框架的性能对比:
|
||||
|
||||
<img alt="actix screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/actix.png?raw=true" class="center" />
|
||||
|
||||
## iced
|
||||
[`iced`](https://github.com/iced-rs/iced)是一个跨平台GUI库,具有简单易用、模块化设计、响应式布局等优点。
|
||||
|
||||
<img alt="iced screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/iced.gif?raw=true" class="center" />
|
||||
|
||||
## cube.js
|
||||
[`cube.js`](https://github.com/cube-js/cube.js)是一个数据分析API平台,可以用于构建内部的BI或为现有的应用增加客户数据统计等功能,使用`Rust`和`Typescript`构建。
|
||||
|
||||
<img alt="cubejs screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/cubejs.png?raw=true" class="center" />
|
||||
|
||||
## wasmer
|
||||
[wasmer](https://github.com/wasmerio/wasmer)是业界领先的`WASM`运行时,支持`WASI`和`Emscripten`。
|
||||
|
||||
```shell
|
||||
$ wasmer qjs.wasm
|
||||
QuickJS - Type "\h" for help
|
||||
qjs > const i = 1 + 2;
|
||||
qjs > console.log("hello " + i);
|
||||
hello 3
|
||||
```
|
||||
|
||||
## tikv
|
||||
[`tikv`](https://github.com/tikv/tikv)相信大家都已知道,`tidb`的底层存储服务,国人之光项目,在数据之外,还做了大量的技术知识普及工作,值得敬佩!
|
||||
|
||||
`tikv`是分布式`KV`数据库,支持分布式事务。
|
||||
|
||||
<img alt="tikv screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/tikv.png?raw=true" class="center" />
|
||||
|
||||
## ruffle
|
||||
[`ruffle`](https://github.com/ruffle-rs/ruffle)是用Rust写的`Flash Player`模拟器,同时支持桌面端和Web端,其中后者通过WASM提供支持。
|
||||
|
||||
## rustdesk
|
||||
[`rustdesk`](https://github.com/rustdesk/rustdesk)是国内团队开发的一款远程桌面软件。
|
||||
|
||||
<img alt="rustdesk screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/rustdesk.jpg?raw=true" class="center" />
|
||||
|
||||
## RustPython
|
||||
[`RustPython`]是使用`Rust`实现的`Python`解释器, 支持`Python3`(CPython >= 3.9.0)。
|
||||
|
||||
大家可以通过官方提供的[在线网址](https://rustpython.github.io/demo/)进行尝试。
|
||||
|
||||
<img alt="rustpython screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/rustpython.jpg?raw=true" class="center" />
|
||||
|
||||
## vector
|
||||
[`vector`](https://github.com/vectordotdev/vector)是一个性能很高的数据采集agent,采集本地的日志、监控等数据,发送到远程的kafka、jaeger等数据下沉端,它最大的优点就是能从多种数据源(包括Opentelemetry)收集数据,然后推送到多个数据处理或者存储等下沉端。
|
||||
|
||||
<img alt="vector screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/vector.jpg?raw=true" class="center" />
|
||||
|
||||
## mdbook
|
||||
[`mdbbok`](https://github.com/rust-lang/mdBook)可以基于`markdown`文件自动创建在线电子书,非常简单好用,目前的问题就是缺乏章节内部的目录跳转和中文搜索。
|
||||
|
||||
<img alt="mdbook screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/mdbook.jpg?raw=true" class="center" />
|
||||
|
||||
## zola
|
||||
[`zola`](https://github.com/getzola/zola)是一个静态网站生成器,类似`hugo`。
|
||||
|
||||
<img alt="zola screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/zola.jpg?raw=true" class="center" />
|
||||
|
||||
|
||||
## gitui
|
||||
[`gitui`](https://github.com/extrawurst/gitui)是一个奇快无比的Git终端UI,无需浏览器即可使用。
|
||||
|
||||
<img alt="gitui screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/gitui.gif?raw=true" class="center" />
|
||||
|
||||
## solana
|
||||
[`solana`](https://github.com/solana-labs/solana)是知名的区块链平台,快速、安全、去中心化,还自带应用市场。
|
||||
|
||||
## ripgrep
|
||||
[`ripgrep`](https://github.com/BurntSushi/ripgrep)是一个性能极高的现代化`grep`实现,后者是`Unix/Linux`下的内置文件搜索工具。该项目是Rust的明星项目,一个是因为性能极其的高,另一个就是源代码质量很高,值得学习, 同时`Vscode`使用它作为内置的搜索引擎。
|
||||
|
||||
从功能来说,除了全面支持`grep`的功能外,`repgre`支持使用正则递归搜索指定的文件目录,默认使用`.gitignore`对指定的文件进行忽略。
|
||||
|
||||
<img alt="ripgrep screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/ripgrep.png?raw=true" class="center" />
|
||||
|
||||
## citybound
|
||||
[`citybound`](https://github.com/citybound/citybound)是一个多人在线模拟游戏,使用Rust + WASM + JS开发。
|
||||
|
||||
<img alt="citybound screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/game/citybound.png?raw=true" class="center" />
|
||||
|
||||
|
||||
## bottlerocket
|
||||
[`bottlerocket`](https://github.com/bottlerocket-os/bottlerocket)是一个基于`Linux`的操作系统,它的目标是为容器提供宿主环境。
|
||||
|
||||
## lemmy
|
||||
[`lemmy`](https://github.com/LemmyNet/lemmy)是一个`reddit`克隆,可以通过连接聚合的方式来构建社区,支持桌面和移动端。
|
||||
|
||||
<img alt="lemmy screenshot" width="100%" src="/superstar/lemmy.jpg?raw=true" class="center" />
|
||||
|
||||
|
||||
## tantivy
|
||||
[`tantivy`](https://github.com/quickwit-inc/tantivy)是Rust实现的本地搜索库,功能对标`lucene`,如果你不需要分布式,那么引入tantivy作为自己本地Rust服务的一个搜索,是相当不错的选择,该库作者一直很活跃,而且最近还创立了搜索引擎公司,感觉大有作为. 该库的优点在于纯Rust实现,性能高(lucene的2-3倍),资源占用低(对比java自然不是一个数量级),社区活跃。
|
||||
|
||||
## sled
|
||||
[`sled`](https://github.com/spacejam/sled)是本地嵌入式的数据库。
|
||||
|
||||
```rust
|
||||
let tree = sled::open("/tmp/welcome-to-sled")?;
|
||||
|
||||
// insert and get, similar to std's BTreeMap
|
||||
let old_value = tree.insert("key", "value")?;
|
||||
|
||||
assert_eq!(
|
||||
tree.get(&"key")?,
|
||||
Some(sled::IVec::from("value")),
|
||||
);
|
||||
|
||||
// range queries
|
||||
for kv_result in tree.range("key_1".."key_9") {}
|
||||
|
||||
// deletion
|
||||
let old_value = tree.remove(&"key")?;
|
||||
|
||||
// atomic compare and swap
|
||||
tree.compare_and_swap(
|
||||
"key",
|
||||
Some("current_value"),
|
||||
Some("new_value"),
|
||||
)?;
|
||||
|
||||
// block until all operations are stable on disk
|
||||
// (flush_async also available to get a Future)
|
||||
tree.flush()?;
|
||||
```
|
||||
|
||||
### redox
|
||||
[`Redox`](https://github.com/redox-os/redox)是一个`Unix`风格的微内核操作系统,使用`Rust`实现。`redox`的目标是安全、快速、免费、可用,它在内核设计上借鉴了很多优秀的内核,例如:`SeL4`, `MINIX`, `Plan 9`和`BSD`。
|
||||
|
||||
但`redox`不仅仅是一个内核,它还是一个功能齐全的操作系统,提供了操作系统该有的功能,例如:内存分配器、文件系统、显示管理、核心工具等等。你可以大概认为它是一个`GNU`或`BSD`生态,但是是通过一门现代化、内存安全的语言实现的。
|
||||
|
||||
> 不过据我仔细观察,redox目前的开发进度不是很活跃,不知道发生了什么,未来若有新的发现会在这里进行更新 - Sunface
|
||||
|
||||
<img alt="redox1 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/os/redox1.jpg?raw=true" class="center" />
|
||||
|
||||
<img alt="redox2 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/os/redox2.jpeg?raw=true" class="center" />
|
||||
|
||||
### youki
|
||||
[`youki`](https://github.com/containers/youki)是一个容器运行时,实现了`OCI`标准,性能非常好的同时具备非常高的安全性, 目前来说,它的性能跟`crun`差不多,比`runc`快50%以上。
|
||||
|
||||
|
||||
### slint
|
||||
[`slint`](https://github.com/slint-ui/slint)是一个GUI工具集,原名sixtyfps, 同时适用于嵌入式系统、桌面系统、移动端、浏览器(WASM),支持使用多种语言进行开发,背后有商业公司的支持,未来前景看好。
|
||||
|
||||
slint已于2023年4月发布1.0版本,标志着结束开发模式并已准备好在生产环境中使用。
|
||||
|
||||
<img alt="sixtyfps screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/sixtyfps.png?raw=true" class="center" />
|
||||
|
||||
### wasmtime
|
||||
[wasmtime](https://github.com/bytecodealliance/wasmtime)是一个为`WASM`设计的`JIT`风格的独立运行时,支持`WASI`。
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ rustup target add wasm32-wasi
|
||||
$ rustc hello.rs --target wasm32-wasi
|
||||
$ wasmtime hello.wasm
|
||||
Hello, w
|
||||
```
|
||||
|
||||
### polkadot
|
||||
[`polkadot`](https://github.com/paritytech/polkadot)是知名的区块链平台,它是从[`Substrate`](https://github.com/paritytech/substrate)抽离出来,后者是下一代区块链开发框架。
|
||||
|
||||
### lapce
|
||||
[`lapce`](https://github.com/lapce/lapce)是一款性能极高、功能强大、基于`wgpu`渲染的代码编辑器,基于`Xi-Editor`开发,后者`Xi-Editor`曾经也红极一时,可惜不再维护了,但是依然非常适合做一个编辑器内核。
|
||||
|
||||
<img alt="lapce screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/lapce.jpg?raw=true" class="center" />
|
||||
|
||||
### rust-gpu
|
||||
[rust-gpu](https://github.com/EmbarkStudios/rust-gpu)的目标是让Rust成为GPU编程的第一梯队语言,由大名鼎鼎的`Embark`公司开发,后台较硬。
|
||||
|
||||
如果需要通用的`GPU`编程,选它就对了。
|
50
src/cmd/ansi.md
Normal file
50
src/cmd/ansi.md
Normal file
@ -0,0 +1,50 @@
|
||||
# ANSI 终端
|
||||
|
||||
[ansi_term](https://crates.io/crates/ansi_term) 包可以帮我们控制终端上的输出样式,例如使用颜色文字、控制输出格式等,当然,前提是在 ANSI 终端上。
|
||||
|
||||
`ansi_term` 中有两个主要数据结构:[ANSIString](https://docs.rs/ansi_term/0.12.1/ansi_term/type.ANSIString.html) 和 [Style](https://docs.rs/ansi_term/0.12.1/ansi_term/struct.Style.html)。
|
||||
|
||||
`Style` 用于控制样式:颜色、加粗、闪烁等,而前者是一个带有样式的字符串。
|
||||
|
||||
## 颜色字体
|
||||
|
||||
```rust,editable
|
||||
use ansi_term::Colour;
|
||||
|
||||
fn main() {
|
||||
println!("This is {} in color, {} in color and {} in color",
|
||||
Colour::Red.paint("red"),
|
||||
Colour::Blue.paint("blue"),
|
||||
Colour::Green.paint("green"));
|
||||
}
|
||||
```
|
||||
|
||||
## 加粗字体
|
||||
|
||||
比颜色复杂的样式构建需要使用 `Style` 结构体:
|
||||
```rust,editable
|
||||
use ansi_term::Style;
|
||||
|
||||
fn main() {
|
||||
println!("{} and this is not",
|
||||
Style::new().bold().paint("This is Bold"));
|
||||
}
|
||||
```
|
||||
|
||||
## 加粗和颜色
|
||||
|
||||
`Colour` 实现了很多跟 `Style` 类似的函数,因此可以实现链式调用。
|
||||
|
||||
```rust,editable
|
||||
use ansi_term::Colour;
|
||||
use ansi_term::Style;
|
||||
|
||||
fn main(){
|
||||
println!("{}, {} and {}",
|
||||
Colour::Yellow.paint("This is colored"),
|
||||
Style::new().bold().paint("this is bold"),
|
||||
// Colour 也可以使用 bold 方法进行加粗
|
||||
Colour::Yellow.bold().paint("this is bold and colored"));
|
||||
}
|
||||
```
|
||||
|
222
src/cmd/awesome.md
Normal file
222
src/cmd/awesome.md
Normal file
@ -0,0 +1,222 @@
|
||||
## 命令行工具
|
||||
对于每一个程序员而言,命令行工具都非常关键。你对他越熟悉,在使用计算机、处理工作流程等越是高效。
|
||||
|
||||
下面我们收集了一些优秀的Rust所写的命令行工具,它们相比目前已有的其它语言的实现,可以提供更加现代化的代码实现、更加高效的性能以及更好的可用性。
|
||||
|
||||
### 索引目录
|
||||
| 新工具 | 替代的目标或功能描述 |
|
||||
| ------ | ----------------- |
|
||||
| [bat](#bat) | cat |
|
||||
| [exa](#exa) | ls |
|
||||
| [lsd](#lsd) | ls |
|
||||
| [fd](#fd) | find |
|
||||
| [procs](#procs) | ps |
|
||||
| [sd](#sd) | sed |
|
||||
| [dust](#dust) | du |
|
||||
| [starship](#starship) | 现代化的命令行提示 |
|
||||
| [ripgrep](#ripgrep) | grep |
|
||||
| [tokei](#tokei) | 代码统计工具 |
|
||||
| [hyperfine](#hyperfine) | 命令行benchmark工具 |
|
||||
| [bottom](#bottom) | top |
|
||||
| [teeldear](#tealdear) | tldr |
|
||||
| [grex](#grex) | 根据文本示例生成正则 |
|
||||
| [bandwitch](#bandwhich) | 显示进程、连接网络使用情况|
|
||||
| [zoxide](#zoxide) | cd |
|
||||
| [delta](#delta) | git可视化 |
|
||||
| [nushell](#nushell) | 全新的现代化shell |
|
||||
| [mcfly](#mcfly) | 替代`ctrl + R`命令搜索 |
|
||||
| [fselect](#fselect) | 使用SQL语法查找文件 |
|
||||
| [pueue](#pueue) | 命令行任务管理工具 |
|
||||
| [watchexec](#watchexec) | 监视目录文件变动并执行命令 |
|
||||
| [dura](#dura) | 更加安全的使用git |
|
||||
| [alacritty](#alacritty) | 强大的基于OpenGL的终端 |
|
||||
| [broot](#broot) | 可视化访问目录树 |
|
||||
|
||||
### bat
|
||||
[bat](https://github.com/sharkdp/bat)`克隆了**cat**的功能并提供了语法高亮和Git集成,它支持`Windows`,`MacOS`和`Linux`。同时,它默认提供了多种文件后缀的语法高亮。
|
||||
|
||||
<img alt="bat screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/bat.png?raw=true" class="center" />
|
||||
|
||||
|
||||
|
||||
### exa
|
||||
[exa](https://github.com/ogham/exa)是`ls`命令的现代化实现,后者是目前`Unix/Linux`系统的默认命令,用于列出当前目录中的内容。
|
||||
|
||||
<img alt="exa screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/exa.jpg?raw=true" class="center" />
|
||||
|
||||
### lsd
|
||||
[lsd](https://github.com/Peltoche/lsd) 也是 `ls` 的新实现,同时增加了很多特性,例如:颜色标注、icons、树形查看、更多的格式化选项等。
|
||||
|
||||
<img alt="lsd screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/lsd.png?raw=true" class="center" />
|
||||
|
||||
### fd
|
||||
[fd](https://github.com/sharkdp/fd) 是一个更快、对用户更友好的**find**实现,后者是 `Unix/Linux` 内置的文件目录搜索工具。之所以说它用户友好,一方面是 `API` 非常清晰明了,其次是它对最常用的场景提供了有意义的默认值:例如,想要通过名称搜索文件:
|
||||
|
||||
- `fd`: `fd PATTERN`
|
||||
- `find`: `find -iname 'PATTERN'`
|
||||
|
||||
同时 `fd` 性能非常非常高,还提供了非常多的搜索选项,例如允许用户通过 `.gitignore` 文件忽略隐藏的目录、文件等。
|
||||
|
||||
<img alt="fd screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/fd.svg?raw=true" class="center" />
|
||||
|
||||
### procs
|
||||
[procs](https://github.com/dalance/procs) 是 **ps** 的默认实现,后者是 `Unix/Linux` 的内置命令,用于获取进程( `process` )的信息。`proc` 提供了更便利、可读性更好的格式化输出。
|
||||
|
||||
<img alt="procs screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/procs.jpg?raw=true" class="center" />
|
||||
|
||||
### sd
|
||||
[sd](https://github.com/chmln/sd) 是 **sed** 命令的现代化实现,后者是 `Unix/Linux` 中内置的工具,用于分析和转换文本。
|
||||
|
||||
`sd` 拥有更简单的使用方式,而且支持方便的正则表达式语法,`sd` 拥有闪电般的性能,比 `sed` 快 **2x-11x** 倍。
|
||||
|
||||
以下是其中一个性能测试结果:
|
||||
|
||||
*对1.5G大小的 JSON 文本进行简单替换*
|
||||
|
||||
`hyperfine -w 3 'sed -E "s/\"/\'/g" *.json >/dev/null' 'sd "\"" "\'" *.json >/dev/null' --export-markdown out.md`
|
||||
|
||||
| Command | Mean [s] | Min…Max [s] |
|
||||
|:---|---:|---:|
|
||||
| `sed -E "s/\"/'/g" *.json >/dev/null` | 2.338 ± 0.008 | 2.332…2.358 |
|
||||
| `sed "s/\"/'/g" *.json >/dev/null` | 2.365 ± 0.009 | 2.351…2.378 |
|
||||
| `sd "\"" "'" *.json >/dev/null` | **0.997 ± 0.006** | 0.987…1.007 |
|
||||
|
||||
结果: ~2.35 times faster
|
||||
|
||||
### dust
|
||||
[dust](https://github.com/bootandy/dust) 是一个更符合使用习惯的**du**,后者是 `Unix/Linux` 内置的命令行工具,用于显示硬盘使用情况的统计。
|
||||
|
||||
<img alt="dust screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/dust.png?raw=true" class="center" />
|
||||
|
||||
### starship
|
||||
[starship](https://github.com/starship/starship) 是一个命令行提示,支持任何 `shell` ,包括 `zsh` ,简单易用、非常快且拥有极高的可配置性, 同时支持智能提示。
|
||||
|
||||
<img alt="starship screenshot" width="100%" src="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif" class="center" />
|
||||
|
||||
### [ripgrep](https://github.com/BurntSushi/ripgrep)
|
||||
[ripgrep](https://github.com/BurntSushi/ripgrep) 是一个性能极高的现代化 `grep` 实现,后者是 `Unix/Linux` 下的内置文件搜索工具。该项目是 Rust 的明星项目,一个是因为性能极其的高,另一个就是源代码质量很高,值得学习, 同时 `Vscode` 使用它作为内置的搜索引擎。
|
||||
|
||||
从功能来说,除了全面支持 `grep` 的功能外,`repgre` 支持使用正则递归搜索指定的文件目录,默认使用 `.gitignore` 对指定的文件进行忽略。
|
||||
|
||||
<img alt="ripgrep screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/ripgrep.png?raw=true" class="center" />
|
||||
|
||||
|
||||
### tokei
|
||||
[tokei](https://github.com/XAMPPRocky/tokei) 可以分门别类的统计目录内的代码行数,速度非常快!
|
||||
|
||||
<img alt="tokei screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/tokei.png?raw=true" class="center" />
|
||||
|
||||
### hyperfine
|
||||
[hyperfine](https://github.com/sharkdp/hyperfine) 是命令行benchmark工具,它支持在多次运行中提供静态的分析,同时支持任何的 `shell` 命令,准确的 `benchmark` 进度和当前预估等等高级特性。
|
||||
|
||||
|
||||
<img alt="hyperfine screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/hyperfine.gif?raw=true" class="center" />
|
||||
|
||||
### bottom
|
||||
[bottom](https://github.com/ClementTsang/bottom) 是一个现代化实现的 `top`,可以跨平台、图形化的显示进程/系统的当前信息。
|
||||
|
||||
<img alt="bottom screenshot" width="100%" src="https://github.com/ClementTsang/bottom/raw/master/assets/demo.gif" class="center" />
|
||||
|
||||
### tealdear
|
||||
[tealdear](https://github.com/dbrgn/tealdeer) 是一个更快实现的**tldr**, 一个用于显示 `man pages` 的命令行程序,简单易用、基于例子和社区驱动是主要特性。
|
||||
|
||||
<img alt="teeldear screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/teeldear.gif?raw=true" class="center" />
|
||||
|
||||
### bandwhich
|
||||
[bandwhich](https://github.com/imsnif/bandwhich) 是一个客户端实用工具,用于显示当前进程、连接、远程 IP( hostname ) 的网络信息。
|
||||
|
||||
<img alt="bandwhich screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/bandwhich.gif?raw=true" class="center" />
|
||||
|
||||
### grex
|
||||
[grex](https://github.com/pemistahl/grex) 既是一个命令行工具又是一个库,可以根据用户提供的文本示例生成对应的正则表达式,非常强大。
|
||||
|
||||
<img alt="grex screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/grex.gif?raw=true" class="center" />
|
||||
|
||||
### zoxide
|
||||
[zoxide](https://github.com/ajeetdsouza/zoxide) 是一个智能化的 `cd` 命令,它甚至会记忆你常用的目录。
|
||||
|
||||
<img alt="zoxide screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/zoxide.webp?raw=true" class="center" />
|
||||
|
||||
### delta
|
||||
[delta](https://github.com/dandavison/delta) 是一个 `git` 分页展示工具,支持语法高亮、代码比对、输出 `grep` 等。
|
||||
|
||||
|
||||
<img alt="delta screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/delta.png?raw=true" class="center" />
|
||||
|
||||
### nushell
|
||||
[nushell](https://github.com/nushell/nushell) 是一个全新的 `shell` ,使用 `Rust` 实现。它的目标是创建一个现代化的 `shell` :虽然依然基于 `Unix` 的哲学,但是更适合现在的时代。例如,你可以使用 `SQL` 语法来选择你想要的内容!
|
||||
|
||||
<img alt="delta screenshot" width="100%" src="https://github.com/nushell/nushell/raw/main/images/nushell-autocomplete5.gif" class="center" />
|
||||
|
||||
### mcfly
|
||||
[mcfly](https://github.com/cantino/mcfly) 会替换默认的 `ctrl-R`,用于在终端中搜索历史命令, 它提供了智能提示功能,并且会根据当前目录中最近执行过的上下文命令进行提示。`mcfly` 甚至使用了一个小型的神经网络用于智能提示!
|
||||
|
||||
<img alt="mcfly screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/mcfly.png?raw=true" class="center" />
|
||||
|
||||
### fselect
|
||||
[fselect](https://github.com/jhspetersson/fselect) 允许使用 SQL 语法来查找系统中的文件。它支持复杂查询、聚合查询、.gitignore 忽略文件、通过宽度高度搜索图片、通过 hash 搜索文件、文件属性查询等等,相当强大!
|
||||
|
||||
```shell
|
||||
# 复杂查询
|
||||
fselect "name from /tmp where (name = *.tmp and size = 0) or (name = *.cfg and size > 1000000)"
|
||||
|
||||
# 聚合函数
|
||||
fselect "MIN(size), MAX(size), AVG(size), SUM(size), COUNT(*) from /home/user/Downloads"
|
||||
|
||||
# 格式化函数
|
||||
fselect "LOWER(name), UPPER(name), LENGTH(name), YEAR(modified) from /home/user/Downloads"
|
||||
```
|
||||
|
||||
### pueue
|
||||
[pueue](https://github.com/nukesor/pueue) 是一个命令行任务管理工具,它可以管理你的长时间运行的命令,支持顺序或并行执行。简单来说,它可以管理一个命令队列。
|
||||
|
||||
<img alt="pueue screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/pueue.gif?raw=true" class="center" />
|
||||
|
||||
### watchexec
|
||||
[watchexec](https://github.com/watchexec/watchexec) 可以监视指定的目录、文件的改动,并执行你预设的命令,支持多种配置项和操作系统。
|
||||
|
||||
```shell
|
||||
# 监视当前目录/子目录中的所有js、css、html文件,一旦发生改变,运行`npm run build`命令
|
||||
$ watchexec -e js,css,html npm run build
|
||||
|
||||
# 当前目录/子目录下任何python文件发生改变时,重启`python server.py`
|
||||
$ watchexec -r -e py -- python server.py
|
||||
```
|
||||
|
||||
### dura
|
||||
[dura](https://github.com/tkellogg/dura) 运行在后台,监视你的 `git` 目录,提交你未提交的更改但是并不会影响 `HEAD`、当前的分支和 `git` 索引(staged文件)。
|
||||
|
||||
如果你曾经遇到过**"完蛋, 我这几天的工作内容丢了"**的情况,那么就可以尝试下 `dura`,`checkout dura brach`,然后代码就可以顺利恢复了:)
|
||||
|
||||
**恢复代码**
|
||||
1. 你可以使用 `dura` 分支来恢复
|
||||
|
||||
```console
|
||||
$ echo "dura/$(git rev-parse HEAD)"
|
||||
```
|
||||
|
||||
1. 也可以手动恢复
|
||||
|
||||
```console
|
||||
# Or, if you don't trust dura yet, `git stash`
|
||||
$ git reset HEAD --hard
|
||||
# get the changes into your working directory
|
||||
$ git checkout $THE_HASH
|
||||
# last few commands reset HEAD back to master but with changes uncommitted
|
||||
$ git checkout -b temp-branch
|
||||
$ git reset master
|
||||
$ git checkout master
|
||||
$ git branch -D temp-branch
|
||||
```
|
||||
|
||||
## alacritty
|
||||
[alacritty](https://github.com/alacritty/alacritty) 是一个跨平台、基于OpenGL的终端,性能极高的同时还支持丰富的自定义和可扩展性,可以说是非常优秀的现代化终端。
|
||||
|
||||
目前已经是 `beta` 阶段,可以作为日常工具来使用。
|
||||
|
||||
<img alt="alacritty screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/superstar/alacritty.png?raw=true" class="center" />
|
||||
|
||||
## broot
|
||||
[`broot`](https://github.com/Canop/broot) 允许你可视化的去访问一个目录结构。
|
||||
|
||||
<img alt="broot screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/command-line/broot.png?raw=true" class="center" />
|
70
src/cmd/parsing.md
Normal file
70
src/cmd/parsing.md
Normal file
@ -0,0 +1,70 @@
|
||||
# 参数解析
|
||||
|
||||
## Clap
|
||||
下面的程序给出了使用 `clap` 来解析命令行参数的样式结构,如果大家想了解更多,在 `clap` [文档](https://docs.rs/clap/)中还给出了另外两种初始化一个应用的方式。
|
||||
|
||||
在下面的构建中,`value_of` 将获取通过 `with_name` 解析出的值。`short` 和 `long` 用于设置用户输入的长短命令格式,例如短命令 `-f` 和长命令 `--file`。
|
||||
|
||||
```rust,editable
|
||||
use clap::{Arg, App};
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("My Test Program")
|
||||
.version("0.1.0")
|
||||
.author("Hackerman Jones <hckrmnjones@hack.gov>")
|
||||
.about("Teaches argument parsing")
|
||||
.arg(Arg::with_name("file")
|
||||
.short("f")
|
||||
.long("file")
|
||||
.takes_value(true)
|
||||
.help("A cool file"))
|
||||
.arg(Arg::with_name("num")
|
||||
.short("n")
|
||||
.long("number")
|
||||
.takes_value(true)
|
||||
.help("Five less than your favorite number"))
|
||||
.get_matches();
|
||||
|
||||
let myfile = matches.value_of("file").unwrap_or("input.txt");
|
||||
println!("The file passed is: {}", myfile);
|
||||
|
||||
let num_str = matches.value_of("num");
|
||||
match num_str {
|
||||
None => println!("No idea what your favorite number is."),
|
||||
Some(s) => {
|
||||
match s.parse::<i32>() {
|
||||
Ok(n) => println!("Your favorite number must be {}.", n + 5),
|
||||
Err(_) => println!("That's not a number! {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`clap` 针对上面提供的构建样式,会自动帮我们生成相应的使用方式说明。例如,上面代码生成的使用说明如下:
|
||||
```shell
|
||||
My Test Program 0.1.0
|
||||
Hackerman Jones <hckrmnjones@hack.gov>
|
||||
Teaches argument parsing
|
||||
|
||||
USAGE:
|
||||
testing [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-f, --file <file> A cool file
|
||||
-n, --number <num> Five less than your favorite number
|
||||
```
|
||||
|
||||
最后,再使用一些参数来运行下我们的代码:
|
||||
```shell
|
||||
$ cargo run -- -f myfile.txt -n 251
|
||||
The file passed is: myfile.txt
|
||||
Your favorite number must be 256.
|
||||
```
|
||||
|
||||
## Structopt
|
||||
@todo
|
201
src/cocurrency/parallel.md
Normal file
201
src/cocurrency/parallel.md
Normal file
@ -0,0 +1,201 @@
|
||||
# 任务并行处理
|
||||
|
||||
### 并行修改数组中的元素
|
||||
|
||||
[rayon](https://docs.rs/rayon/1.5.1/rayon/index.html) 提供了一个 [par_iter_mut](https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefMutIterator.html#tymethod.par_iter_mut) 方法用于并行化迭代一个数据集合。
|
||||
|
||||
```rust,editable
|
||||
use rayon::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut arr = [0, 7, 9, 11];
|
||||
arr.par_iter_mut().for_each(|p| *p -= 1);
|
||||
println!("{:?}", arr);
|
||||
}
|
||||
```
|
||||
|
||||
### 并行测试集合中的元素是否满足给定的条件
|
||||
|
||||
[rayon::any](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.any) 和 [rayon::all](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.all) 类似于 [std::any](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any) / [std::all](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all) ,但是是并行版本的。
|
||||
|
||||
- `rayon::any` 并行检查迭代器中是否有任何元素满足给定的条件,一旦发现符合条件的元素,就立即返回
|
||||
- `rayon::all` 并行检查迭代器中的所有元素是否满足给定的条件,一旦发现不满足条件的元素,就立即返回
|
||||
|
||||
```rust,editable
|
||||
use rayon::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut vec = vec![2, 4, 6, 8];
|
||||
|
||||
assert!(!vec.par_iter().any(|n| (*n % 2) != 0));
|
||||
assert!(vec.par_iter().all(|n| (*n % 2) == 0));
|
||||
assert!(!vec.par_iter().any(|n| *n > 8 ));
|
||||
assert!(vec.par_iter().all(|n| *n <= 8 ));
|
||||
|
||||
vec.push(9);
|
||||
|
||||
assert!(vec.par_iter().any(|n| (*n % 2) != 0));
|
||||
assert!(!vec.par_iter().all(|n| (*n % 2) == 0));
|
||||
assert!(vec.par_iter().any(|n| *n > 8 ));
|
||||
assert!(!vec.par_iter().all(|n| *n <= 8 ));
|
||||
}
|
||||
```
|
||||
|
||||
### 使用给定条件并行搜索
|
||||
下面例子使用 [par_iter](https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefIterator.html#tymethod.par_iter) 和 [rayon::find_any](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.find_any) 来并行搜索一个数组,直到找到任意一个满足条件的元素。
|
||||
|
||||
如果有多个元素满足条件,`rayon` 会返回第一个找到的元素,注意:第一个找到的元素未必是数组中的顺序最靠前的那个。
|
||||
|
||||
```rust,editable
|
||||
use rayon::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let v = vec![6, 2, 1, 9, 3, 8, 11];
|
||||
|
||||
// 这里使用了 `&&x` 的形式,大家可以在以下链接阅读更多 https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.find
|
||||
let f1 = v.par_iter().find_any(|&&x| x == 9);
|
||||
let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6);
|
||||
let f3 = v.par_iter().find_any(|&&x| x > 8);
|
||||
|
||||
assert_eq!(f1, Some(&9));
|
||||
assert_eq!(f2, Some(&8));
|
||||
assert!(f3 > Some(&8));
|
||||
}
|
||||
```
|
||||
|
||||
### 对数组进行并行排序
|
||||
下面的例子将对字符串数组进行并行排序。
|
||||
|
||||
[par_sort_unstable](https://docs.rs/rayon/*/rayon/slice/trait.ParallelSliceMut.html#method.par_sort_unstable) 方法的排序性能往往要比[稳定的排序算法](https://docs.rs/rayon/1.5.1/rayon/slice/trait.ParallelSliceMut.html#method.par_sort)更高。
|
||||
|
||||
|
||||
```rust,editable
|
||||
use rand::{Rng, thread_rng};
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rayon::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut vec = vec![String::new(); 100_000];
|
||||
// 并行生成数组中的字符串
|
||||
vec.par_iter_mut().for_each(|p| {
|
||||
let mut rng = thread_rng();
|
||||
*p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect()
|
||||
});
|
||||
|
||||
//
|
||||
vec.par_sort_unstable();
|
||||
}
|
||||
```
|
||||
|
||||
### 并行化 Map-Reuduce
|
||||
|
||||
下面例子使用 [rayon::filter](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.filter), [rayon::map](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.map), 和 [rayon::reduce](https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.reduce) 来超过 30 岁的 `Person` 的平均年龄。
|
||||
|
||||
- `rayon::filter` 返回集合中所有满足给定条件的元素
|
||||
- `rayon::map` 对集合中的每一个元素执行一个操作,创建并返回新的迭代器,类似于[迭代器适配器](https://course.rs/advance/functional-programing/iterator.html#迭代器适配器)
|
||||
- `rayon::reduce` 则迭代器的元素进行不停的聚合运算,直到获取一个最终结果,这个结果跟例子中 `rayon::sum` 获取的结果是相同的
|
||||
|
||||
```rust,editable
|
||||
use rayon::prelude::*;
|
||||
|
||||
struct Person {
|
||||
age: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let v: Vec<Person> = vec![
|
||||
Person { age: 23 },
|
||||
Person { age: 19 },
|
||||
Person { age: 42 },
|
||||
Person { age: 17 },
|
||||
Person { age: 17 },
|
||||
Person { age: 31 },
|
||||
Person { age: 30 },
|
||||
];
|
||||
|
||||
let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32;
|
||||
let sum_over_30 = v.par_iter()
|
||||
.map(|x| x.age)
|
||||
.filter(|&x| x > 30)
|
||||
.reduce(|| 0, |x, y| x + y);
|
||||
|
||||
let alt_sum_30: u32 = v.par_iter()
|
||||
.map(|x| x.age)
|
||||
.filter(|&x| x > 30)
|
||||
.sum();
|
||||
|
||||
let avg_over_30 = sum_over_30 as f32 / num_over_30;
|
||||
let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30;
|
||||
|
||||
assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON);
|
||||
println!("The average age of people older than 30 is {}", avg_over_30);
|
||||
}
|
||||
```
|
||||
|
||||
### 并行生成缩略图
|
||||
下面例子将为目录中的所有图片并行生成缩略图,然后将结果存到新的目录 `thumbnails` 中。
|
||||
|
||||
[glob::glob_with](https://docs.rs/glob/*/glob/fn.glob_with.html) 可以找出当前目录下的所有 `.jpg` 文件,`rayon` 通过 [DynamicImage::resize](https://docs.rs/image/*/image/enum.DynamicImage.html#method.resize) 来并行调整图片的大小。
|
||||
|
||||
```rust,editable
|
||||
# use error_chain::error_chain;
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs::create_dir_all;
|
||||
|
||||
# use error_chain::ChainedError;
|
||||
use glob::{glob_with, MatchOptions};
|
||||
use image::{FilterType, ImageError};
|
||||
use rayon::prelude::*;
|
||||
|
||||
# error_chain! {
|
||||
# foreign_links {
|
||||
# Image(ImageError);
|
||||
# Io(std::io::Error);
|
||||
# Glob(glob::PatternError);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let options: MatchOptions = Default::default();
|
||||
// 找到当前目录中的所有 `jpg` 文件
|
||||
let files: Vec<_> = glob_with("*.jpg", options)?
|
||||
.filter_map(|x| x.ok())
|
||||
.collect();
|
||||
|
||||
if files.len() == 0 {
|
||||
error_chain::bail!("No .jpg files found in current directory");
|
||||
}
|
||||
|
||||
let thumb_dir = "thumbnails";
|
||||
create_dir_all(thumb_dir)?;
|
||||
|
||||
println!("Saving {} thumbnails into '{}'...", files.len(), thumb_dir);
|
||||
|
||||
let image_failures: Vec<_> = files
|
||||
.par_iter()
|
||||
.map(|path| {
|
||||
make_thumbnail(path, thumb_dir, 300)
|
||||
.map_err(|e| e.chain_err(|| path.display().to_string()))
|
||||
})
|
||||
.filter_map(|x| x.err())
|
||||
.collect();
|
||||
|
||||
image_failures.iter().for_each(|x| println!("{}", x.display_chain()));
|
||||
|
||||
println!("{} thumbnails saved successfully", files.len() - image_failures.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_thumbnail<PA, PB>(original: PA, thumb_dir: PB, longest_edge: u32) -> Result<()>
|
||||
where
|
||||
PA: AsRef<Path>,
|
||||
PB: AsRef<Path>,
|
||||
{
|
||||
let img = image::open(original.as_ref())?;
|
||||
let file_path = thumb_dir.as_ref().join(original);
|
||||
|
||||
Ok(img.resize(longest_edge, longest_edge, FilterType::Nearest)
|
||||
.save(file_path)?)
|
||||
}
|
||||
```
|
336
src/cocurrency/threads.md
Normal file
336
src/cocurrency/threads.md
Normal file
@ -0,0 +1,336 @@
|
||||
# 线程
|
||||
|
||||
### 生成一个临时性的线程
|
||||
|
||||
下面例子用到了 [crossbeam](cookbook/cocurrency/intro.md) 包,它提供了非常实用的、用于并发和并行编程的数据结构和函数。
|
||||
|
||||
[Scope::spawn](https://docs.rs/crossbeam/*/crossbeam/thread/struct.Scope.html#method.spawn) 会生成一个被限定了作用域的线程,该线程最大的特点就是:它会在传给 [crossbeam::scope](https://docs.rs/crossbeam/0.8.1/crossbeam/fn.scope.html) 的闭包函数返回前先行结束。得益于这个特点,子线程的创建使用就像是本地闭包函数调用,因此生成的线程内部可以使用外部环境中的变量!
|
||||
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let arr = &[1, 25, -4, 10];
|
||||
let max = find_max(arr);
|
||||
assert_eq!(max, Some(25));
|
||||
}
|
||||
|
||||
// 将数组分成两个部分,并使用新的线程对它们进行处理
|
||||
fn find_max(arr: &[i32]) -> Option<i32> {
|
||||
const THRESHOLD: usize = 2;
|
||||
|
||||
if arr.len() <= THRESHOLD {
|
||||
return arr.iter().cloned().max();
|
||||
}
|
||||
|
||||
let mid = arr.len() / 2;
|
||||
let (left, right) = arr.split_at(mid);
|
||||
|
||||
crossbeam::scope(|s| {
|
||||
let thread_l = s.spawn(|_| find_max(left));
|
||||
let thread_r = s.spawn(|_| find_max(right));
|
||||
|
||||
let max_l = thread_l.join().unwrap()?;
|
||||
let max_r = thread_r.join().unwrap()?;
|
||||
|
||||
Some(max_l.max(max_r))
|
||||
}).unwrap()
|
||||
}
|
||||
```
|
||||
|
||||
### 创建并行流水线
|
||||
下面我们使用 [crossbeam](https://docs.rs/crossbeam/latest/crossbeam/) 和 [crossbeam-channel](https://docs.rs/crossbeam-channel/*/crossbeam_channel/index.html) 来创建一个并行流水线:流水线的两端分别是数据源和数据下沉( sink ),在流水线中间,有两个工作线程会从源头接收数据,对数据进行并行处理,最后将数据下沉。
|
||||
|
||||
- 消息通道( channel )是 [crossbeam_channel::bounded](https://docs.rs/crossbeam-channel/0.5.4/crossbeam_channel/fn.bounded.html),它只能缓存一条消息。当缓存满后,发送者继续调用 [crossbeam_channel::Sender::send] 发送消息时会阻塞,直到一个工作线程( 消费者 ) 拿走这条消息
|
||||
- 消费者获取消息时先到先得的策略,因此两个工作线程只有一个能取到消息,保证消息不会被重复消费、处理
|
||||
- 通过迭代器 [crossbeam_channel::Receiver::iter](https://docs.rs/crossbeam-channel/*/crossbeam_channel/struct.Receiver.html#method.iter) 读取消息会阻塞当前线程,直到新消息的到来或 channel 关闭
|
||||
- channel 只有在所有的发送者或消费者关闭后,才能被关闭。而其中一个消费者 `rcv2` 处于阻塞读取状态,无比被关闭,因此我们必须要关闭所有发送者: `drop(snd1);` `drop(snd2)` ,这样 channel 关闭后,主线程的 `rcv2` 才能从阻塞状态退出,最后整个程序结束。大家还是迷惑的话,可以看看这篇[文章](https://course.rs/practice/pitfalls/main-with-channel-blocked.html)。
|
||||
|
||||
```rust,editable
|
||||
extern crate crossbeam;
|
||||
extern crate crossbeam_channel;
|
||||
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use crossbeam_channel::bounded;
|
||||
|
||||
fn main() {
|
||||
let (snd1, rcv1) = bounded(1);
|
||||
let (snd2, rcv2) = bounded(1);
|
||||
let n_msgs = 4;
|
||||
let n_workers = 2;
|
||||
|
||||
crossbeam::scope(|s| {
|
||||
// 生产者线程
|
||||
s.spawn(|_| {
|
||||
for i in 0..n_msgs {
|
||||
snd1.send(i).unwrap();
|
||||
println!("Source sent {}", i);
|
||||
}
|
||||
|
||||
// 关闭其中一个发送者 snd1
|
||||
// 该关闭操作对于结束最后的循环是必须的
|
||||
drop(snd1);
|
||||
});
|
||||
|
||||
// 通过两个线程并行处理
|
||||
for _ in 0..n_workers {
|
||||
// 从数据源接收数据,然后发送到下沉端
|
||||
let (sendr, recvr) = (snd2.clone(), rcv1.clone());
|
||||
// 生成单独的工作线程
|
||||
s.spawn(move |_| {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
// 等待通道的关闭
|
||||
for msg in recvr.iter() {
|
||||
println!("Worker {:?} received {}.",
|
||||
thread::current().id(), msg);
|
||||
sendr.send(msg * 2).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 关闭通道,如果不关闭,下沉端将永远无法结束循环
|
||||
drop(snd2);
|
||||
|
||||
// 下沉端
|
||||
for msg in rcv2.iter() {
|
||||
println!("Sink received {}", msg);
|
||||
}
|
||||
}).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 线程间传递数据
|
||||
|
||||
下面我们来看看 [crossbeam-channel](https://docs.rs/crossbeam-channel/*/crossbeam_channel/index.html) 的单生产者单消费者( SPSC ) 使用场景。
|
||||
|
||||
```rust,editable
|
||||
use std::{thread, time};
|
||||
use crossbeam_channel::unbounded;
|
||||
|
||||
fn main() {
|
||||
// unbounded 意味着 channel 可以存储任意多的消息
|
||||
let (snd, rcv) = unbounded();
|
||||
let n_msgs = 5;
|
||||
crossbeam::scope(|s| {
|
||||
s.spawn(|_| {
|
||||
for i in 0..n_msgs {
|
||||
snd.send(i).unwrap();
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
}
|
||||
});
|
||||
}).unwrap();
|
||||
for _ in 0..n_msgs {
|
||||
let msg = rcv.recv().unwrap();
|
||||
println!("Received {}", msg);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 维护全局可变的状态
|
||||
|
||||
[lazy_static]() 会创建一个全局的静态引用( static ref ),该引用使用了 `Mutex` 以支持可变性,因此我们可以在代码中对其进行修改。`Mutex` 能保证该全局状态同时只能被一个线程所访问。
|
||||
|
||||
```rust,editable
|
||||
use error_chain::error_chain;
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Mutex;
|
||||
|
||||
error_chain!{ }
|
||||
|
||||
lazy_static! {
|
||||
static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
||||
}
|
||||
|
||||
fn insert(fruit: &str) -> Result<()> {
|
||||
let mut db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?;
|
||||
db.push(fruit.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
insert("apple")?;
|
||||
insert("orange")?;
|
||||
insert("peach")?;
|
||||
{
|
||||
let db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?;
|
||||
|
||||
db.iter().enumerate().for_each(|(i, item)| println!("{}: {}", i, item));
|
||||
}
|
||||
insert("grape")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 并行计算 iso 文件的 SHA256
|
||||
|
||||
下面的示例将为当前目录中的每一个 .iso 文件都计算一个 SHA256 sum。其中线程池中会初始化和 CPU 核心数一致的线程数,其中核心数是通过 [num_cpus::get](https://docs.rs/num_cpus/*/num_cpus/fn.get.html) 函数获取。
|
||||
|
||||
`Walkdir::new` 可以遍历当前的目录,然后调用 `execute` 来执行读操作和 SHA256 哈希计算。
|
||||
|
||||
```rust,editable
|
||||
|
||||
use walkdir::WalkDir;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read, Error};
|
||||
use std::path::Path;
|
||||
use threadpool::ThreadPool;
|
||||
use std::sync::mpsc::channel;
|
||||
use ring::digest::{Context, Digest, SHA256};
|
||||
|
||||
// Verify the iso extension
|
||||
fn is_iso(entry: &Path) -> bool {
|
||||
match entry.extension() {
|
||||
Some(e) if e.to_string_lossy().to_lowercase() == "iso" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> {
|
||||
let mut buf_reader = BufReader::new(File::open(&filepath)?);
|
||||
let mut context = Context::new(&SHA256);
|
||||
let mut buffer = [0; 1024];
|
||||
|
||||
loop {
|
||||
let count = buf_reader.read(&mut buffer)?;
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
context.update(&buffer[..count]);
|
||||
}
|
||||
|
||||
Ok((context.finish(), filepath))
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let pool = ThreadPool::new(num_cpus::get());
|
||||
|
||||
let (tx, rx) = channel();
|
||||
|
||||
for entry in WalkDir::new("/home/user/Downloads")
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| !e.path().is_dir() && is_iso(e.path())) {
|
||||
let path = entry.path().to_owned();
|
||||
let tx = tx.clone();
|
||||
pool.execute(move || {
|
||||
let digest = compute_digest(path);
|
||||
tx.send(digest).expect("Could not send data!");
|
||||
});
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
for t in rx.iter() {
|
||||
let (sha, path) = t?;
|
||||
println!("{:?} {:?}", sha, path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 使用线程池来绘制分形
|
||||
|
||||
下面例子中将基于 [Julia Set]() 来绘制一个分形图片,其中使用到了线程池来做分布式计算。
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/221000/26546700/9be34e80-446b-11e7-81dc-dd9871614ea1.png" />
|
||||
|
||||
```rust,edtiable
|
||||
# use error_chain::error_chain;
|
||||
use std::sync::mpsc::{channel, RecvError};
|
||||
use threadpool::ThreadPool;
|
||||
use num::complex::Complex;
|
||||
use image::{ImageBuffer, Pixel, Rgb};
|
||||
|
||||
#
|
||||
# error_chain! {
|
||||
# foreign_links {
|
||||
# MpscRecv(RecvError);
|
||||
# Io(std::io::Error);
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# // Function converting intensity values to RGB
|
||||
# // Based on http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm
|
||||
# fn wavelength_to_rgb(wavelength: u32) -> Rgb<u8> {
|
||||
# let wave = wavelength as f32;
|
||||
#
|
||||
# let (r, g, b) = match wavelength {
|
||||
# 380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0),
|
||||
# 440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0),
|
||||
# 490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)),
|
||||
# 510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0),
|
||||
# 580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0),
|
||||
# 645..=780 => (1.0, 0.0, 0.0),
|
||||
# _ => (0.0, 0.0, 0.0),
|
||||
# };
|
||||
#
|
||||
# let factor = match wavelength {
|
||||
# 380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.),
|
||||
# 701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.),
|
||||
# _ => 1.0,
|
||||
# };
|
||||
#
|
||||
# let (r, g, b) = (normalize(r, factor), normalize(g, factor), normalize(b, factor));
|
||||
# Rgb::from_channels(r, g, b, 0)
|
||||
# }
|
||||
#
|
||||
# // Maps Julia set distance estimation to intensity values
|
||||
# fn julia(c: Complex<f32>, x: u32, y: u32, width: u32, height: u32, max_iter: u32) -> u32 {
|
||||
# let width = width as f32;
|
||||
# let height = height as f32;
|
||||
#
|
||||
# let mut z = Complex {
|
||||
# // scale and translate the point to image coordinates
|
||||
# re: 3.0 * (x as f32 - 0.5 * width) / width,
|
||||
# im: 2.0 * (y as f32 - 0.5 * height) / height,
|
||||
# };
|
||||
#
|
||||
# let mut i = 0;
|
||||
# for t in 0..max_iter {
|
||||
# if z.norm() >= 2.0 {
|
||||
# break;
|
||||
# }
|
||||
# z = z * z + c;
|
||||
# i = t;
|
||||
# }
|
||||
# i
|
||||
# }
|
||||
#
|
||||
# // Normalizes color intensity values within RGB range
|
||||
# fn normalize(color: f32, factor: f32) -> u8 {
|
||||
# ((color * factor).powf(0.8) * 255.) as u8
|
||||
# }
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let (width, height) = (1920, 1080);
|
||||
// 为指定宽高的输出图片分配内存
|
||||
let mut img = ImageBuffer::new(width, height);
|
||||
let iterations = 300;
|
||||
|
||||
let c = Complex::new(-0.8, 0.156);
|
||||
|
||||
let pool = ThreadPool::new(num_cpus::get());
|
||||
let (tx, rx) = channel();
|
||||
|
||||
for y in 0..height {
|
||||
let tx = tx.clone();
|
||||
// execute 将每个像素作为单独的作业接收
|
||||
pool.execute(move || for x in 0..width {
|
||||
let i = julia(c, x, y, width, height, iterations);
|
||||
let pixel = wavelength_to_rgb(380 + i * 400 / iterations);
|
||||
tx.send((x, y, pixel)).expect("Could not send data!");
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..(width * height) {
|
||||
let (x, y, pixel) = rx.recv()?;
|
||||
// 使用数据来设置像素的颜色
|
||||
img.put_pixel(x, y, pixel);
|
||||
}
|
||||
|
||||
// 输出图片内容到指定文件中
|
||||
let _ = img.save("output.png")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
165
src/daily-dev.md
Normal file
165
src/daily-dev.md
Normal file
@ -0,0 +1,165 @@
|
||||
# 日常开发常用库
|
||||
|
||||
### 目录索引
|
||||
- [多线程](#多线程)
|
||||
- [Web/HTTP](#webhttp), [SQL客户端](#SQL客户端), [NoSql客户端](#NoSql客户端), [网络通信协议](#网络通信协议), [异步网络编程](#异步网络编程)
|
||||
- [服务发现](#服务发现), [消息队列](#消息队列), [搜索引擎](#搜索引擎)
|
||||
- [编解码](#编解码), [Email](#Email), [前端框架](#前端框架)
|
||||
- [日志监控](#日志监控), [代码Debug](#代码Debug), [性能优化](#性能优化)
|
||||
|
||||
### Web/HTTP
|
||||
* HTTP客户端
|
||||
* [reqwest](https://github.com/seanmonstar/reqwest) 一个简单又强大的HTTP客户端,`reqwest`是目前使用最多的HTTP库
|
||||
|
||||
* Web框架
|
||||
* [axum](https://github.com/tokio-rs/axum) 基于Tokio和Hyper打造,模块化设计较好,目前口碑很好,值得使用Ergonomic and modular web framework built with Tokio, Tower, and Hyper
|
||||
* [Rocket](https://github.com/SergioBenitez/Rocket) 功能强大,API简单的Web框架,但是主要开发者目前因为个人原因无法进行后续开发,未来存在不确定性
|
||||
* [actix-web](https://github.com/actix/actix-web) 性能极高的Web框架,就是团队内部有些问题,未来存在一定的不确定性
|
||||
* 总体来说,上述三个web框架都有很深的用户基础,其实都可以选用,如果让我推荐,顺序如下: `axum` > `Rocket` > `actix-web`。 不过如果你不需要多么完善的web功能,只需要一个性能极高的http库,那么`actix-web`是非常好的选择,它的性能非常非常非常高!
|
||||
|
||||
### 日志监控
|
||||
* 日志
|
||||
[[crates.io](https://crates.io/keywords/log)] [[github](https://github.com/search?q=rust+log)]
|
||||
* [tokio-rs/tracing](https://github.com/tokio-rs/tracing) 强大的日志框架,同时还支持OpenTelemetry格式,无缝打通未来的监控
|
||||
* [rust-lang/log](https://github.com/rust-lang/log) 官方日志库,事实上的API标准, 但是三方库未必遵循
|
||||
* [estk/log4rs](https://github.com/estk/log4rs) 模仿JAVA `logback`和`log4j`实现的日志库, 可配置性较强
|
||||
* 在其它文章中,也许会推荐slog,但是我们不推荐,一个是因为近半年未更新,一个是`slog`自己也推荐使用`tracing`。
|
||||
* 监控
|
||||
* [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-rust) `OpenTelemetry`是现在非常火的可观测性解决方案,提供了协议、API、SDK等核心工具,用于收集监控数据,最后将这些metrics/logs/traces数据写入到`prometheus`, `jaeger`等监控平台中。最主要是,它后台很硬,后面有各大公司作为背书,未来非常看好!
|
||||
* [vectordotdev/vector](https://github.com/vectordotdev/vector) 一个性能很高的数据采集agent,采集本地的日志、监控等数据,发送到远程的kafka、jaeger等数据下沉端,它最大的优点就是能从多种数据源(包括Opentelemetry)收集数据,然后推送到多个数据处理或者存储等下沉端。
|
||||
|
||||
### SQL客户端
|
||||
* 性能对比
|
||||
* [metrics](https://github.com/diesel-rs/metrics) 该库对Rust现存的数据库连接服务进行性能测试,若大家有性能上的需求,值得一看
|
||||
|
||||
* 通用
|
||||
* [launchbadge/sqlx](https://github.com/launchbadge/sqlx) 异步实现、高性能、纯Rust代码的SQL库,支持`PostgreSQL`, `MySQL`, `SQLite`,和 `MSSQL`.
|
||||
|
||||
* ORM
|
||||
* [rbatis/rbatis](https://github.com/rbatis/rbatis) 国内团队开发的ORM,异步、性能高、简单易上手
|
||||
* [diesel-rs/diesel](https://github.com/diesel-rs/diesel) 安全、扩展性强的Rust ORM库,支持`Mysql`、`Postgre`、`SqlLite`
|
||||
|
||||
|
||||
* Mysql
|
||||
* [blackbeam/rust-mysql-simple](https://github.com/blackbeam/rust-mysql-simple) 纯Rust实现的Mysql驱动,提供连接池
|
||||
* [blackbeam/mysql_async](https://github.com/blackbeam/mysql_async) 基于Tokio实现的异步Mysql驱动
|
||||
* 上面两个都是一个团队出品,前者文档更全、star更多,建议使用前者
|
||||
|
||||
|
||||
* Postgre
|
||||
* [sfackler/rust-postgres](https://github.com/sfackler/rust-postgres) 纯Rust实现的Postgre客户端
|
||||
|
||||
* Sqlite
|
||||
* [rusqlite](https://github.com/rusqlite/rusqlite) 用于[Sqlite3](https://www.sqlite.org/index.html)的Rust客户端
|
||||
|
||||
### NoSql客户端
|
||||
|
||||
* Redis
|
||||
* [mitsuhiko/redis-rs](https://github.com/mitsuhiko/redis-rs) 虽然最近更新不太活跃,但是它依然是最好的redis客户端,说实话,我期待更好的,可能这也是Rust生态的未来可期之处吧
|
||||
|
||||
* Canssandra
|
||||
* [krojew/cdrs-tokio](https://github.com/krojew/cdrs-tokio) [[cdrs-tokio](https://crates.io/crates/cdrs-tokio)] 生产可用的Cassandra客户端,异步、纯Rust实现,就是个人项目 + star较少,未来不确定会不会不维护
|
||||
* [scylla-rust-driver](https://github.com/scylladb/scylla-rust-driver) ScyllaDB提供的官方库,支持cql协议,由于背靠大山,未来非常可期
|
||||
|
||||
|
||||
* MongoDB
|
||||
* [mongodb/mongo-rust-driver](https://github.com/mongodb/mongo-rust-driver) 官方MongoDB客户端,闭着眼睛选就对了
|
||||
|
||||
|
||||
### 分布式
|
||||
#### 服务发现
|
||||
- [luncj/etcd-rs](https://github.com/luncj/etcd-rs) 异步实现的Rust etcd客户端,优点是有一定的文档、作者较为活跃,意味着你提问题他可能会回答,不过,如果你不放心,还是考虑使用HTTP的方式访问ETCD
|
||||
|
||||
#### 消息队列
|
||||
* Kafka
|
||||
* [fede1024/rust-rdkafka](https://github.com/fede1024/rust-rdkafka) Rust Kafka客户端,基于C版本的Kafka库[librdkafka]实现,文档较全、功能较为全面
|
||||
* [kafka-rust/kafka-rust](https://github.com/kafka-rust/kafka-rust) 相比上一个库,它算是纯Rust实现,文档还行,支持Kafka0.8.2及以后的版本,但是对于部分0.9版本的特性还不支持。同时有一个问题:最初的作者不维护了,转给了现在的作者,但是感觉好像也不是很活跃
|
||||
* Nats
|
||||
* [nats-io/nats.rs](https://github.com/nats-io/nats.rs) Nats官方提供的客户端
|
||||
|
||||
### 网络、通信协议
|
||||
* Websocket
|
||||
* [snapview/tokio-tungstenite](https://github.com/snapview/tokio-tungstenite) 更适合Web应用使用的生产级Websocket库,它是异步非阻塞的,基于基于下下面的`tungstenite-rs`库和tokio实现
|
||||
* [rust-websocket](https://github.com/websockets-rs/rust-websocket) 老牌Websocket库,提供了客户端和服务器端实现,但是。。。很久没更新了
|
||||
* [snapview/tungstenite-rs](https://github.com/snapview/tungstenite-rs) 轻量级的Websocket流实现,该库更偏底层,例如,你可以用来构建其它网络库
|
||||
* gRPC
|
||||
* [hyperium/tonic](https://github.com/hyperium/tonic) 纯Rust实现的gRPC客户端和服务器端,支持async/await异步调用,文档和示例较为清晰
|
||||
* [tikv/grpc-rs](https://github.com/tikv/grpc-rs) 国产开源之光Tidb团队出品的gRPC框架, 基于C的代码实现, 就是最近好像不是很活跃
|
||||
* 其实这两个实现都很优秀,把`tonic`放在第一位,主要是因为它是纯Rust实现,同时社区也更为活跃,但是并不代表它比`tikv`的更好!
|
||||
* [tokio-rs/prost](https://github.com/tokio-rs/prost) 纯Rust实现的[Protocol Buffers](https://developers.google.com/protocol-buffers/)类库,Prost 支持从 proto2 和 proto3 文件生成简单、实用的代码。
|
||||
* QUIC
|
||||
* [cloudflare/quiche](https://github.com/cloudflare/quiche) 大名鼎鼎`cloudflare`提供的QUIC实现,据说在公司内部重度使用,有了大规模生产级别的验证,非常值得信任,同时该库还实现了HTTP/3
|
||||
* [quinn-rs/quinn](https://github.com/quinn-rs/quinn) 提供异步API调用,纯Rust实现,同时提供了几个有用的网络库
|
||||
* MQTT
|
||||
* [bytebeamio/rumqtt](https://github.com/bytebeamio/rumqtt) MQTT3.1.1/5协议库,同时实现了客户端与服务器端broker
|
||||
* [ntex-rs/ntex-mqtt](https://github.com/ntex-rs/ntex-mqtt) 客户端与服务端框架,支持MQTT3.1.1与5协议
|
||||
* [eclipse/paho.mqtt.rust](https://github.com/eclipse/paho.mqtt.rust) 老牌MQTT框架,对MQTT支持较全, 其它各语言的实现也有
|
||||
|
||||
### 异步网络编程
|
||||
|
||||
* [tokio-rs/tokio](https://github.com/tokio-rs/tokio) 最火的异步网络库,除了复杂上手难度高一些外,没有其它大的问题。同时tokio团队提供了多个非常优秀的Rust库,整个生态欣欣向荣,用户认可度很高
|
||||
* [async-std](https://async.rs/) 跟标准库API很像的异步网络库,相对简单易用,但是貌似开发有些停滞,还有就是功能上不够完善。但是对于普通用户来说,这个库非常值得一试,它在功能和简单易用上取得了很好的平衡
|
||||
* [actix](https://github.com/actix/actix) 基于Actor模型的异步网络库,但这个库的开发貌似已经停滞,他们团队一直在专注于`actix-web`的开发
|
||||
* [mio](https://github.com/tokio-rs/mio) 严格来说,MIO与之前三个不是同一个用途的,MIO = Meta IO,是一个底层IO库,往往用于构建其它网络库,当然如果你对应用网络性能有非常极限的要求, 可以考虑它,因为它的层次比较低,所带来的抽象负担小,所以性能损耗小
|
||||
* 如果你要开发生产级别的项目,我推荐使用`tokio`,稳定可靠,功能丰富,控制粒度细;自己的学习项目或者没有那么严肃的开源项目,我推荐`async-std`,简单好用,值得学习;当你确切知道需要Actor网络模型时,就用`actix`
|
||||
|
||||
|
||||
### 搜索引擎
|
||||
|
||||
* ElasticSearch客户端
|
||||
* [elastic/elasticsearch](https://github.com/elastic/elasticsearch-rs) 官方es客户端,目前第三方的基本都处于停滞状态,所以不管好坏,用呗
|
||||
|
||||
* Rust搜索引擎
|
||||
* [Tantivy](https://github.com/quickwit-inc/tantivy) Tantivy是Rust实现的本地搜索库,功能对标`lucene`,如果你不需要分布式,那么引入tantivy作为自己本地Rust服务的一个搜索,是相当不错的选择,该库作者一直很活跃,而且最近还创立了搜索引擎公司,感觉大有作为. 该库的优点在于纯Rust实现,性能高(lucene的2-3倍),资源占用低(对比java自然不是一个数量级),社区活跃。
|
||||
|
||||
* Rust搜索平台
|
||||
* [quickwit](https://github.com/quickwit-inc/quickwit) 对标ElasticSearch,一个通用目的的分布式搜索平台,目前还在起步阶段(0.2版本),未来非常可期,目前还不建议使用
|
||||
* [MeiliSearch](https://github.com/meilisearch/MeiliSearch) 虽然也是一个搜索平台,但是并不是通用目的的,`MeiliSearch`目标是为终端用户提供边输入边提示的即刻搜索功能,因此是一个轻量级搜索平台,不适用于数据量大时的搜索目的。总之,如果你需要在网页端或者APP为用户提供一个搜索条,然后支持输入容错、前缀搜索时,就可以使用它。
|
||||
|
||||
### 代码Debug
|
||||
* GDB
|
||||
* [gdbgui](https://github.com/cs01/gdbgui) 提供浏览器支持的gdb debug工具,支持C,C++,Rust和Go.
|
||||
* LLDB
|
||||
* [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) — 专门为VSCode设计的LLDB Debug扩展
|
||||
|
||||
### 性能优化
|
||||
* [bheisler/criterion.rs](https://github.com/bheisler/criterion.rs) 比官方提供的benchmark库更好,目前已经成为事实上标准的性能测试工具
|
||||
* [Bytehound](https://github.com/koute/bytehound) Linux下的内存分析工具,可以用来分析:内存泄漏、内存分配、调用栈追踪,甚至它还有一个浏览器UI! 懂的人都懂,性能测试工具的UI服务是多么稀缺和珍贵!
|
||||
* [llogiq/flame](https://github.com/llogiq/flame) 专为Rust打造的火焰图分析工具,可以告诉你程序在哪些代码上花费的时间过多,非常适合用于代码性能瓶颈的分析。与`perf`不同,`flame`库允许你自己定义想要测试的代码片段,只需要在代码前后加上相应的指令即可,非常好用
|
||||
* [sharkdp/hyperfine](https://github.com/sharkdp/hyperfine) 一个命令行benchmark工具,支持任意shell命令,支持缓存清除、预热、多次运行统计分析等,尽量保证结果的准确性
|
||||
|
||||
|
||||
### 多线程
|
||||
* 消息通道channel
|
||||
* [**crossbeam-channel**](https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-channel), 老牌强库,功能较全,性能较强,之前是独立的库,但是后面合并到了`crossbeam`主仓库中
|
||||
* [**flume**](https://github.com/zesterer/flume), 官方给出的性能数据要比crossbeam更好些,但是貌似最近没怎么更新
|
||||
* 并发原语(锁)
|
||||
* [parking_lot](https://crates.io/crates/parking_lot), 社区较为活跃,star较多,更新较为活跃
|
||||
* [spin](https://crates.io/crates/spin), 在多数场景中性能比`parking_lot`高一点,最近没怎么更新
|
||||
* 如果不是追求特别极致的性能,建议选择前者
|
||||
|
||||
### 编解码
|
||||
* [Serde](https://github.com/serde-rs/serde) 一个超高性能的通用序列化/反序列化框架,可以跟多种协议的库联合使用,实现统一编解码格式
|
||||
* CSV
|
||||
* [BurntSushi/rust-csv](https://github.com/BurntSushi/rust-csv) 高性能CSV读写库,支持[Serde](https://github.com/serde-rs/serde)
|
||||
* JSON
|
||||
* [serde-rs/json](https://github.com/serde-rs/json) 快到上天的JSON库,也是Rust事实上的标准JSON库,你也可以使用它的大哥[serde](https://github.com/serde-rs/serde),一个更通用的序列化/反序列化库
|
||||
* MsgPack
|
||||
* [3Hren/msgpack-rust](https://github.com/3Hren/msgpack-rust) 纯Rust实现的MessagePack编解码协议
|
||||
* ProtocolBuffers
|
||||
* [tokio-rs/prost](https://github.com/tokio-rs/prost) tokio出品,基本都属精品,此库也不例外,简单易用,文档详细
|
||||
* [stepancheg/rust-protobuf](https://github.com/stepancheg/rust-protobuf) 纯Rust实现
|
||||
* TOML
|
||||
* [alexcrichton/toml-rs](https://github.com/alexcrichton/toml-rs) TOML编码/解码,可以配合`serde`使用
|
||||
* XML
|
||||
* [tafia/quick-xml](https://github.com/tafia/quick-xml) 高性能XML库,可以配合`serde`使用,文档较为详细
|
||||
* YAML
|
||||
* [dtolnay/serde-yaml](https://github.com/dtolnay/serde-yaml) 使用`serde`编解码`YAML`格式的数据
|
||||
|
||||
### 前端框架
|
||||
* [yewstack/yew](https://github.com/yewstack/yew) 老牌的 WASM 前端应用框架,也是目前 Rust 生态中最出名的前端框架了!
|
||||
* [DioxusLabs/Dioxus](https://github.com/DioxusLabs/dioxus) 一款较新的跨平台 UI 开发框架,类 React 的设计。目前还不稳定,可以观望一段时间。
|
||||
|
||||
|
||||
### Email
|
||||
* [lettre/lettre](https://github.com/lettre/lettre) — Rust SMTP库
|
123
src/database/postgres.md
Normal file
123
src/database/postgres.md
Normal file
@ -0,0 +1,123 @@
|
||||
# Postgres
|
||||
|
||||
### 在数据库中创建表格
|
||||
我们通过 [postgres](https://docs.rs/postgres/0.17.2/postgres/) 来操作数据库。下面的例子有一个前提:数据库 `library` 已经存在,其中用户名和密码都是 `postgres`。
|
||||
|
||||
```rust,editable
|
||||
use postgres::{Client, NoTls, Error};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
// 连接到数据库 library
|
||||
let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", NoTls)?;
|
||||
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS author (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
country VARCHAR NOT NULL
|
||||
)
|
||||
")?;
|
||||
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS book (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR NOT NULL,
|
||||
author_id INTEGER NOT NULL REFERENCES author
|
||||
)
|
||||
")?;
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 插入和查询
|
||||
|
||||
```rust,editable
|
||||
use postgres::{Client, NoTls, Error};
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct Author {
|
||||
_id: i32,
|
||||
name: String,
|
||||
country: String
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let mut client = Client::connect("postgresql://postgres:postgres@localhost/library",
|
||||
NoTls)?;
|
||||
|
||||
let mut authors = HashMap::new();
|
||||
authors.insert(String::from("Chinua Achebe"), "Nigeria");
|
||||
authors.insert(String::from("Rabindranath Tagore"), "India");
|
||||
authors.insert(String::from("Anita Nair"), "India");
|
||||
|
||||
for (key, value) in &authors {
|
||||
let author = Author {
|
||||
_id: 0,
|
||||
name: key.to_string(),
|
||||
country: value.to_string()
|
||||
};
|
||||
|
||||
// 插入数据
|
||||
client.execute(
|
||||
"INSERT INTO author (name, country) VALUES ($1, $2)",
|
||||
&[&author.name, &author.country],
|
||||
)?;
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
for row in client.query("SELECT id, name, country FROM author", &[])? {
|
||||
let author = Author {
|
||||
_id: row.get(0),
|
||||
name: row.get(1),
|
||||
country: row.get(2),
|
||||
};
|
||||
println!("Author {} is from {}", author.name, author.country);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 聚合数据
|
||||
|
||||
下面代码将使用降序的方式列出 [Museum of Modern Art]() 数据库中的前 7999 名艺术家的国籍分布.
|
||||
|
||||
```rust,editable
|
||||
use postgres::{Client, Error, NoTls};
|
||||
|
||||
struct Nation {
|
||||
nationality: String,
|
||||
count: i64,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let mut client = Client::connect(
|
||||
"postgresql://postgres:postgres@127.0.0.1/moma",
|
||||
NoTls,
|
||||
)?;
|
||||
|
||||
for row in client.query
|
||||
("SELECT nationality, COUNT(nationality) AS count
|
||||
FROM artists GROUP BY nationality ORDER BY count DESC", &[])? {
|
||||
|
||||
let (nationality, count) : (Option<String>, Option<i64>)
|
||||
= (row.get (0), row.get (1));
|
||||
|
||||
if nationality.is_some () && count.is_some () {
|
||||
|
||||
let nation = Nation{
|
||||
nationality: nationality.unwrap(),
|
||||
count: count.unwrap(),
|
||||
};
|
||||
println!("{} {}", nation.nationality, nation.count);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
136
src/database/sqlite.md
Normal file
136
src/database/sqlite.md
Normal file
@ -0,0 +1,136 @@
|
||||
# SQLite
|
||||
|
||||
### 创建 SQLite 数据库
|
||||
|
||||
使用 `rusqlite` 可以创建 SQLite 数据库,[Connection::open](https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.open) 会尝试打开一个数据库,若不存在,则创建新的数据库。
|
||||
|
||||
> 这里创建的 `cats.db` 数据库将被后面的例子所使用
|
||||
|
||||
|
||||
```rust,editable
|
||||
use rusqlite::{Connection, Result};
|
||||
use rusqlite::NO_PARAMS;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let conn = Connection::open("cats.db")?;
|
||||
|
||||
conn.execute(
|
||||
"create table if not exists cat_colors (
|
||||
id integer primary key,
|
||||
name text not null unique
|
||||
)",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
conn.execute(
|
||||
"create table if not exists cats (
|
||||
id integer primary key,
|
||||
name text not null,
|
||||
color_id integer not null references cat_colors(id)
|
||||
)",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 插入和查询
|
||||
|
||||
```rust,editable
|
||||
|
||||
use rusqlite::NO_PARAMS;
|
||||
use rusqlite::{Connection, Result};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Cat {
|
||||
name: String,
|
||||
color: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// 打开第一个例子所创建的数据库
|
||||
let conn = Connection::open("cats.db")?;
|
||||
|
||||
let mut cat_colors = HashMap::new();
|
||||
cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]);
|
||||
cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]);
|
||||
|
||||
for (color, catnames) in &cat_colors {
|
||||
// 插入一条数据行
|
||||
conn.execute(
|
||||
"INSERT INTO cat_colors (name) values (?1)",
|
||||
&[&color.to_string()],
|
||||
)?;
|
||||
// 获取最近插入数据行的 id
|
||||
let last_id: String = conn.last_insert_rowid().to_string();
|
||||
|
||||
for cat in catnames {
|
||||
conn.execute(
|
||||
"INSERT INTO cats (name, color_id) values (?1, ?2)",
|
||||
&[&cat.to_string(), &last_id],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT c.name, cc.name from cats c
|
||||
INNER JOIN cat_colors cc
|
||||
ON cc.id = c.color_id;",
|
||||
)?;
|
||||
|
||||
let cats = stmt.query_map(NO_PARAMS, |row| {
|
||||
Ok(Cat {
|
||||
name: row.get(0)?,
|
||||
color: row.get(1)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
for cat in cats {
|
||||
println!("Found cat {:?}", cat);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 使用事务
|
||||
使用 [Connection::transaction](https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.transaction) 可以开始新的事务,若没有对事务进行显式地提交 [Transaction::commit](https://docs.rs/rusqlite/0.27.0/rusqlite/struct.Transaction.html#method.commit),则会进行回滚。
|
||||
|
||||
下面的例子中,`rolled_back_tx` 插入了重复的颜色名称,会发生回滚。
|
||||
|
||||
```rust,editable
|
||||
use rusqlite::{Connection, Result, NO_PARAMS};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// 打开第一个例子所创建的数据库
|
||||
let mut conn = Connection::open("cats.db")?;
|
||||
|
||||
successful_tx(&mut conn)?;
|
||||
|
||||
let res = rolled_back_tx(&mut conn);
|
||||
assert!(res.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn successful_tx(conn: &mut Connection) -> Result<()> {
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
tx.execute("delete from cat_colors", NO_PARAMS)?;
|
||||
tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
|
||||
tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;
|
||||
|
||||
tx.commit()
|
||||
}
|
||||
|
||||
fn rolled_back_tx(conn: &mut Connection) -> Result<()> {
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
tx.execute("delete from cat_colors", NO_PARAMS)?;
|
||||
tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
|
||||
tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;
|
||||
tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
|
||||
|
||||
tx.commit()
|
||||
}
|
||||
```
|
69
src/datetime/duration.md
Normal file
69
src/datetime/duration.md
Normal file
@ -0,0 +1,69 @@
|
||||
# 时间计算和转换
|
||||
|
||||
### 测量某段代码的耗时
|
||||
测量从 [time::Instant::now](https://doc.rust-lang.org/std/time/struct.Instant.html#method.now) 开始所经过的时间 [time::Instant::elapsed](https://doc.rust-lang.org/std/time/struct.Instant.html#method.elapsed).
|
||||
|
||||
```rust,editable
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn main() {
|
||||
let start = Instant::now();
|
||||
expensive_function();
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("Time elapsed in expensive_function() is: {:?}", duration);
|
||||
}
|
||||
```
|
||||
|
||||
### 对日期和时间进行计算
|
||||
使用 [DateTime::checked_add_signed](https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_add_signed) 计算和显示从现在开始两周后的日期和时间,然后再计算一天前的日期 [DateTime::checked_sub_signed](https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_sub_signed)。
|
||||
|
||||
[DateTime::format](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.format) 所支持的转义序列可以在 [chrono::format::strftime](https://docs.rs/chrono/*/chrono/format/strftime/index.html) 找到.
|
||||
|
||||
```rust,editable
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
|
||||
fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> {
|
||||
date_time.checked_sub_signed(Duration::days(1))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let now = Utc::now();
|
||||
println!("{}", now);
|
||||
|
||||
let almost_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2))
|
||||
.and_then(|in_2weeks| in_2weeks.checked_add_signed(Duration::weeks(1)))
|
||||
.and_then(day_earlier);
|
||||
|
||||
match almost_three_weeks_from_now {
|
||||
Some(x) => println!("{}", x),
|
||||
None => eprintln!("Almost three weeks from now overflows!"),
|
||||
}
|
||||
|
||||
match now.checked_add_signed(Duration::max_value()) {
|
||||
Some(x) => println!("{}", x),
|
||||
None => eprintln!("We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center."),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 将本地时间转换成其它时区
|
||||
使用 [offset::Local::now](https://docs.rs/chrono/*/chrono/offset/struct.Local.html#method.now) 获取本地时间并进行显示,接着,使用 [DateTime::from_utc](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.from_utc) 将它转换成 UTC 标准时间。最后,再使用 [offset::FixedOffset](https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html) 将 UTC 时间转换成 UTC+8 和 UTC-2 的时间。
|
||||
|
||||
```rust,editable
|
||||
use chrono::{DateTime, FixedOffset, Local, Utc};
|
||||
|
||||
fn main() {
|
||||
let local_time = Local::now();
|
||||
let utc_time = DateTime::<Utc>::from_utc(local_time.naive_utc(), Utc);
|
||||
let china_timezone = FixedOffset::east(8 * 3600);
|
||||
let rio_timezone = FixedOffset::west(2 * 3600);
|
||||
println!("Local time now is {}", local_time);
|
||||
println!("UTC time now is {}", utc_time);
|
||||
println!(
|
||||
"Time in Hong Kong now is {}",
|
||||
utc_time.with_timezone(&china_timezone)
|
||||
);
|
||||
println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone));
|
||||
}
|
||||
```
|
114
src/datetime/parsing.md
Normal file
114
src/datetime/parsing.md
Normal file
@ -0,0 +1,114 @@
|
||||
# 解析和显示
|
||||
|
||||
### 检查日期和时间
|
||||
通过 [DateTime](https://docs.rs/chrono/*/chrono/struct.DateTime.html) 获取当前的 UTC 时间:
|
||||
- [Timelike](https://docs.rs/chrono/*/chrono/trait.Timelike.html), 时/分/秒
|
||||
- [Datelike](https://docs.rs/chrono/*/chrono/trait.Datelike.html), 年/月/日
|
||||
|
||||
```rust,editable
|
||||
use chrono::{Datelike, Timelike, Utc};
|
||||
|
||||
fn main() {
|
||||
let now = Utc::now();
|
||||
|
||||
let (is_pm, hour) = now.hour12();
|
||||
println!(
|
||||
"The current UTC time is {:02}:{:02}:{:02} {}",
|
||||
hour,
|
||||
now.minute(),
|
||||
now.second(),
|
||||
if is_pm { "PM" } else { "AM" }
|
||||
);
|
||||
println!(
|
||||
"And there have been {} seconds since midnight",
|
||||
now.num_seconds_from_midnight()
|
||||
);
|
||||
|
||||
let (is_common_era, year) = now.year_ce();
|
||||
println!(
|
||||
"The current UTC date is {}-{:02}-{:02} {:?} ({})",
|
||||
year,
|
||||
now.month(),
|
||||
now.day(),
|
||||
now.weekday(),
|
||||
if is_common_era { "CE" } else { "BCE" }
|
||||
);
|
||||
println!(
|
||||
"And the Common Era began {} days ago",
|
||||
now.num_days_from_ce()
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 日期和时间戳的相互转换
|
||||
|
||||
```rust,editable
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
|
||||
fn main() {
|
||||
// 生成一个具体的日期时间
|
||||
let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44);
|
||||
println!(
|
||||
"Number of seconds between 1970-01-01 00:00:00 and {} is {}.",
|
||||
// 打印日期和日期对应的时间戳
|
||||
date_time, date_time.timestamp());
|
||||
|
||||
// 计算从 1970 1月1日 0:00:00 UTC 开始,10亿秒后是什么日期时间
|
||||
let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0);
|
||||
println!(
|
||||
"Date after a billion seconds since 1970-01-01 00:00:00 was {}.",
|
||||
date_time_after_a_billion_seconds);
|
||||
}
|
||||
```
|
||||
|
||||
### 显示格式化的日期和时间
|
||||
通过 [Utc::now](https://docs.rs/chrono/*/chrono/offset/struct.Utc.html#method.now) 可以获取当前的 UTC 时间。
|
||||
|
||||
```rust,editable
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
fn main() {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
|
||||
println!("UTC now is: {}", now);
|
||||
// 使用 RFC 2822 格式显示当前时间
|
||||
println!("UTC now in RFC 2822 is: {}", now.to_rfc2822());
|
||||
// 使用 RFC 3339 格式显示当前时间
|
||||
println!("UTC now in RFC 3339 is: {}", now.to_rfc3339());
|
||||
// 使用自定义格式显示当前时间
|
||||
println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y"));
|
||||
}
|
||||
```
|
||||
|
||||
### 将字符串解析为 DateTime 结构体
|
||||
我们可以将多种格式的日期时间字符串转换成 [DateTime](https://docs.rs/chrono/*/chrono/struct.DateTime.html) 结构体。[DateTime::parse_from_str](https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.parse_from_str) 使用的转义序列可以在 [chrono::format::strftime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html) 找到.
|
||||
|
||||
只有当能唯一的标识出日期和时间时,才能创建 `DateTime`。如果要在没有时区的情况下解析日期或时间,你需要使用 [`NativeDate`](https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html) 等函数。
|
||||
|
||||
```rust,editable
|
||||
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use chrono::format::ParseError;
|
||||
|
||||
|
||||
fn main() -> Result<(), ParseError> {
|
||||
let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?;
|
||||
println!("{}", rfc2822);
|
||||
|
||||
let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?;
|
||||
println!("{}", rfc3339);
|
||||
|
||||
let custom = DateTime::parse_from_str("5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")?;
|
||||
println!("{}", custom);
|
||||
|
||||
let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?;
|
||||
println!("{}", time_only);
|
||||
|
||||
let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?;
|
||||
println!("{}", date_only);
|
||||
|
||||
let no_timezone = NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")?;
|
||||
println!("{}", no_timezone);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
1
src/dev/intro.md
Normal file
1
src/dev/intro.md
Normal file
@ -0,0 +1 @@
|
||||
# 开发调试
|
1
src/dev/logs.md
Normal file
1
src/dev/logs.md
Normal file
@ -0,0 +1 @@
|
||||
# 日志
|
1
src/dev/profile.md
Normal file
1
src/dev/profile.md
Normal file
@ -0,0 +1 @@
|
||||
# 性能分析
|
192
src/devtools/build-tools.md
Normal file
192
src/devtools/build-tools.md
Normal file
@ -0,0 +1,192 @@
|
||||
# 构建时工具
|
||||
本章节的内容是关于构建工具的,如果大家没有听说过 `build.rs` 文件,强烈建议先看看[这里](https://course.rs/cargo/reference/build-script/intro.html)了解下何为构建工具。
|
||||
|
||||
### 编译并静态链接一个 C 库
|
||||
|
||||
[cc](https://docs.rs/cc/latest/cc/) 包能帮助我们更好地跟 C/C++/汇编进行交互:它提供了简单的 API 可以将外部的库编译成静态库( .a ),然后通过 `rustc` 进行静态链接。
|
||||
|
||||
下面的例子中,我们将在 Rust 代码中使用 C 的代码: *src/hello.c*。在开始编译 Rust 的项目代码前,`build.rs` 构建脚本将先被执行。通过 cc 包,一个静态的库可以被生成( *libhello.a* ),然后该库将被 Rust的代码所使用:通过 `extern` 声明外部函数签名的方式来使用。
|
||||
|
||||
由于例子中的 C 代码很简单,因此只需要将一个文件传递给 [cc::Build](https://docs.rs/cc/*/cc/struct.Build.html)。如果大家需要更复杂的构建,`cc::Build` 还提供了通过 [include](https://docs.rs/cc/*/cc/struct.Build.html#method.include) 来包含路径的方式,以及额外的编译标志( [flags](https://docs.rs/cc/1.0.73/cc/struct.Build.html#method.flag) )。
|
||||
|
||||
*Cargo.toml*
|
||||
|
||||
```toml
|
||||
[package]
|
||||
...
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[dependencies]
|
||||
error-chain = "0.11"
|
||||
```
|
||||
|
||||
*build.rs*
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("src/hello.c")
|
||||
.compile("hello"); // outputs `libhello.a`
|
||||
}
|
||||
```
|
||||
|
||||
*src/hello.c*
|
||||
|
||||
```C
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
void hello() {
|
||||
printf("Hello from C!\n");
|
||||
}
|
||||
|
||||
void greet(const char* name) {
|
||||
printf("Hello, %s!\n", name);
|
||||
}
|
||||
```
|
||||
|
||||
*src/main.rs*
|
||||
|
||||
```rust
|
||||
use error_chain::error_chain;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
NulError(::std::ffi::NulError);
|
||||
Io(::std::io::Error);
|
||||
}
|
||||
}
|
||||
fn prompt(s: &str) -> Result<String> {
|
||||
use std::io::Write;
|
||||
print!("{}", s);
|
||||
std::io::stdout().flush()?;
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input)?;
|
||||
Ok(input.trim().to_string())
|
||||
}
|
||||
|
||||
extern {
|
||||
fn hello();
|
||||
fn greet(name: *const c_char);
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
unsafe { hello() }
|
||||
let name = prompt("What's your name? ")?;
|
||||
let c_name = CString::new(name)?;
|
||||
unsafe { greet(c_name.as_ptr()) }
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 编译并静态链接一个 C++ 库
|
||||
链接到 C++ 库跟之前的方式非常相似。主要的区别在于链接到 C++ 库时,你需要通过构建方法 [cpp(true)](https://docs.rs/cc/*/cc/struct.Build.html#method.cpp) 来指定一个 C++ 编译器,然后在 C++ 的代码顶部添加 `extern "C"` 来阻止 C++ 编译器对库名进行名称重整( name mangling )。
|
||||
|
||||
*Cargo.toml*
|
||||
|
||||
```toml
|
||||
[package]
|
||||
...
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
```
|
||||
|
||||
*build.rs*
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.file("src/foo.cpp")
|
||||
.compile("foo");
|
||||
}
|
||||
```
|
||||
|
||||
*src/foo.cpp*
|
||||
|
||||
```c++
|
||||
extern "C" {
|
||||
int multiply(int x, int y);
|
||||
}
|
||||
|
||||
int multiply(int x, int y) {
|
||||
return x*y;
|
||||
}
|
||||
```
|
||||
|
||||
*src/main.rs*
|
||||
|
||||
```rust
|
||||
extern {
|
||||
fn multiply(x : i32, y : i32) -> i32;
|
||||
}
|
||||
|
||||
fn main(){
|
||||
unsafe {
|
||||
println!("{}", multiply(5,7));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 为 C 库创建自定义的 define
|
||||
|
||||
[cc::Build::define](https://docs.rs/cc/*/cc/struct.Build.html#method.define) 可以让我们使用自定义的 define 来构建 C 库。
|
||||
|
||||
以下示例在构建脚本 `build.rs` 中动态定义了一个 define,然后在运行时打印出 **Welcome to foo - version 1.0.2**。Cargo 会设置一些[环境变量](https://doc.rust-lang.org/cargo/reference/environment-variables.html),它们对于自定义的 define 会有所帮助。
|
||||
|
||||
*Cargo.toml*
|
||||
|
||||
```toml
|
||||
[package]
|
||||
...
|
||||
version = "1.0.2"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
```
|
||||
|
||||
*build.rs*
|
||||
```rust
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.define("APP_NAME", "\"foo\"")
|
||||
.define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str())
|
||||
.define("WELCOME", None)
|
||||
.file("src/foo.c")
|
||||
.compile("foo");
|
||||
}
|
||||
```
|
||||
|
||||
*src/foo.c*
|
||||
```C
|
||||
#include <stdio.h>
|
||||
|
||||
void print_app_info() {
|
||||
#ifdef WELCOME
|
||||
printf("Welcome to ");
|
||||
#endif
|
||||
printf("%s - version %s\n", APP_NAME, VERSION);
|
||||
}
|
||||
```
|
||||
|
||||
*src/main.rs*
|
||||
```rust
|
||||
extern {
|
||||
fn print_app_info();
|
||||
}
|
||||
|
||||
fn main(){
|
||||
unsafe {
|
||||
print_app_info();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
140
src/devtools/config-log.md
Normal file
140
src/devtools/config-log.md
Normal file
@ -0,0 +1,140 @@
|
||||
# 配置日志
|
||||
|
||||
### 为每个模块开启独立的日志级别
|
||||
下面代码创建了模块 `foo` 和嵌套模块 `foo::bar`,并通过 [RUST_LOG](https://docs.rs/env_logger/*/env_logger/#enabling-logging) 环境变量对各自的日志级别进行了控制。
|
||||
|
||||
```rust,editable
|
||||
mod foo {
|
||||
mod bar {
|
||||
pub fn run() {
|
||||
log::warn!("[bar] warn");
|
||||
log::info!("[bar] info");
|
||||
log::debug!("[bar] debug");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
log::warn!("[foo] warn");
|
||||
log::info!("[foo] info");
|
||||
log::debug!("[foo] debug");
|
||||
bar::run();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
log::warn!("[root] warn");
|
||||
log::info!("[root] info");
|
||||
log::debug!("[root] debug");
|
||||
foo::run();
|
||||
}
|
||||
```
|
||||
|
||||
要让环境变量生效,首先需要通过 `env_logger::init()` 开启相关的支持。然后通过以下命令来运行程序:
|
||||
```shell
|
||||
RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test
|
||||
```
|
||||
|
||||
此时的默认日志级别被设置为 `warn`,但我们还将 `foo` 模块级别设置为 `info`, `foo::bar` 模块日志级别设置为 `debug`。
|
||||
|
||||
```bash
|
||||
WARN:test: [root] warn
|
||||
WARN:test::foo: [foo] warn
|
||||
INFO:test::foo: [foo] info
|
||||
WARN:test::foo::bar: [bar] warn
|
||||
INFO:test::foo::bar: [bar] info
|
||||
DEBUG:test::foo::bar: [bar] debug
|
||||
```
|
||||
|
||||
### 使用自定义环境变量来设置日志
|
||||
|
||||
[Builder](https://docs.rs/env_logger/*/env_logger/struct.Builder.html) 将对日志进行配置,以下代码使用 `MY_APP_LOG` 来替代 `RUST_LOG` 环境变量:
|
||||
|
||||
```rust,editable
|
||||
use std::env;
|
||||
use env_logger::Builder;
|
||||
|
||||
fn main() {
|
||||
Builder::new()
|
||||
.parse(&env::var("MY_APP_LOG").unwrap_or_default())
|
||||
.init();
|
||||
|
||||
log::info!("informational message");
|
||||
log::warn!("warning message");
|
||||
log::error!("this is an error {}", "message");
|
||||
}
|
||||
```
|
||||
|
||||
### 在日志中包含时间戳
|
||||
|
||||
```rust,editable
|
||||
use std::io::Write;
|
||||
use chrono::Local;
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
|
||||
fn main() {
|
||||
Builder::new()
|
||||
.format(|buf, record| {
|
||||
writeln!(buf,
|
||||
"{} [{}] - {}",
|
||||
Local::now().format("%Y-%m-%dT%H:%M:%S"),
|
||||
record.level(),
|
||||
record.args()
|
||||
)
|
||||
})
|
||||
.filter(None, LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
log::warn!("warn");
|
||||
log::info!("info");
|
||||
log::debug!("debug");
|
||||
}
|
||||
```
|
||||
|
||||
以下是 `stderr` 的输出:
|
||||
```shell
|
||||
2022-03-22T21:57:06 [WARN] - warn
|
||||
2022-03-22T21:57:06 [INFO] - info
|
||||
```
|
||||
|
||||
### 将日志输出到指定文件
|
||||
[log4rs](https://docs.rs/log4rs/) 可以帮我们将日志输出指定的位置,它可以使用外部 YAML 文件或 `builder` 的方式进行配置。
|
||||
|
||||
```rust,editable
|
||||
# use error_chain::error_chain;
|
||||
|
||||
use log::LevelFilter;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use log4rs::config::{Appender, Config, Root};
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# LogConfig(log4rs::config::Errors);
|
||||
# SetLogger(log::SetLoggerError);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// 创建日志配置,并指定输出的位置
|
||||
let logfile = FileAppender::builder()
|
||||
// 编码模式的详情参见: https://docs.rs/log4rs/1.0.0/log4rs/encode/pattern/index.html
|
||||
.encoder(Box::new(PatternEncoder::new("{l} - {m}\n")))
|
||||
.build("log/output.log")?;
|
||||
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
||||
.build(Root::builder()
|
||||
.appender("logfile")
|
||||
.build(LevelFilter::Info))?;
|
||||
|
||||
log4rs::init_config(config)?;
|
||||
|
||||
log::info!("Hello, world!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
```
|
127
src/devtools/log.md
Normal file
127
src/devtools/log.md
Normal file
@ -0,0 +1,127 @@
|
||||
# 日志
|
||||
|
||||
## log 包
|
||||
[log](https://docs.rs/crate/log/0.4.16) 提供了日志相关的实用工具。
|
||||
|
||||
### 在控制台打印 debug 信息
|
||||
`env_logger` 通过环境变量来配置日志。[log::debug!](https://docs.rs/log/0.4.16/log/macro.debug.html) 使用起来跟 [std::fmt](https://doc.rust-lang.org/std/fmt/) 中的格式化字符串很像。
|
||||
|
||||
```rust
|
||||
fn execute_query(query: &str) {
|
||||
log::debug!("Executing query: {}", query);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
execute_query("DROP TABLE students");
|
||||
}
|
||||
```
|
||||
|
||||
如果大家运行代码,会发现没有任何日志输出,原因是默认的日志级别是 `error`,因此我们需要通过 `RUST_LOG` 环境变量来设置下新的日志级别:
|
||||
```shell
|
||||
$ RUST_LOG=debug cargo run
|
||||
```
|
||||
|
||||
然后你将成功看到以下输出:
|
||||
```shell
|
||||
DEBUG:main: Executing query: DROP TABLE students
|
||||
```
|
||||
|
||||
### 将错误日志输出到控制台
|
||||
下面我们通过 [log::error!](https://docs.rs/log/0.4.16/log/macro.error.html) 将错误日志输出到标准错误 `stderr`。
|
||||
|
||||
```rust
|
||||
fn execute_query(_query: &str) -> Result<(), &'static str> {
|
||||
Err("I'm afraid I can't do that")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let response = execute_query("DROP TABLE students");
|
||||
if let Err(err) = response {
|
||||
log::error!("Failed to execute query: {}", err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 将错误输出到标准输出 stdout
|
||||
默认的错误会输出到标准错误输出 `stderr`,下面我们通过自定的配置来让错误输出到标准输出 `stdout`。
|
||||
|
||||
```rust,editable
|
||||
use env_logger::{Builder, Target};
|
||||
|
||||
fn main() {
|
||||
Builder::new()
|
||||
.target(Target::Stdout)
|
||||
.init();
|
||||
|
||||
log::error!("This error has been printed to Stdout");
|
||||
}
|
||||
```
|
||||
|
||||
### 使用自定义 logger
|
||||
下面的代码将实现一个自定义 logger `ConsoleLogger`,输出到标准输出 `stdout`。为了使用日志宏,`ConsoleLogger` 需要实现 [log::Log](https://docs.rs/log/*/log/trait.Log.html) 特征,然后使用 [log::set_logger](https://docs.rs/log/*/log/fn.set_logger.html) 来安装使用。
|
||||
|
||||
```rust,editable
|
||||
use log::{Record, Level, Metadata, LevelFilter, SetLoggerError};
|
||||
|
||||
static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger;
|
||||
|
||||
struct ConsoleLogger;
|
||||
|
||||
impl log::Log for ConsoleLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= Level::Info
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
println!("Rust says: {} - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), SetLoggerError> {
|
||||
log::set_logger(&CONSOLE_LOGGER)?;
|
||||
log::set_max_level(LevelFilter::Info);
|
||||
|
||||
log::info!("hello log");
|
||||
log::warn!("warning");
|
||||
log::error!("oops");
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 输出到 Unix syslog
|
||||
下面的代码将使用 [syslog](https://docs.rs/crate/syslog/6.0.1) 包将日志输出到 [Unix Syslog](https://www.gnu.org/software/libc/manual/html_node/Overview-of-Syslog.html).
|
||||
|
||||
```rust,editable
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(target_os = "linux")]
|
||||
use syslog::{Facility, Error};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn main() -> Result<(), Error> {
|
||||
// 初始化 logger
|
||||
syslog::init(Facility::LOG_USER,
|
||||
log::LevelFilter::Debug,
|
||||
// 可选的应用名称
|
||||
Some("My app name"))?;
|
||||
log::debug!("this is a debug {}", "message");
|
||||
log::error!("this is an error!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn main() {
|
||||
println!("So far, only Linux systems are supported.");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## tracing
|
||||
@todo
|
186
src/devtools/version.md
Normal file
186
src/devtools/version.md
Normal file
@ -0,0 +1,186 @@
|
||||
# 版本号
|
||||
|
||||
### 解析并增加版本号
|
||||
下面例子使用 [Version::parse](https://docs.rs/semver/*/semver/struct.Version.html#method.parse) 将一个字符串转换成 [semver::Version](https://docs.rs/semver/*/semver/struct.Version.html) 版本号,然后将它的 patch, minor, major 版本号都增加 1。
|
||||
|
||||
注意,为了符合[语义化版本的说明](http://semver.org),增加 `minor` 版本时,`patch` 版本会被重设为 `0`,当增加 `major` 版本时,`minor` 和 `patch` 都将被重设为 `0`。
|
||||
|
||||
```rust,editable
|
||||
use semver::{Version, SemVerError};
|
||||
|
||||
fn main() -> Result<(), SemVerError> {
|
||||
let mut parsed_version = Version::parse("0.2.6")?;
|
||||
|
||||
assert_eq!(
|
||||
parsed_version,
|
||||
Version {
|
||||
major: 0,
|
||||
minor: 2,
|
||||
patch: 6,
|
||||
pre: vec![],
|
||||
build: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
parsed_version.increment_patch();
|
||||
assert_eq!(parsed_version.to_string(), "0.2.7");
|
||||
println!("New patch release: v{}", parsed_version);
|
||||
|
||||
parsed_version.increment_minor();
|
||||
assert_eq!(parsed_version.to_string(), "0.3.0");
|
||||
println!("New minor release: v{}", parsed_version);
|
||||
|
||||
parsed_version.increment_major();
|
||||
assert_eq!(parsed_version.to_string(), "1.0.0");
|
||||
println!("New major release: v{}", parsed_version);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 解析一个复杂的版本号字符串
|
||||
这里的版本号字符串还将包含 `SemVer` 中定义的预发布和构建元信息。
|
||||
|
||||
值得注意的是,为了符合 `SemVer` 的规则,构建元信息虽然会被解析,但是在做版本号比较时,该信息会被忽略。换而言之,即使两个版本号的构建字符串不同,它们的版本号依然可能相同。
|
||||
|
||||
```rust,editable
|
||||
use semver::{Identifier, Version, SemVerError};
|
||||
|
||||
fn main() -> Result<(), SemVerError> {
|
||||
let version_str = "1.0.49-125+g72ee7853";
|
||||
let parsed_version = Version::parse(version_str)?;
|
||||
|
||||
assert_eq!(
|
||||
parsed_version,
|
||||
Version {
|
||||
major: 1,
|
||||
minor: 0,
|
||||
patch: 49,
|
||||
pre: vec![Identifier::Numeric(125)],
|
||||
build: vec![],
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parsed_version.build,
|
||||
vec![Identifier::AlphaNumeric(String::from("g72ee7853"))]
|
||||
);
|
||||
|
||||
let serialized_version = parsed_version.to_string();
|
||||
assert_eq!(&serialized_version, version_str);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 检查给定的版本号是否是预发布
|
||||
下面例子给出两个版本号,然后通过 [is_prerelease](https://docs.rs/semver/1.0.7/semver/struct.Version.html#method.is_prerelease) 判断哪个是预发布的版本号。
|
||||
|
||||
```rust,editable
|
||||
use semver::{Version, SemVerError};
|
||||
|
||||
fn main() -> Result<(), SemVerError> {
|
||||
let version_1 = Version::parse("1.0.0-alpha")?;
|
||||
let version_2 = Version::parse("1.0.0")?;
|
||||
|
||||
assert!(version_1.is_prerelease());
|
||||
assert!(!version_2.is_prerelease());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 找出给定范围内的最新版本
|
||||
下面例子给出了一个版本号列表,我们需要找到其中最新的版本。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# SemVer(semver::SemVerError);
|
||||
# SemVerReq(semver::ReqParseError);
|
||||
# }
|
||||
3}
|
||||
|
||||
fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result<Option<Version>>
|
||||
where
|
||||
I: IntoIterator<Item = &'a str>,
|
||||
{
|
||||
let vreq = VersionReq::parse(version_req_str)?;
|
||||
|
||||
Ok(
|
||||
iterable
|
||||
.into_iter()
|
||||
.filter_map(|s| Version::parse(s).ok())
|
||||
.filter(|s| vreq.matches(s))
|
||||
.max(),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
assert_eq!(
|
||||
find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?,
|
||||
Some(Version::parse("1.0.0")?)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_max_matching_version(
|
||||
">1.2.3-alpha.3",
|
||||
vec![
|
||||
"1.2.3-alpha.3",
|
||||
"1.2.3-alpha.4",
|
||||
"1.2.3-alpha.10",
|
||||
"1.2.3-beta.4",
|
||||
"3.4.5-alpha.9",
|
||||
]
|
||||
)?,
|
||||
Some(Version::parse("1.2.3-beta.4")?)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 检查外部命令的版本号兼容性
|
||||
下面将通过 [Command](https://doc.rust-lang.org/std/process/struct.Command.html) 来执行系统命令 `git --version`,并对该系统命令返回的 `git` 版本号进行解析。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use std::process::Command;
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# Utf8(std::string::FromUtf8Error);
|
||||
# SemVer(semver::SemVerError);
|
||||
# SemVerReq(semver::ReqParseError);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let version_constraint = "> 1.12.0";
|
||||
let version_test = VersionReq::parse(version_constraint)?;
|
||||
let output = Command::new("git").arg("--version").output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
error_chain::bail!("Command executed with failing error code");
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let version = stdout.split(" ").last().ok_or_else(|| {
|
||||
"Invalid command output"
|
||||
})?;
|
||||
let parsed_version = Version::parse(version)?;
|
||||
|
||||
if !version_test.matches(&parsed_version) {
|
||||
error_chain::bail!("Command version lower than minimum supported version (found {}, need {})",
|
||||
parsed_version, version_constraint);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
277
src/encoding/csv.md
Normal file
277
src/encoding/csv.md
Normal file
@ -0,0 +1,277 @@
|
||||
# CSV
|
||||
|
||||
## 读取 CSV 记录
|
||||
我们可以将标准的 CSV 记录值读取到 [csv::StringRecord](https://docs.rs/csv/*/csv/struct.StringRecord.html) 中,但是该数据结构期待合法的 UTF8 数据行,你还可以使用 [csv::ByteRecord](https://docs.rs/csv/1.1.6/csv/struct.ByteRecord.html) 来读取非 UTF8 数据。
|
||||
|
||||
```rust,editable
|
||||
use csv::Error;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let csv = "year,make,model,description
|
||||
1948,Porsche,356,Luxury sports car
|
||||
1967,Ford,Mustang fastback 1967,American car";
|
||||
|
||||
let mut reader = csv::Reader::from_reader(csv.as_bytes());
|
||||
for record in reader.records() {
|
||||
let record = record?;
|
||||
println!(
|
||||
"In {}, {} built the {} model. It is a {}.",
|
||||
&record[0],
|
||||
&record[1],
|
||||
&record[2],
|
||||
&record[3]
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
还可以使用 [`serde`](https://docs.rs/serde/1.0.136/serde/) 将数据反序列化成一个强类型的结构体。
|
||||
|
||||
```rust,editable
|
||||
use serde::Deserialize;
|
||||
#[derive(Deserialize)]
|
||||
struct Record {
|
||||
year: u16,
|
||||
make: String,
|
||||
model: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), csv::Error> {
|
||||
let csv = "year,make,model,description
|
||||
1948,Porsche,356,Luxury sports car
|
||||
1967,Ford,Mustang fastback 1967,American car";
|
||||
|
||||
let mut reader = csv::Reader::from_reader(csv.as_bytes());
|
||||
|
||||
for record in reader.deserialize() {
|
||||
let record: Record = record?;
|
||||
println!(
|
||||
"In {}, {} built the {} model. It is a {}.",
|
||||
record.year,
|
||||
record.make,
|
||||
record.model,
|
||||
record.description
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 读取使用了不同分隔符的 CSV 记录
|
||||
下面的例子将读取使用了 `tab` 作为分隔符的 CSV 记录。
|
||||
|
||||
```rust,editable
|
||||
use csv::Error;
|
||||
use serde::Deserialize;
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Record {
|
||||
name: String,
|
||||
place: String,
|
||||
#[serde(deserialize_with = "csv::invalid_option")]
|
||||
id: Option<u64>,
|
||||
}
|
||||
|
||||
use csv::ReaderBuilder;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let data = "name\tplace\tid
|
||||
Mark\tMelbourne\t46
|
||||
Ashley\tZurich\t92";
|
||||
|
||||
let mut reader = ReaderBuilder::new().delimiter(b'\t').from_reader(data.as_bytes());
|
||||
for result in reader.deserialize::<Record>() {
|
||||
println!("{:?}", result?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 基于给定条件来过滤 CSV 记录
|
||||
|
||||
```rust,editable
|
||||
use error_chain::error_chain;
|
||||
|
||||
use std::io;
|
||||
|
||||
error_chain!{
|
||||
foreign_links {
|
||||
Io(std::io::Error);
|
||||
CsvError(csv::Error);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let query = "CA";
|
||||
let data = "\
|
||||
City,State,Population,Latitude,Longitude
|
||||
Kenai,AK,7610,60.5544444,-151.2583333
|
||||
Oakman,AL,,33.7133333,-87.3886111
|
||||
Sandfort,AL,,32.3380556,-85.2233333
|
||||
West Hollywood,CA,37031,34.0900000,-118.3608333";
|
||||
|
||||
let mut rdr = csv::ReaderBuilder::new().from_reader(data.as_bytes());
|
||||
let mut wtr = csv::Writer::from_writer(io::stdout());
|
||||
|
||||
wtr.write_record(rdr.headers()?)?;
|
||||
|
||||
for result in rdr.records() {
|
||||
let record = result?;
|
||||
if record.iter().any(|field| field == query) {
|
||||
wtr.write_record(&record)?;
|
||||
}
|
||||
}
|
||||
|
||||
wtr.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 序列化为 CSV
|
||||
|
||||
下面例子展示了如何将 Rust 类型序列化为 CSV。
|
||||
|
||||
```rust,editable
|
||||
use std::io;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut wtr = csv::Writer::from_writer(io::stdout());
|
||||
|
||||
wtr.write_record(&["Name", "Place", "ID"])?;
|
||||
|
||||
wtr.serialize(("Mark", "Sydney", 87))?;
|
||||
wtr.serialize(("Ashley", "Dublin", 32))?;
|
||||
wtr.serialize(("Akshat", "Delhi", 11))?;
|
||||
|
||||
wtr.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 serde 序列化为 CSV
|
||||
下面例子将自定义数据结构通过 `serde` 序列化 CSV。
|
||||
```rust,editable
|
||||
use error_chain::error_chain;
|
||||
use serde::Serialize;
|
||||
use std::io;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
IOError(std::io::Error);
|
||||
CSVError(csv::Error);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Record<'a> {
|
||||
name: &'a str,
|
||||
place: &'a str,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut wtr = csv::Writer::from_writer(io::stdout());
|
||||
|
||||
let rec1 = Record { name: "Mark", place: "Melbourne", id: 56};
|
||||
let rec2 = Record { name: "Ashley", place: "Sydney", id: 64};
|
||||
let rec3 = Record { name: "Akshat", place: "Delhi", id: 98};
|
||||
|
||||
wtr.serialize(rec1)?;
|
||||
wtr.serialize(rec2)?;
|
||||
wtr.serialize(rec3)?;
|
||||
|
||||
wtr.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### CSV 列转换
|
||||
下面代码将包含有颜色名和十六进制颜色的 CSV 文件转换为包含颜色名和 rgb 颜色。这里使用 `csv` 包对 CSV 文件进行读写,然后用 `serde` 进行序列化和反序列化。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
use csv::{Reader, Writer};
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# CsvError(csv::Error);
|
||||
# ParseInt(std::num::ParseIntError);
|
||||
# CsvInnerError(csv::IntoInnerError<Writer<Vec<u8>>>);
|
||||
# IO(std::fmt::Error);
|
||||
# UTF8(std::string::FromUtf8Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HexColor {
|
||||
red: u8,
|
||||
green: u8,
|
||||
blue: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Row {
|
||||
color_name: String,
|
||||
color: HexColor,
|
||||
}
|
||||
|
||||
impl FromStr for HexColor {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(hex_color: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let trimmed = hex_color.trim_matches('#');
|
||||
if trimmed.len() != 6 {
|
||||
Err("Invalid length of hex string".into())
|
||||
} else {
|
||||
Ok(HexColor {
|
||||
red: u8::from_str_radix(&trimmed[..2], 16)?,
|
||||
green: u8::from_str_radix(&trimmed[2..4], 16)?,
|
||||
blue: u8::from_str_radix(&trimmed[4..6], 16)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for HexColor {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let data = "color_name,color
|
||||
red,#ff0000
|
||||
green,#00ff00
|
||||
blue,#0000FF
|
||||
periwinkle,#ccccff
|
||||
magenta,#ff00ff"
|
||||
.to_owned();
|
||||
let mut out = Writer::from_writer(vec![]);
|
||||
let mut reader = Reader::from_reader(data.as_bytes());
|
||||
for result in reader.deserialize::<Row>() {
|
||||
let res = result?;
|
||||
out.serialize((
|
||||
res.color_name,
|
||||
res.color.red,
|
||||
res.color.green,
|
||||
res.color.blue,
|
||||
))?;
|
||||
}
|
||||
let written = String::from_utf8(out.into_inner()?)?;
|
||||
assert_eq!(Some("magenta,255,0,255"), written.lines().last());
|
||||
println!("{}", written);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
1
src/encoding/intro.md
Normal file
1
src/encoding/intro.md
Normal file
@ -0,0 +1 @@
|
||||
# 编解码
|
1
src/encoding/json.md
Normal file
1
src/encoding/json.md
Normal file
@ -0,0 +1 @@
|
||||
# JSON
|
1
src/encoding/protobuf.md
Normal file
1
src/encoding/protobuf.md
Normal file
@ -0,0 +1 @@
|
||||
# protobuf
|
106
src/encoding/strings.md
Normal file
106
src/encoding/strings.md
Normal file
@ -0,0 +1,106 @@
|
||||
# 字符编码
|
||||
|
||||
### 百分号编码( Percent encoding )
|
||||
[百分号编码](https://en.wikipedia.org/wiki/Percent-encoding)又称 URL 编码。
|
||||
|
||||
[percent-encoding](https://docs.rs/crate/percent-encoding/2.1.0) 包提供了两个函数:`utf8_percent_encode` 函数用于编码、`percent_decode` 用于解码。
|
||||
|
||||
```rust,editable
|
||||
use percent_encoding::{utf8_percent_encode, percent_decode, AsciiSet, CONTROLS};
|
||||
use std::str::Utf8Error;
|
||||
|
||||
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
|
||||
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
|
||||
|
||||
fn main() -> Result<(), Utf8Error> {
|
||||
let input = "confident, productive systems programming";
|
||||
|
||||
let iter = utf8_percent_encode(input, FRAGMENT);
|
||||
// 将元素类型为 &str 的迭代器收集为 String 类型
|
||||
let encoded: String = iter.collect();
|
||||
assert_eq!(encoded, "confident,%20productive%20systems%20programming");
|
||||
|
||||
let iter = percent_decode(encoded.as_bytes());
|
||||
let decoded = iter.decode_utf8()?;
|
||||
assert_eq!(decoded, "confident, productive systems programming");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
该编码集定义了哪些字符( 特别是非 ASCII 和控制字符 )需要被百分比编码。具体的选择取决于上下文,例如 `url` 会对 URL 路径中的 `?` 进行编码,但是在路径后的查询字符串中,并不会进行编码。
|
||||
|
||||
|
||||
### 将字符串编码为 application/x-www-form-urlencoded
|
||||
|
||||
使用 [form_urlencoded::byte_serialize](https://docs.rs/form_urlencoded/1.0.1/form_urlencoded/fn.byte_serialize.html) 函数将一个字符串编码成 [application/x-www-form-urlencoded](https://url.spec.whatwg.org/#application/x-www-form-urlencoded) 格式,然后再使用 [form_urlencoded::parse](https://docs.rs/form_urlencoded/1.0.1/form_urlencoded/fn.parse.html) 对其进行解码。
|
||||
|
||||
```rust,editable
|
||||
use url::form_urlencoded::{byte_serialize, parse};
|
||||
|
||||
fn main() {
|
||||
let urlencoded: String = byte_serialize("What is ❤?".as_bytes()).collect();
|
||||
assert_eq!(urlencoded, "What+is+%E2%9D%A4%3F");
|
||||
println!("urlencoded:'{}'", urlencoded);
|
||||
|
||||
let decoded: String = parse(urlencoded.as_bytes())
|
||||
.map(|(key, val)| [key, val].concat())
|
||||
.collect();
|
||||
assert_eq!(decoded, "What is ❤?");
|
||||
println!("decoded:'{}'", decoded);
|
||||
}
|
||||
```
|
||||
|
||||
### 十六进制编解码
|
||||
|
||||
[data_encoding](https://docs.rs/data-encoding/*/data_encoding/) 可以将一个字符串编码成十六进制字符串,反之亦然。
|
||||
|
||||
下面的例子将 `&[u8]` 转换成十六进制等效形式,然后与期待的值进行比较。
|
||||
|
||||
```rust,editable
|
||||
use data_encoding::{HEXUPPER, DecodeError};
|
||||
|
||||
fn main() -> Result<(), DecodeError> {
|
||||
let original = b"The quick brown fox jumps over the lazy dog.";
|
||||
let expected = "54686520717569636B2062726F776E20666F78206A756D7073206F76\
|
||||
657220746865206C617A7920646F672E";
|
||||
|
||||
let encoded = HEXUPPER.encode(original);
|
||||
assert_eq!(encoded, expected);
|
||||
|
||||
let decoded = HEXUPPER.decode(&encoded.into_bytes())?;
|
||||
assert_eq!(&decoded[..], &original[..]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Base64 编解码
|
||||
[base64](https://docs.rs/base64/0.13.0/base64/index.html) 可以把一个字节切片编码成 `base64` String。
|
||||
|
||||
```rust,editable
|
||||
use error_chain::error_chain;
|
||||
|
||||
use std::str;
|
||||
use base64::{encode, decode};
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Base64(base64::DecodeError);
|
||||
Utf8Error(str::Utf8Error);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// 将 `&str` 转换成 `&[u8; N]`
|
||||
let hello = b"hello rustaceans";
|
||||
let encoded = encode(hello);
|
||||
let decoded = decode(&encoded)?;
|
||||
|
||||
println!("origin: {}", str::from_utf8(hello)?);
|
||||
println!("base64 encoded: {}", encoded);
|
||||
println!("back to origin: {}", str::from_utf8(&decoded)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
142
src/encoding/structured.md
Normal file
142
src/encoding/structured.md
Normal file
@ -0,0 +1,142 @@
|
||||
# 结构化数据
|
||||
|
||||
### 序列和反序列非结构化的JSON
|
||||
[serde_json]() 是一个高性能的 JSON 包,它支持我们在不声明结构体的情况下,去解析 JSON。
|
||||
|
||||
```rust,editable
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, Error};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let j = r#"{
|
||||
"userid": 103609,
|
||||
"verified": true,
|
||||
"access_privileges": [
|
||||
"user",
|
||||
"admin"
|
||||
]
|
||||
}"#;
|
||||
|
||||
let parsed: Value = serde_json::from_str(j)?;
|
||||
|
||||
let expected = json!({
|
||||
"userid": 103609,
|
||||
"verified": true,
|
||||
"access_privileges": [
|
||||
"user",
|
||||
"admin"
|
||||
]
|
||||
});
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 解析 TOML 文件
|
||||
[toml](https://docs.rs/toml/latest/toml/) 包可以将 TOML 文件的内容解析为一个 `toml::Value` 值,该值能代表任何合法的 TOML 数据。
|
||||
|
||||
```rust,editable
|
||||
use toml::{Value, de::Error};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let toml_content = r#"
|
||||
[package]
|
||||
name = "your_package"
|
||||
version = "0.1.0"
|
||||
authors = ["You! <you@example.org>"]
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
"#;
|
||||
|
||||
let package_info: Value = toml::from_str(toml_content)?;
|
||||
|
||||
assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0"));
|
||||
assert_eq!(package_info["package"]["name"].as_str(),
|
||||
Some("your_package"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
还可以配合 [serde]() 将 TOML 解析到我们自定义的结构体中:
|
||||
```rust,editable
|
||||
use serde::Deserialize;
|
||||
|
||||
use toml::de::Error;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
package: Package,
|
||||
dependencies: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Package {
|
||||
name: String,
|
||||
version: String,
|
||||
authors: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let toml_content = r#"
|
||||
[package]
|
||||
name = "your_package"
|
||||
version = "0.1.0"
|
||||
authors = ["You! <you@example.org>"]
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
"#;
|
||||
|
||||
let package_info: Config = toml::from_str(toml_content)?;
|
||||
|
||||
assert_eq!(package_info.package.name, "your_package");
|
||||
assert_eq!(package_info.package.version, "0.1.0");
|
||||
assert_eq!(package_info.package.authors, vec!["You! <you@example.org>"]);
|
||||
assert_eq!(package_info.dependencies["serde"], "1.0");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 使用小端字节序来读写整数
|
||||
[byteorder](https://docs.rs/byteorder/latest/byteorder/) 在自行接收或发送网络字节流时会非常有用( 除非性能要求高,否则还是建议使用 JSON 等数据协议,不要自己做字节流解析 )。
|
||||
|
||||
```rust,editable
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::Error;
|
||||
|
||||
#[derive(Default, PartialEq, Debug)]
|
||||
struct Payload {
|
||||
kind: u8,
|
||||
value: u16,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let original_payload = Payload::default();
|
||||
let encoded_bytes = encode(&original_payload)?;
|
||||
let decoded_payload = decode(&encoded_bytes)?;
|
||||
assert_eq!(original_payload, decoded_payload);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode(payload: &Payload) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = vec![];
|
||||
bytes.write_u8(payload.kind)?;
|
||||
bytes.write_u16::<LittleEndian>(payload.value)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn decode(mut bytes: &[u8]) -> Result<Payload, Error> {
|
||||
let payload = Payload {
|
||||
kind: bytes.read_u8()?,
|
||||
value: bytes.read_u16::<LittleEndian>()?,
|
||||
};
|
||||
Ok(payload)
|
||||
}
|
||||
```
|
242
src/files/dir.md
Normal file
242
src/files/dir.md
Normal file
@ -0,0 +1,242 @@
|
||||
# 目录访问
|
||||
|
||||
### 获取24小时内被修改过的文件
|
||||
通过遍历读取目录中文件的 [Metadata::modified](https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.modified) 属性,来获取目标文件名列表。
|
||||
|
||||
```rust,editable
|
||||
use error_chain::error_chain;
|
||||
|
||||
use std::{env, fs};
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Io(std::io::Error);
|
||||
SystemTimeError(std::time::SystemTimeError);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let current_dir = env::current_dir()?;
|
||||
println!(
|
||||
"Entries modified in the last 24 hours in {:?}:",
|
||||
current_dir
|
||||
);
|
||||
|
||||
for entry in fs::read_dir(current_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
let metadata = fs::metadata(&path)?;
|
||||
let last_modified = metadata.modified()?.elapsed()?.as_secs();
|
||||
|
||||
if last_modified < 24 * 3600 && metadata.is_file() {
|
||||
println!(
|
||||
"Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
|
||||
last_modified,
|
||||
metadata.permissions().readonly(),
|
||||
metadata.len(),
|
||||
path.file_name().ok_or("No filename")?
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 获取给定路径的 loops
|
||||
|
||||
使用 [same_file::is_same_file]() 可以检查给定路径的 loops,loop 可以通过以下方式创建:
|
||||
```shell
|
||||
mkdir -p /tmp/foo/bar/baz
|
||||
ln -s /tmp/foo/ /tmp/foo/bar/baz/qux
|
||||
```
|
||||
|
||||
```rust,editable
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use same_file::is_same_file;
|
||||
|
||||
fn contains_loop<P: AsRef<Path>>(path: P) -> io::Result<Option<(PathBuf, PathBuf)>> {
|
||||
let path = path.as_ref();
|
||||
let mut path_buf = path.to_path_buf();
|
||||
while path_buf.pop() {
|
||||
if is_same_file(&path_buf, path)? {
|
||||
return Ok(Some((path_buf, path.to_path_buf())));
|
||||
} else if let Some(looped_paths) = contains_loop(&path_buf)? {
|
||||
return Ok(Some(looped_paths));
|
||||
}
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(
|
||||
contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(),
|
||||
Some((
|
||||
PathBuf::from("/tmp/foo"),
|
||||
PathBuf::from("/tmp/foo/bar/baz/qux")
|
||||
))
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 递归查找重复的文件名
|
||||
|
||||
[walkdir](https://docs.rs/walkdir/latest/walkdir/) 可以帮助我们遍历指定的目录。
|
||||
|
||||
```rust,editable
|
||||
use std::collections::HashMap;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() {
|
||||
let mut filenames = HashMap::new();
|
||||
|
||||
// 遍历当前目录
|
||||
for entry in WalkDir::new(".")
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| !e.file_type().is_dir()) {
|
||||
let f_name = String::from(entry.file_name().to_string_lossy());
|
||||
let counter = filenames.entry(f_name.clone()).or_insert(0);
|
||||
*counter += 1;
|
||||
|
||||
if *counter == 2 {
|
||||
println!("{}", f_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 递归查找满足条件的所有文件
|
||||
|
||||
下面的代码通过 [walkdir](https://docs.rs/walkdir/latest/walkdir/) 来查找当前目录中最近一天内发生过修改的所有文件。
|
||||
|
||||
[follow_links](https://docs.rs/walkdir/*/walkdir/struct.WalkDir.html#method.follow_links) 为 `true` 时,那软链接会被当成正常的文件或目录一样对待,也就是说软链接指向的文件或目录也会被访问和检查。若软链接指向的目标不存在或它是一个 loops,就会导致错误的发生。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# WalkDir(walkdir::Error);
|
||||
# Io(std::io::Error);
|
||||
# SystemTime(std::time::SystemTimeError);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
for entry in WalkDir::new(".")
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok()) {
|
||||
let f_name = entry.file_name().to_string_lossy();
|
||||
let sec = entry.metadata()?.modified()?;
|
||||
|
||||
if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 {
|
||||
println!("{}", f_name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 遍历目录跳过隐藏文件
|
||||
|
||||
下面例子使用 [walkdir](https://docs.rs/walkdir/latest/walkdir/) 来遍历一个目录,同时跳过隐藏文件 `is_not_hidden`。
|
||||
|
||||
```rust,editable
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||
entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map(|s| entry.depth() == 0 || !s.starts_with("."))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
WalkDir::new(".")
|
||||
.into_iter()
|
||||
.filter_entry(|e| is_not_hidden(e))
|
||||
.filter_map(|v| v.ok())
|
||||
.for_each(|x| println!("{}", x.path().display()));
|
||||
}
|
||||
```
|
||||
|
||||
### 递归计算给定深度的文件大小
|
||||
递归访问的深度可以使用 [WalkDir::min_depth](https://docs.rs/walkdir/*/walkdir/struct.WalkDir.html#method.min_depth) 和 [WalkDir::max_depth](https://docs.rs/walkdir/2.3.2/walkdir/struct.WalkDir.html#method.max_depth) 来控制。
|
||||
|
||||
```rust,editable
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() {
|
||||
let total_size = WalkDir::new(".")
|
||||
.min_depth(1)
|
||||
.max_depth(3)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| entry.metadata().ok())
|
||||
.filter(|metadata| metadata.is_file())
|
||||
.fold(0, |acc, m| acc + m.len());
|
||||
|
||||
println!("Total size: {} bytes.", total_size);
|
||||
}
|
||||
```
|
||||
|
||||
### 递归查找所有 png 文件
|
||||
例子中使用了 [glob](https://docs.rs/glob/) 包,其中的 `**` 代表当前目录及其所有子目录,例如,`/media/**/*.png` 代表在 `media` 和它的所有子目录下查找 png 文件.
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use glob::glob;
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# Glob(glob::GlobError);
|
||||
# Pattern(glob::PatternError);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
for entry in glob("**/*.png")? {
|
||||
println!("{}", entry?.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 查找满足给定正则的所有文件且忽略文件名大小写
|
||||
|
||||
[glob_with](https://docs.rs/glob/*/glob/fn.glob_with.html) 函数可以按照给定的正则表达式进行查找,同时还能使用选项来控制一些匹配设置。
|
||||
|
||||
```rust,editable
|
||||
use error_chain::error_chain;
|
||||
use glob::{glob_with, MatchOptions};
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Glob(glob::GlobError);
|
||||
Pattern(glob::PatternError);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let options = MatchOptions {
|
||||
case_sensitive: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for entry in glob_with("/media/img_[0-9]*.png", options)? {
|
||||
println!("{}", entry?.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
94
src/files/read-write.md
Normal file
94
src/files/read-write.md
Normal file
@ -0,0 +1,94 @@
|
||||
# 文件读写
|
||||
|
||||
## 迭代文件中的内容行
|
||||
|
||||
```rust,editable
|
||||
use std::fs::File;
|
||||
use std::io::{Write, BufReader, BufRead, Error};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let path = "lines.txt";
|
||||
|
||||
// 创建文件
|
||||
let mut output = File::create(path)?;
|
||||
// 写入三行内容
|
||||
write!(output, "Rust\n💖\nFun")?;
|
||||
|
||||
let input = File::open(path)?;
|
||||
let buffered = BufReader::new(input);
|
||||
|
||||
// 迭代文件中的每一行内容,line 是字符串
|
||||
for line in buffered.lines() {
|
||||
println!("{}", line?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## 避免对同一个文件进行读写
|
||||
[same_file](https://docs.rs/same-file/latest/same_file/) 可以帮我们识别两个文件是否是相同的。
|
||||
|
||||
```rust,editable
|
||||
use same_file::Handle;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Error, ErrorKind};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let path_to_read = Path::new("new.txt");
|
||||
|
||||
// 从标准输出上获取待写入的文件名
|
||||
let stdout_handle = Handle::stdout()?;
|
||||
// 将待写入的文件名跟待读取的文件名进行比较
|
||||
let handle = Handle::from_path(path_to_read)?;
|
||||
|
||||
if stdout_handle == handle {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
"You are reading and writing to the same file",
|
||||
));
|
||||
} else {
|
||||
let file = File::open(&path_to_read)?;
|
||||
let file = BufReader::new(file);
|
||||
for (num, line) in file.lines().enumerate() {
|
||||
println!("{} : {}", num, line?.to_uppercase());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
以下代码会报错,因为待写入的文件名也是 *new.txt*,跟待读取的文件名相同
|
||||
```shell
|
||||
cargo run >> ./new.txt
|
||||
```
|
||||
|
||||
### 使用内存映射访问文件
|
||||
[memmap](https://docs.rs/memmap/) 能创建一个文件的内存映射( memory map ),然后模拟一些非顺序读。
|
||||
|
||||
使用内存映射,意味着你将相关的索引加载到内存中,而不是通过 [seek](https://doc.rust-lang.org/std/fs/struct.File.html#method.seek) 的方式去访问文件。
|
||||
|
||||
[Mmap::map](https://docs.rs/memmap/*/memmap/struct.Mmap.html#method.map) 函数会假定待映射的文件不会同时被其它进程修改。
|
||||
|
||||
```rust,editable
|
||||
use memmap::Mmap;
|
||||
use std::fs::File;
|
||||
use std::io::{Write, Error};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
write!(File::create("content.txt")?, "My hovercraft is full of eels!")?;
|
||||
|
||||
let file = File::open("content.txt")?;
|
||||
let map = unsafe { Mmap::map(&file)? };
|
||||
|
||||
let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29];
|
||||
assert_eq!(&map[3..13], b"hovercraft");
|
||||
let random_bytes: Vec<u8> = random_indexes.iter()
|
||||
.map(|&idx| map[idx])
|
||||
.collect();
|
||||
assert_eq!(&random_bytes[..], b"My loaf!");
|
||||
Ok(())
|
||||
}
|
||||
```
|
1
src/index-awesome.md
Normal file
1
src/index-awesome.md
Normal file
@ -0,0 +1 @@
|
||||
# Awesome 目录索引
|
1
src/index-cookbook.md
Normal file
1
src/index-cookbook.md
Normal file
@ -0,0 +1 @@
|
||||
# Cookbook 目录索引
|
31
src/memory/global-vars.md
Normal file
31
src/memory/global-vars.md
Normal file
@ -0,0 +1,31 @@
|
||||
# 全局变量
|
||||
|
||||
### 使用 lazy_static 在运行期初始化全局变量
|
||||
|
||||
下面的例子,我们将使用 [lazy_static](https://docs.rs/lazy_static/latest/lazy_static/) 声明一个在运行期初始化( 懒求值 )的 `Hashmap`,它会被求值一次,然后保存在一个全局的 `static` 引用之后。
|
||||
|
||||
```rust,editable
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
lazy_static! {
|
||||
static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("James", vec!["user", "admin"]);
|
||||
map.insert("Jim", vec!["user"]);
|
||||
map
|
||||
};
|
||||
}
|
||||
|
||||
fn show_access(name: &str) {
|
||||
let access = PRIVILEGES.get(name);
|
||||
println!("{}: {:?}", name, access);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let access = PRIVILEGES.get("James");
|
||||
println!("James: {:?}", access);
|
||||
|
||||
show_access("Jim");
|
||||
}
|
||||
```
|
71
src/os/awesome.md
Normal file
71
src/os/awesome.md
Normal file
@ -0,0 +1,71 @@
|
||||
# 操作系统
|
||||
|
||||
操作系统范畴很大,本章节中精选的内容聚焦在用Rust实现的操作系统以及用Rust写操作系统的教程。
|
||||
|
||||
## 目录
|
||||
| 系统 | 描述 |
|
||||
| -------- | --------- |
|
||||
| [redox](#redox) | `Unix`风格的微内核OS |
|
||||
| [tock](#tock) | 嵌入式操作系统 |
|
||||
| [theseus](#theseus) | 独特设计的OS |
|
||||
| [writing os in rust](#writing-an-os-in-rust) | 使用Rust开发简单的操作系统 |
|
||||
| [rust-raspberrypi-OS-tutorials](#rust-raspberrypi-os-tutorials) | Rust嵌入式系统开发教程 |
|
||||
| [rcore-os](#rcore-os) | 清华大学提供的`rcore`操作系统教程 |
|
||||
| [edu-os](#edu-os) | 亚琛工业大学操作系统课程的配套项目 |
|
||||
|
||||
### redox
|
||||
[redox](https://github.com/redox-os/redox) 是一个 `Unix` 风格的微内核操作系统,使用 `Rust` 实现。`redox` 的目标是安全、快速、免费、可用,它在内核设计上借鉴了很多优秀的内核,例如:`SeL4`, `MINIX`, `Plan 9`和`BSD`。
|
||||
|
||||
但 `redox` 不仅仅是一个内核,它还是一个功能齐全的操作系统,提供了操作系统该有的功能,例如:内存分配器、文件系统、显示管理、核心工具等等。你可以大概认为它是一个 `GNU` 或 `BSD` 生态,但是是通过一门现代化、内存安全的语言实现的。
|
||||
|
||||
> 不过据我仔细观察,redox目前的开发进度不是很活跃,不知道发生了什么,未来若有新的发现会在这里进行更新 - Sunface
|
||||
|
||||
<img alt="redox1 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/os/redox1.jpg?raw=true" class="center" />
|
||||
|
||||
<img alt="redox2 screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/os/redox2.jpeg?raw=true" class="center" />
|
||||
|
||||
### tock
|
||||
[tock](https://github.com/tock/tock) 是一个嵌入式操作系统,设计用于在低内存和低功耗的微控制器上运行多个并发的、相互不信任的应用程序,例如它可在 `Cortex-M` 和 `RISC-V` 平台上运行。
|
||||
|
||||
`Tock` 使用两个核心机制保护操作系统中不同组件的安全运行:
|
||||
|
||||
- 内核和设备驱动全部使用Rust编写,提供了很好安全性的同时,还将内核和设备进行了隔离
|
||||
- 使用了内存保护单元技术,让应用之间、应用和内核之间实现了安全隔离
|
||||
|
||||
具体可通过这本书了解: [The Tock Book](https://book.tockos.org/introduction.html).
|
||||
|
||||
<img alt="tock screenshot" width="100%" src="https://book.tockos.org/imgs/imix.svg" class="center" />
|
||||
|
||||
### Theseus
|
||||
[Theseus](https://github.com/theseus-os/Theseus) 是从零开始构建的操作系统,完全使用Rust进行开发。它使用了新的操作系统结构、更好的状态管理,以及利用语言内设计原则将操作系统的职责(如资源管理)转移到编译器中。
|
||||
|
||||
该OS目前尚处于早期阶段,但是看上去作者很有信心未来可以落地,如果想要了解,可以通过官方提供的[在线书籍](https://theseus-os.github.io/Theseus/book/index.html)进行学习。
|
||||
|
||||
### Writing an OS in Rust
|
||||
[Writing an OS in Rust](https://os.phil-opp.com) 是非常有名的博客系列,专门讲解如何使用Rust来写一个简单的操作系统,配套源码在[这里](https://github.com/phil-opp/blog_os),目前已经发布了第二版。
|
||||
|
||||
以下是`async/await`的目录截图:
|
||||
<img alt="writing-os screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/os/writing-os.jpg?raw=true" class="center" />
|
||||
|
||||
### rust-raspberrypi-OS-tutorials
|
||||
[rust-raspberrypi-OS-tutorials](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials) 教大家如何用Rust开发一个嵌入式操作系统,可以运行在树莓派上。这个教程讲得很细,号称手把手教学,而且是从零实现,因此很值得学习。
|
||||
|
||||
<img alt="rrot screenshot" width="50%" height="400px" src="https://github.com/studyrs/cookbook-images/blob/main/os/rrot.jpg?raw=true" class="center" />
|
||||
<img alt="rrot1 screenshot" width="49%" height="400px" src="https://github.com/studyrs/cookbook-images/blob/main/os/rrot1.gif?raw=true" class="center" />
|
||||
|
||||
### rcore-os
|
||||
[rcore-os](https://github.com/rcore-os) 是由清华大学开发的操作系统,用 Rus t实现, 与 `linux` 相兼容,主要目的目前还是用于教学,因为还有相关的配套教程,非常值得学习。目前支持的功能不完全列表如下:`linux` 兼容的 `syscall` 接口、网络协议栈、简单的文件系统、信号系统、异步IO、内核模块化。
|
||||
|
||||
- [内核实现](https://github.com/rcore-os/rCore)
|
||||
- [配套教程](https://github.com/rcore-os/rCore-Tutorial-Book-v3)
|
||||
|
||||
|
||||
以下是在树莓派上运行的图:
|
||||
<img alt="rcore screenshot" width="100%" src="https://github.com/studyrs/cookbook-images/blob/main/os/rcore.jpg?raw=true" class="center" />
|
||||
|
||||
|
||||
|
||||
### edu-os
|
||||
[edu-os](https://github.com/RWTH-OS/eduOS-rs) 是 `Unix` 风格的操作系统,用于教学目的,它是亚琛工业大学(RWTH Aachen University)操作系统课程的配套大项目,但是我并没有找到对应的课程资料,根据作者的描述,上面部分的**Writing an OS in Rust**对他有很大的启发。
|
||||
|
||||
<img alt="eduos screenshot" width="100%" src="https://github.com/RWTH-OS/eduOS-rs/raw/master/img/demo.gif?raw=true" class="center" />
|
228
src/os/command.md
Normal file
228
src/os/command.md
Normal file
@ -0,0 +1,228 @@
|
||||
# 调用系统命令
|
||||
|
||||
### 调用一个外部命令并处理输出内容
|
||||
|
||||
下面的代码将调用操作系统中的 `git log --oneline` 命令,然后使用 [regex](https://docs.rs/regex/*/regex/struct.Regex.html) 对它输出到 `stdout` 上的调用结果进行解析,以获取哈希值和最后 5 条提交信息( commit )。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use std::process::Command;
|
||||
use regex::Regex;
|
||||
|
||||
#error_chain!{
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# Regex(regex::Error);
|
||||
# Utf8(std::string::FromUtf8Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
#[derive(PartialEq, Default, Clone, Debug)]
|
||||
struct Commit {
|
||||
hash: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let output = Command::new("git").arg("log").arg("--oneline").output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
error_chain::bail!("Command executed with failing error code");
|
||||
}
|
||||
|
||||
let pattern = Regex::new(r"(?x)
|
||||
([0-9a-fA-F]+) # commit hash
|
||||
(.*) # The commit message")?;
|
||||
|
||||
String::from_utf8(output.stdout)?
|
||||
.lines()
|
||||
.filter_map(|line| pattern.captures(line))
|
||||
.map(|cap| {
|
||||
Commit {
|
||||
hash: cap[1].to_string(),
|
||||
message: cap[2].trim().to_string(),
|
||||
}
|
||||
})
|
||||
.take(5)
|
||||
.for_each(|x| println!("{:?}", x));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 调用 python 解释器运行代码并检查返回的错误码
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#error_chain!{
|
||||
# errors { CmdError }
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# Utf8(std::string::FromUtf8Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut child = Command::new("python").stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
child.stdin
|
||||
.as_mut()
|
||||
.ok_or("Child process stdin has not been captured!")?
|
||||
.write_all(b"import this; copyright(); credits(); exit()")?;
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
if output.status.success() {
|
||||
let raw_output = String::from_utf8(output.stdout)?;
|
||||
let words = raw_output.split_whitespace()
|
||||
.map(|s| s.to_lowercase())
|
||||
.collect::<HashSet<_>>();
|
||||
println!("Found {} unique words:", words.len());
|
||||
println!("{:#?}", words);
|
||||
Ok(())
|
||||
} else {
|
||||
let err = String::from_utf8(output.stderr)?;
|
||||
error_chain::bail!("External command failed:\n {}", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 通过管道来运行外部命令
|
||||
下面的例子将显示当前目录中大小排名前十的文件和子目录,效果等效于命令 `du -ah . | sort -hr | head -n 10`。
|
||||
|
||||
[`Command`](https://doc.rust-lang.org/std/process/struct.Command.html) 命令代表一个进程,其中父进程通过 [Stdio::piped](https://doc.rust-lang.org/std/process/struct.Stdio.html) 来捕获子进程的输出。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# Utf8(std::string::FromUtf8Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let directory = std::env::current_dir()?;
|
||||
let mut du_output_child = Command::new("du")
|
||||
.arg("-ah")
|
||||
.arg(&directory)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
if let Some(du_output) = du_output_child.stdout.take() {
|
||||
let mut sort_output_child = Command::new("sort")
|
||||
.arg("-hr")
|
||||
.stdin(du_output)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
du_output_child.wait()?;
|
||||
|
||||
if let Some(sort_output) = sort_output_child.stdout.take() {
|
||||
let head_output_child = Command::new("head")
|
||||
.args(&["-n", "10"])
|
||||
.stdin(sort_output)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let head_stdout = head_output_child.wait_with_output()?;
|
||||
|
||||
sort_output_child.wait()?;
|
||||
|
||||
println!(
|
||||
"Top 10 biggest files and directories in '{}':\n{}",
|
||||
directory.display(),
|
||||
String::from_utf8(head_stdout.stdout).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 将子进程的 stdout 和 stderr 重定向到同一个文件
|
||||
下面的例子将生成一个子进程,然后将它的标准输出和标准错误输出都输出到同一个文件中。最终的效果跟 Unix 命令 `ls . oops >out.txt 2>&1` 相同。
|
||||
|
||||
[File::try_clone](https://doc.rust-lang.org/std/fs/struct.File.html#method.try_clone) 会克隆一份文件句柄的引用,然后保证这两个句柄在写的时候会使用相同的游标位置。
|
||||
|
||||
```rust,editable
|
||||
use std::fs::File;
|
||||
use std::io::Error;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let outputs = File::create("out.txt")?;
|
||||
let errors = outputs.try_clone()?;
|
||||
|
||||
Command::new("ls")
|
||||
.args(&[".", "oops"])
|
||||
.stdout(Stdio::from(outputs))
|
||||
.stderr(Stdio::from(errors))
|
||||
.spawn()?
|
||||
.wait_with_output()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 持续处理子进程的输出
|
||||
下面的代码会创建一个管道,然后当 `BufReader` 更新时,就持续从 `stdout` 中读取数据。最终效果等同于 Unix 命令 `journalctl | grep usb`。
|
||||
|
||||
```rust,editable
|
||||
use std::process::{Command, Stdio};
|
||||
use std::io::{BufRead, BufReader, Error, ErrorKind};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let stdout = Command::new("journalctl")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?
|
||||
.stdout
|
||||
.ok_or_else(|| Error::new(ErrorKind::Other,"Could not capture standard output."))?;
|
||||
|
||||
let reader = BufReader::new(stdout);
|
||||
|
||||
reader
|
||||
.lines()
|
||||
.filter_map(|line| line.ok())
|
||||
.filter(|line| line.find("usb").is_some())
|
||||
.for_each(|line| println!("{}", line));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 读取环境变量
|
||||
|
||||
使用 [std::env::var](https://doc.rust-lang.org/std/env/fn.var.html) 可以读取系统中的环境变量。
|
||||
|
||||
```rust,editable
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Error;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
// 读取环境变量 `CONFIG` 的值并写入到 `config_path` 中。
|
||||
// 若 `CONFIG` 环境变量没有设置,则使用一个默认的值 "/etc/myapp/config"
|
||||
let config_path = env::var("CONFIG")
|
||||
.unwrap_or("/etc/myapp/config".to_string());
|
||||
|
||||
let config: String = fs::read_to_string(config_path)?;
|
||||
println!("Config: {}", config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
11
src/os/processor.md
Normal file
11
src/os/processor.md
Normal file
@ -0,0 +1,11 @@
|
||||
# 处理器
|
||||
|
||||
### 获取逻辑CPU的核心数
|
||||
|
||||
[num_cpus](https://docs.rs/num_cpus/latest/num_cpus/) 可以用于获取逻辑和物理的 CPU 核心数,下面的例子是获取逻辑核心数。
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
println!("Number of logical cores is {}", num_cpus::get());
|
||||
}
|
||||
```
|
28
src/protocols/tcpip.md
Normal file
28
src/protocols/tcpip.md
Normal file
@ -0,0 +1,28 @@
|
||||
# TCP/IP
|
||||
|
||||
### 监听 TCP 端口
|
||||
|
||||
以下代码会监听指定的 TCP 端口,并接收一条外部进入的 TCP 连接,然后将读取到的一条信息输出到标准输出( `println!` )。
|
||||
|
||||
```rust,editable
|
||||
use std::net::{SocketAddrV4, Ipv4Addr, TcpListener};
|
||||
use std::io::{Read, Error};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let loopback = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let socket = SocketAddrV4::new(loopback, 0);
|
||||
let listener = TcpListener::bind(socket)?;
|
||||
let port = listener.local_addr()?;
|
||||
println!("Listening on {}, access this port to end the program", port);
|
||||
let (mut tcp_stream, addr) = listener.accept()?; //block until requested
|
||||
println!("Connection received! {:?} is sending data.", addr);
|
||||
let mut input = String::new();
|
||||
let _ = tcp_stream.read_to_string(&mut input)?;
|
||||
println!("{:?} says {}", addr, input);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 循环接收进入的 TCP 连接
|
||||
@todo
|
202
src/text/regex.md
Normal file
202
src/text/regex.md
Normal file
@ -0,0 +1,202 @@
|
||||
# 正则表达式
|
||||
|
||||
### 验证邮件格式并取出 @ 前的信息
|
||||
下面代码使用 [regex](https://docs.rs/regex/latest/regex/) 包来验证邮件格式的正确性,然后提取出 `@` 符号前的所有内容。
|
||||
|
||||
```rust,editable
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
fn extract_login(input: &str) -> Option<&str> {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"(?x)
|
||||
^(?P<login>[^@\s]+)@
|
||||
([[:word:]]+\.)*
|
||||
[[:word:]]+$
|
||||
").unwrap();
|
||||
}
|
||||
RE.captures(input).and_then(|cap| {
|
||||
cap.name("login").map(|login| login.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email"));
|
||||
assert_eq!(
|
||||
extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"),
|
||||
Some(r"sdf+sdsfsd.as.sdsd")
|
||||
);
|
||||
assert_eq!(extract_login(r"More@Than@One@at.com"), None);
|
||||
assert_eq!(extract_login(r"Not an email@email"), None);
|
||||
}
|
||||
```
|
||||
|
||||
### 从文本中提出 # 开头的标签
|
||||
例子对标签进行提取、排序和去重。需要注意,下面的标签仅仅是拉丁字母的,如果你要支持更多的字母,可以参考下 [Twitter 的正则语法](https://github.com/twitter/twitter-text/blob/c9fc09782efe59af4ee82855768cfaf36273e170/java/src/com/twitter/Regex.java#L255),友情提示,复杂的多!
|
||||
|
||||
```rust,editable
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn extract_hashtags(text: &str) -> HashSet<&str> {
|
||||
lazy_static! {
|
||||
static ref HASHTAG_REGEX : Regex = Regex::new(
|
||||
r"\#[a-zA-Z][0-9a-zA-Z_]*"
|
||||
).unwrap();
|
||||
}
|
||||
HASHTAG_REGEX.find_iter(text).map(|mat| mat.as_str()).collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ ";
|
||||
let tags = extract_hashtags(tweet);
|
||||
assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world"));
|
||||
assert_eq!(tags.len(), 3);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 从文本中提取出所有手机号
|
||||
[Regex::captures_iter] 可以对字符串型文本进行处理,以获取文本中的多个手机号。下面的例子适用于美国的号码。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use regex::Regex;
|
||||
use std::fmt;
|
||||
|
||||
#error_chain!{
|
||||
# foreign_links {
|
||||
# Regex(regex::Error);
|
||||
# Io(std::io::Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
struct PhoneNumber<'a> {
|
||||
area: &'a str,
|
||||
exchange: &'a str,
|
||||
subscriber: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for PhoneNumber<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "1 ({}) {}-{}", self.area, self.exchange, self.subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let phone_text = "
|
||||
+1 505 881 9292 (v) +1 505 778 2212 (c) +1 505 881 9297 (f)
|
||||
(202) 991 9534
|
||||
Alex 5553920011
|
||||
1 (800) 233-2010
|
||||
1.299.339.1020";
|
||||
|
||||
let re = Regex::new(
|
||||
r#"(?x)
|
||||
(?:\+?1)? # Country Code Optional
|
||||
[\s\.]?
|
||||
(([2-9]\d{2})|\(([2-9]\d{2})\)) # Area Code
|
||||
[\s\.\-]?
|
||||
([2-9]\d{2}) # Exchange Code
|
||||
[\s\.\-]?
|
||||
(\d{4}) # Subscriber Number"#,
|
||||
)?;
|
||||
|
||||
let phone_numbers = re.captures_iter(phone_text).filter_map(|cap| {
|
||||
let groups = (cap.get(2).or(cap.get(3)), cap.get(4), cap.get(5));
|
||||
match groups {
|
||||
(Some(area), Some(ext), Some(sub)) => Some(PhoneNumber {
|
||||
area: area.as_str(),
|
||||
exchange: ext.as_str(),
|
||||
subscriber: sub.as_str(),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
phone_numbers.map(|m| m.to_string()).collect::<Vec<_>>(),
|
||||
vec![
|
||||
"1 (505) 881-9292",
|
||||
"1 (505) 778-2212",
|
||||
"1 (505) 881-9297",
|
||||
"1 (202) 991-9534",
|
||||
"1 (555) 392-0011",
|
||||
"1 (800) 233-2010",
|
||||
"1 (299) 339-1020",
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 通过多个正则来过滤日志文件
|
||||
例子的目标是过滤出包含 "version X.X.X"、以 443 结尾的 IP 地址和特别的警告的日志行。
|
||||
|
||||
值得注意的是,由于在正则中反斜杠非常常见,因此使用 `r#""` 形式的原生字符串对于开发者和使用者都更加友好。
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufRead};
|
||||
use regex::RegexSetBuilder;
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# Io(std::io::Error);
|
||||
# Regex(regex::Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let log_path = "application.log";
|
||||
let buffered = BufReader::new(File::open(log_path)?);
|
||||
|
||||
let set = RegexSetBuilder::new(&[
|
||||
r#"version "\d\.\d\.\d""#,
|
||||
r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#,
|
||||
r#"warning.*timeout expired"#,
|
||||
]).case_insensitive(true)
|
||||
.build()?;
|
||||
|
||||
buffered
|
||||
.lines()
|
||||
.filter_map(|line| line.ok())
|
||||
.filter(|line| set.is_match(line.as_str()))
|
||||
.for_each(|x| println!("{}", x));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 将文本中所有的指定模式替换成另外一种模式
|
||||
下面代码将标准的 ISO 8601 YYYY-MM-DD 日期模式替换成带有斜杠的美式英语日期。例如 `2013-01-15` -> `01/15/2013`。
|
||||
|
||||
```rust,editable
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use regex::Regex;
|
||||
|
||||
fn reformat_dates(before: &str) -> Cow<str> {
|
||||
lazy_static! {
|
||||
static ref ISO8601_DATE_REGEX : Regex = Regex::new(
|
||||
r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})"
|
||||
).unwrap();
|
||||
}
|
||||
ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let before = "2012-03-14, 2013-01-15 and 2014-07-05";
|
||||
let after = reformat_dates(before);
|
||||
assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014");
|
||||
}
|
||||
```
|
||||
|
75
src/text/string.md
Normal file
75
src/text/string.md
Normal file
@ -0,0 +1,75 @@
|
||||
# 字符串解析
|
||||
|
||||
### 访问 Unicode 字符
|
||||
|
||||
[unicode-segmentation]() 包的 [UnicodeSegmentation::graphemes]() 函数可以将 UTF-8 字符串收集成一个 Unicode 字符组成的数组。这样我们就可以通过索引的方式来访问对应的字符了。
|
||||
|
||||
```rust,editable
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
fn main() {
|
||||
let name = "José Guimarães\r\n";
|
||||
let graphemes = UnicodeSegmentation::graphemes(name, true)
|
||||
.collect::<Vec<&str>>();
|
||||
assert_eq!(graphemes[3], "é");
|
||||
}
|
||||
```
|
||||
|
||||
### 为自定义结构体实现 FromStr 特征
|
||||
为我们的 RGB 结构体实现 `FromStr` 特征后,就可以将一个十六进制的颜色表示字符串转换成 RGB 结构体。
|
||||
|
||||
```rust,editable
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct RGB {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
}
|
||||
|
||||
impl FromStr for RGB {
|
||||
type Err = std::num::ParseIntError;
|
||||
|
||||
|
||||
// 将十六进制的颜色码解析为 `RGB` 的实例
|
||||
fn from_str(hex_code: &str) -> Result<Self, Self::Err> {
|
||||
|
||||
// u8::from_str_radix(src: &str, radix: u32) 将一个字符串切片按照指定的基数转换为 u8 类型
|
||||
let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?;
|
||||
let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?;
|
||||
let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?;
|
||||
|
||||
Ok(RGB { r, g, b })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let code: &str = &r"#fa7268";
|
||||
match RGB::from_str(code) {
|
||||
Ok(rgb) => {
|
||||
println!(
|
||||
r"The RGB color code is: R: {} G: {} B: {}",
|
||||
rgb.r, rgb.g, rgb.b
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
println!("{} is not a valid color hex code!", code);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 from_str 是否按照预期工作
|
||||
assert_eq!(
|
||||
RGB::from_str(&r"#fa7268").unwrap(),
|
||||
RGB {
|
||||
r: 250,
|
||||
g: 114,
|
||||
b: 104
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 实现 Display 特征
|
||||
@todo
|
34
src/web/scraping.md
Normal file
34
src/web/scraping.md
Normal file
@ -0,0 +1,34 @@
|
||||
# 提取网络链接( 爬虫 )
|
||||
|
||||
### 从目标网页 HTML 中提取出所有链接
|
||||
|
||||
下面代码使用 [reqwest::get](https://docs.rs/reqwest/*/reqwest/fn.get.html) 发起一次 http 请求,然后通过 `select` 包的 [Document::from_read](https://docs.rs/select/*/select/document/struct.Document.html#method.from_read) 将请求的结果解析为 HTML 文档。
|
||||
|
||||
|
||||
```rust,editable
|
||||
#use error_chain::error_chain;
|
||||
use select::document::Document;
|
||||
use select::predicate::Name;
|
||||
|
||||
#error_chain! {
|
||||
# foreign_links {
|
||||
# ReqError(reqwest::Error);
|
||||
# IoError(std::io::Error);
|
||||
# }
|
||||
#}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let res = reqwest::get("https://www.rust-lang.org/en-US/")
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
Document::from(res.as_str())
|
||||
.find(Name("a"))
|
||||
.filter_map(|n| n.attr("href"))
|
||||
.for_each(|x| println!("{}", x));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
315
theme/index.hbs
Normal file
315
theme/index.hbs
Normal file
@ -0,0 +1,315 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
{{#if is_print }}
|
||||
<meta name="robots" content="noindex" />
|
||||
{{/if}}
|
||||
{{#if base_url}}
|
||||
<base href="{{ base_url }}">
|
||||
{{/if}}
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
{{> head}}
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
{{#if favicon_svg}}
|
||||
<link rel="icon" href="{{ path_to_root }}favicon.svg">
|
||||
{{/if}}
|
||||
{{#if favicon_png}}
|
||||
<link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
|
||||
{{/if}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
|
||||
{{#if print_enable}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
|
||||
{{/if}}
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
|
||||
{{#if copy_fonts}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
|
||||
{{/if}}
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="{{ path_to_root }}highlight.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
{{#each additional_css}}
|
||||
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
|
||||
{{/each}}
|
||||
|
||||
{{#if mathjax_support}}
|
||||
<!-- MathJax -->
|
||||
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
{{/if}}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "{{ path_to_root }}";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
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 type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('{{ default_theme }}')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
{{#toc}}{{/toc}}
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
{{> header}}
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky bordered">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
|
||||
</ul>
|
||||
{{#if search_enabled}}
|
||||
<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>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">{{ book_title }}</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
{{#if print_enable}}
|
||||
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if git_repository_url}}
|
||||
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if git_repository_edit_url}}
|
||||
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
|
||||
<i id="git-edit-button" class="fa fa-edit"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if search_enabled}}
|
||||
<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>
|
||||
{{/if}}
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
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>
|
||||
{{{ content }}}
|
||||
<div id="giscus-container"></div>
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
{{#if livereload}}
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("{{{livereload}}}");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if google_analytics}}
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '{{google_analytics}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_line_numbers}}
|
||||
<script type="text/javascript">
|
||||
window.playground_line_numbers = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_copyable}}
|
||||
<script type="text/javascript">
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_js}}
|
||||
<script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
{{/if}}
|
||||
|
||||
{{#if search_js}}
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
{{/if}}
|
||||
|
||||
<script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
var pagePath = "{{ path }}"
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
{{#each additional_js}}
|
||||
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
|
||||
{{/each}}
|
||||
|
||||
{{#if is_print}}
|
||||
{{#if mathjax_support}}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
MathJax.Hub.Register.StartupHook('End', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{else}}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
</script>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</body>
|
||||
</html>
|
113
theme/style1.css
Normal file
113
theme/style1.css
Normal file
@ -0,0 +1,113 @@
|
||||
@media only screen and (max-width:1080px) {
|
||||
.sidetoc {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width:1080px) {
|
||||
main {
|
||||
position: relative;
|
||||
padding-right: 170px;
|
||||
}
|
||||
.sidetoc {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/*left: calc(100% + (var(--content-max-width))/4 - 180px);*/
|
||||
left: calc(100% - 200px);
|
||||
position: absolute;
|
||||
}
|
||||
.pagetoc {
|
||||
position: fixed;
|
||||
width: 200px;
|
||||
height: calc(100vh - var(--menu-bar-height) - 10rem);
|
||||
overflow: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
.pagetoc a {
|
||||
border-left: 1px solid var(--sidebar-bg);
|
||||
color: var(--fg) !important;
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
padding-left: 10px;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.pagetoc a:hover,
|
||||
.pagetoc a.active {
|
||||
background: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg) !important;
|
||||
}
|
||||
.pagetoc .active {
|
||||
background: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
margin-top: 50px;
|
||||
border-top: 1px solid #ccc;
|
||||
overflow: hidden;
|
||||
padding: 10px 0;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
/* 修改章节目录的间距 */
|
||||
.chapter li.chapter-item {
|
||||
/* 没有文件时的文字颜色 */
|
||||
color: #939da3;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* 修改滚动条宽度 */
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
/* 表格靠左对齐 */
|
||||
table {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
/* 只使用底部的页面跳转,因为左右两边的宽跳转会被 page-toc 遮盖 */
|
||||
@media only screen and (max-width: 2560px) {
|
||||
.nav-wide-wrapper { display: none; }
|
||||
.nav-wrapper { display: block; }
|
||||
}
|
||||
@media only screen and (max-width: 2560px) {
|
||||
.sidebar-visible .nav-wide-wrapper { display: none; }
|
||||
.sidebar-visible .nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
|
||||
/* 修改顶部图标大小 */
|
||||
/* #menu-bar {
|
||||
font-size: 17px;
|
||||
} */
|
||||
/* 修改 github 样式 */
|
||||
.fa-github {
|
||||
font-weight: 550;
|
||||
}
|
||||
.fa-github:after{
|
||||
content: "星光点点比不过你的 🌟";
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Fix on mobile device */
|
||||
code {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 修复可编辑代码框顶部过窄的问题 */
|
||||
code.editable, .ace_scroller {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
/* 修改书侧边目录的区域分隔行样式 */
|
||||
.chapter .spacer {
|
||||
background-color: #99CCFF;
|
||||
height: 2px;
|
||||
margin-top: 8px;
|
||||
}
|
Reference in New Issue
Block a user