通过构建一个小型的Markdown编译器开始使用Rust

2020-05-17 04:42:20

我的名字是Jesse,这是一个面向喜欢在实践中学习的开发人员的Rust入门教程。

本教程的目的是发展对Rust中的工具构建的直觉-具体地说,学习如何在Rust中思考和构建。

我们的目标是生产一个非常基本的命令行编译器,可以将包含标题和段落的基本Markdown文档转换为html文件。

为此,我们将从构建一个简单的“Hello,World!”开始。可执行的。然后,在六章的过程中,迭代和展开,直到最后我们可以将一个非常简单的Markdown文件编译成有效的HTML。

铁锈需要一种与大多数开发人员通常习惯的不同的心态。通过首先专注于培养自信,可以减轻学习锈带来的许多最初的痛苦和挫折感。这就是本教程的目的:建立信心。

当我第一次接触铁锈的时候,我只能呆上几个星期,然后我的挫折感就会压倒我,我不得不放弃它一段时间。最后,我会回到铁锈,再经历几个令人惊叹的时刻,然后再次感到沮丧,休息一下。这个循环还会持续大约四次,直到最后我坐下来,拿着纸和笔,准确地写下了到底是什么同时困扰着我,并吸引我去锈。

我得出的结论可能不会让您感到惊讶:Rust学习起来很沮丧,因为在处理编程问题时需要不同的思维模式。我不能做我从C到C++、C++到PHP、C++到Java,或者我熟悉的其他大约30种语言中的任何一种时所做的事情,那就是将新语言的任何句法差异恰好适合于我现有的框架,即一种语言应该做什么,应该是什么感觉。拉斯特要求,在很大程度上,要有一个清白的历史。

作为一名教程作者,我想写一篇关于Rust的入门教程,以便让其他语言的开发人员尽可能快地上手--但我不想像其他Rust教程一样,只深入到它的一些细节部分,尽管这些部分是基础的,但对于任何想要学习像Rust这样独特的新语言的人来说,都是令人不快的。

不只是我有这个顾虑。在一项对6000名Rust用户的调查中,近25%的人表示,这种语言太难或太难学了,所有用户都说最难学的两个领域是终身和所有权/借阅系统。如果我要写一篇能让人们对铁锈感到兴奋的教程,我知道在处理这些复杂的主题之前,我必须专注于建立信心。在我看来,一旦一个人对这门语言的信心足够高,解决生命周期和所有权等问题将是他们学习的一个自然过程。

花点时间复习一下学习目标(下面),然后转到第一章。

在您的工作站上安装铁锈。如果您还没有安装铁锈,您可以完成本教程。

Rust对我的主要吸引力在于,我可以完全控制像C这样的低级语言,并拥有高级语言的一些更具表现力的元素和存储安全性。这几乎就像是一种全新的思考软件工程问题的方式。事实上,Rust处理变量符号的方式,将参数传递给函数,以及从函数返回值,是思考我们正在构建的工具的一种有点独特的方式。

嗯,就像共同核心的出现改变了我们教授数学的方式一样,有些人非常(有时是有毒的)批评Rust如何迫使我们以不同的方式思考事物。但是,每种语言都有优缺点,考虑到您正在构建的是什么以及您喜欢如何对您的问题进行心理建模,您真的要找出哪种语言最有意义。

我建议你不要掉进这些“铁锈比它好”或“铁锈吸力因为”的兔子洞里。我们都是工程师,工程师使用工具。我们应该欢迎新的方式来思考我们多年来一直在解决的问题,因为最终我们会找到更好的方法来解决它们。

形成你自己的观点。工具应该是有趣的、引人入胜的,而不是诽谤的谈话点。

我的系列教程的设计不同于我所见过的任何关于铁锈的东西。作为一个自学成才的人,我写这些教程的方式会帮助我获得基本的自信,那就是我从来没有在网上阅读过传统书籍和流行的入门教程。铁锈需要不同的心态,是的,但我认为作为铁锈教师,我们不应该把铁锈的所有深奥知识预先装入旨在温和地将某人介绍到这门语言的教程中。

官方的“铁锈”一书很棒。奥莱利的书很棒。也有很多很棒的教程。就我个人而言,我喜欢学习一门新的语言,这是潜心钻研和构建工具所必需的思维模式。毕竟,RUST是一种系统语言,而工具构建是深入实际应用程序的一种很好的方式。最重要的是,拥有一个易于理解的并行目标(构建tinymarkdown编译器)可以为我们正在学习和应用的内容提供一些基础信心。

这是学习生锈的一种独特方式,这一点是肯定的,我也希望我能用这种方式来学习。

话虽如此,本课程并不是要让你熟悉Rustper se;我在Rust的新手身上看到的问题是,要学的东西太多了,太快了,而且在“你好,世界!”之后,一开始就没有足够的回报。来证明留在这里是正当的。在我看来,这是因为大多数Rust教程并没有告诉人们Rust对于构建工具是多么有趣,而是过于关注Rust的独特功能。这些教程仍然很重要,但我认为其他教程-那些专注于构建东西的教程,即使不是那么复杂-也很重要,它们有助于赞扬仍然年轻的在线Rust教程生态系统(截至2019年)。

Markdown是一种用于非常快速地编写HTML内容的简约语言。它使我们能够编写基于文本的文档,这些文档既易读又小,同时还可以编译成Vaid HTML。它使起草、更新和发布变得非常容易,是程序员在所有工作领域中常用的工具。

Markdown编译器的工作是将“markdown”内容转换为有效的HTML。

#这是一个标题,这是一段文字。多整洁啊!在本教程中,我们将学习如何通过在段落块周围放置`<;p>;`标签将其转换为段落块。

<;h1>;这是标题<;/h1>;<;p>;这是一段文本。多整洁啊!在本教程中,我们将学习如何通过使用<;code>;<;p>;<;/code>;标签将其转换为段落块。<;/p>;

我们在本课程中构建的编译器将标题标签、段落块和代码片段从Markdown转换为有效的HTML。

我们本章的目标是培养建立新的RustProject的信心。

我们将首先创建一个文件夹来跟踪所有教程项目。在我的教程中,您通常会看到C:\RustTutorials作为我们所有项目的根文件夹。随便给你的名字取什么名字都行。

在本教程中,我们创建了一个名为TinyMD的Markdown编译器-这也是我拒绝的Netflix节目的名称,该节目讲述的是一只进入医学院的有知觉的蜘蛛。您不必创建新目录;我们将使用Rust的货物工具链、包管理器和项目构建工具来创建新目录,这些工具是在您安装Rust时随Rust编译器一起提供的。

我们将使用Cargo工具查看此项目和Rust中的所有项目,传递新参数并指定我们需要的内容:

命令Cargo new构建一个新项目,--bin标志告诉Cargo该项目将生成一个可执行文件(称为tinymd),而不是一个库。我将在不同的教程中介绍库和包系统,但现在您可以将其视为NPM、Rubygems或任何其他包系统。

一旦您运行了Cargo new命令,您将得到一个确认该项目已正确搭建的消息:

启动新的Rust项目后,让我们继续并打开该项目的根文件夹。

使用您喜欢的编辑器打开名为tinyms的新文件夹。如果您像我一样使用VS Code,您通常可以键入:

Cargo.toml文件,它是清单文件。这是项目配置和依赖项脚本。这类似于Ruby中的Gemfile或Node中的Package.json。

每当我们创建这样的新项目时,Rust都会为我们插入一些默认代码。在这里您可以看到,它只不过是一个简单的“Hello,world!”程序。

通过查看main.rs文件中预打包的虚拟代码,您可以直观地了解Rust的语法:

正如您可能已经猜到的那样:这看起来很像C;函数是用fn声明的;printf等效项是println!,它是一个宏,而不是函数;这个程序只做打印命令行的";Hello,world!";。

Rust中的宏封装代码并以开发人员友好的方式呈现,我们在这些教程中只用到少数几个,所有这些都是通过Rust提供的。宏使提供辩论工具变得很容易,这些工具可以帮助开发人员编写更流畅、更易读的代码,目前您可以将其视为超级函数。

我个人不喜欢新的Rust程序员从一开始就必须处理宏,但这只是Rust真正成为一种独特的编程体验的方式之一。

在我们未雨绸缪之前,让我们先把这个项目建起来。打开您的终端(如果您使用的是VS Code,可以通过键入Ctrl+`使用集成终端),然后输入Cargo Build:

$Cargo编译tinymd v0.1.0(C:\RustTutorials\tinymd)在1.19s$中完成了dev[未优化+调试信息]目标

您可以看到,Build命令在构建项目之前首先对其进行编译。那里的其他东西-dev[未优化+debuginfo]-告诉我们,我们编译和构建这个项目只是为了dev目的。代码没有优化,并且存在我们通常不会保留在发布版本中的调试符号。dev目标是Cargo构建命令的默认目标,相当于Debugbuild(在具有Debug或Release构建的范例中)。

默认的货物构建非常适合学习Rust,并且将是我们构建所有Rust项目的方式。它负责依赖项管理和检查,并为我们提供编译器(Rustc)的接口,这样我们就可以一站式地处理我们的代码。

此时,您应该在项目的根目录中看到一个名为target的新目录。它是通过使用CARADE BUILD命令的defaulttarget创建的,即Debug。在这里,您将找到项目的可执行文件。

我们将经常使用的第三个Cargo命令是Cargo Run,这是我们将如何运行我们的项目构建的可执行文件。

$Cargo在运行`target\debug\tinymd.exe`Hello,world!$后,在0.00s内完成了dev[未优化+调试信息]目标。

请注意,我们不需要传递项目名称;Rust将根据发布目标执行适当的.exe文件,从而为我们推断出这一点。

如果你看到“你好,世界!”在您的终端,然后祝贺您:您已经建立了您的第一个铁锈程序!

在下一节中,我们将深入研究Rust语言,并开始定制我们的markdown编译器,使其看起来像一个实际的程序。

如果您对本章有任何意见、担忧或投诉,请向我发送一些反馈。

本章的目标是培养学生对Rust中的基本函数和变量的信心。

我们项目的所有源文件都位于src目录中,为清楚起见,我们只将main.rs文件用于我们的markdown编译器。

提供的Hello World示例代码为我们提供了开始定制项目所需的所有信息。在本章结束时,不带参数执行我们的代码将输出一个横幅-一小段文本,包括程序名称、作者、简短描述和用法示例。

正如您从main()函数中看到的,没有返回值或参数的基本函数编写起来相当简单。让我们继续在文件的顶部编写一个名为Usage()的新函数。在它内部,我们将使用我们可以看到用于编写";Hello,world!";的相同代码来输出我们的工具的名称和简短描述:

fn用法(){println!(";tinymd,<;your name>;";);;}fn main(){println!(";Hello,world!););}。

如果您还没有这样做,请继续用您的名字替换<;您的名字。

要编译并运行它,您只需运行Cargo Run,如果它检测到您的源文件中有任何更改,它将在场景后面重新编译并重新编译您的项目:

如果您真的不想查看附带的信息(已完成的Devand运行行),您可以将-q标志传递给Cargo,它告诉它保持安静:

为了节省空间,我将在对代码进行更改时编译、构建和运行代码-所以当您看到-q标志时不要惊慌。

接下来,让我们将主函数主体中的Hello World行替换为对use()的调用:

干净利落!但是,无需争论、无需返回的功能几乎与充气飞镖板一样有用。为了改进use()函数,让它返回一个可以写入控制台窗口的简单值。

在本节中,我们将创建一个名为get_function()的函数,该函数将返回我们工具的某个任意版本号。

我们在前面看到,没有参数和返回值的函数是这样编写的:

假设我们的版本号是1000,我们希望从函数返回该版本号,然后将其打印出来。回想一下,unsignedinteger的范围是0到2X-1,这意味着我们需要将数字1000存储在至少16位的无符号整数中(范围是0到65,535)。我们在“铁锈”杂志上用关键词u16来表示这一点。

这将声明一个名为get_version的函数,该函数不接受任何参数,并返回sa u16。您可以看到,我们使用->;符号集指定返回值。

要返回值1000,我们可以使用return关键字(它的作用与听起来完全一样),也可以像Ruby那样只写数字。

在上面的两个示例中,函数的计算结果都是数字1000。这是因为Rust被认为是一种基于表达式的语言(就像Ruby!)。在基于表达式的语言中,一切都是表达式-并且表达式的计算结果是值。这意味着作为表达式的代码块的计算结果是一个值。因为函数是一块代码,所以函数的计算结果也是一个值。

请注意,当我们使用RETURN关键字时,我们只需要分号;数字1001本身就是块求值的值,而RETURN 1000;是一条语句-并且语句以分号结尾。

Rust社区(以及大多数基于表达式的语言)接受的方式是只对早期返回使用RETURN关键字-也就是说,当特定块中的最后一条语句可能不是该块可以计算的唯一值时使用返回关键字。例如,如果您希望根据版本号打印一些输出,可以执行如下操作:

fn get_version()->;u16{let version=1;//仅作为示例,如果版本<;2{返回1;}2}。

显然,这是一个毫无意义的代码块,但是您可以看到,该块的值将为1,并且在满足感的if表达式中,我们使用rereturn关键字来表示提前返回。这是提前返回,因为块通常的计算结果为2,但是在早期IF检查的情况下,它可能会比返回2时更早地返回值。

让我们通过在Usage()的println!()宏内调用新的get_version()函数,将其添加到我们的程序中:

fn get_version()->;u16{1000}fn use(){println!(";tinymd,<;your name>;";);println!(";version{}";,get_version());}fn main(){use();}

在这里,您可以看到我们如何将参数传递给println!(),就像在另一种语言中传递典型的printf函数一样。因为Rust为我们提供了这个宏,所以它可以识别您试图打印的变量类型,所以您不必担心指定类型。例如,在C的printf函数中,您可以使用%s来表示要打印字符数组的位置;Rust只要求您使用{},而不考虑变量的类型。(我们很快就会打印字符串,请耐心等待!)。

$Cargo运行编译tinymd v0.1.0(C:\RustTutorials\tinymd)在运行`target\debug\tinymd.exe`tinymd(由<;your name&>version 1000编写的标记编译器)时,在0.63秒内完成了dev[未优化+debuginfo]目标。

正在打印版本整数(1000),并且println!宏可以根据get_version()指定的返回类型推断它是一个整数。

这一章我们差不多读完了。现在我们已经对Rust的函数语法有了信心,接下来让我们学习如何创建简单变量并为其赋值。

我们将学习的第一种变量是整数。所有inRust变量都是通过将它们的类型放在它们的名称后面来声明的。例如,如果我们想要创建一个整数变量来保存应用程序的版本,我们可以这样声明一个变量版本:

仅出于示例目的,让version:u16;//i;m使用u16。这也可能是//一架U8。这取决于你的可变尺码。

Rust中的变量是用let关键字声明的,然后我们使用冒号(:)来描述变量的类型。所有变量都需要这样声明,除非变量类型可以通过(比方说)函数的返回值来推断。让我们练习使用这两种方法声明变量。

要将任意应用程序版本存储在变量中,让我们在use()函数的作用域中声明它。然后,不是让println!使用函数get_version()来打印版本,而是让它使用我们的局部变量:

fn get_version()->;u16{1000}fn use(){let the_version:u16;//声明我们的变量the_version=get_version();//为变量println!(";tinymd,<;your name>;";);println!(";version{}";,the_version);赋值。//打印赋值}fn main(){用法();}。

如您所见,用println中的变量替换函数!是相当直截了当的。此外,看看我们如何声明_version,然后将函数的值传递给它。Rust根据我们用来给变量赋值的函数的返回值来推断变量Wewant的类型。

如果您对本章有任何意见、担忧或投诉,请向我发送一些反馈。

我们在本章的目标是培养对Rust中的一些基本字符串操作的信心。

我们的小型标记编译器才刚刚诞生。我们有一个项目设置,如果我们运行这个程序,我们就能够输出一个横幅--一块有帮助的文本,通常说明程序是什么,是谁编写的,以及如何使用它。我们已经将此横幅包装在use()函数中。到最后一章结束时,我们已经输出了程序的版本。

在本章中,我们将通过从项目的清单文件(Cargo.toml)中提取数据来改进横幅,而不是将版本号等硬编码到main.rs中。要做到这一点,我们需要了解Rust是如何处理字符串的,也许最重要的是,是什么让Rust完全不同。

..