当前位置: 首页 > 科技观察

给JavaScript开发者的Rust简介

时间:2023-03-21 18:25:29 科技观察

Rust是一种编程语言,它于2010年起源于MozillaResearch。现在所有的大公司都在使用它。亚马逊和微软都认可它是他们系统中C/C++的最佳替代品,但Rust并不止于此。像Figma和Discord这样的公司现在也通过在他们的客户端应用程序中使用Rust来引领潮流。本Rust教程旨在简要介绍Rust、如何在浏览器中使用它以及何时应该考虑使用它。我将从比较Rust和JavaScript开始,然后引导您完成Rust在浏览器中运行的步骤。最后,我将对使用Rust和JavaScript的COVID模拟器Web应用程序进行快速性能评估。简而言之,Rust在概念上与JavaScript非常不同。但有一些相似之处需要指出,所以让我们看看问题的两面。相似之处两种语言都有一个现代的包管理系统。JavaScript有npm,Rust有Cargo。Rust使用Cargo.toml而不是package.json来进行依赖管理。要创建一个新项目,请使用cargoinit,要运行它,请使用cargorun。不会太陌生吧?Rust中有很多你已经从JavaScript中了解到的很酷的特性,只是语法略有不同。使用以下常见JavaScript模式对数组中的每个元素应用闭包:letstaff=[{name:"George",money:0},{name:"Lea",money:500000},];letsalary=1000;staff。forEach((员工)=>{员工.money+=薪水;});在Rust中,我们可以这样写:letsalary=1000;staff.iter_mut().for_each(|employee|{employee.money+=salary;});不可否认,习惯这种语法需要时间,使用竖线(|)而不是括号。但是在克服了最初的尴尬之后,我发现它比另一组括号读起来更清楚。作为另一个例子,这是JavaScript中的对象解构:letpoint={x:5,y:10};let{x,y}=point;同样在Rust中:letpoint=Point{x:5,y:10};letPoint{x,y}=point;主要区别在于,在Rust中我们必须指定类型(Point),更一般地说,Rust需要在编译时知道所有类型。但与大多数其他编译语言不同,编译器会尽可能多地自行推断类型。为了进一步解释这个问题,下面是适用于C++和许多其他语言的代码。每个变量都需要一个明确的类型声明。inta=5;floatb=0.5;floatc=1.5*a;在JavaScript和Rust中,这段代码是有效的:leta=5;letb=0.5;letc=1.5*a;共享功能太多无法一一列举:Rust具有async+await语法。可以像letarray=[1,2,3]一样简单地创建数组。代码组织在具有显式导入和导出的模块中。字符串以Unicode编码,处理特殊字符没有问题。我可以继续说下去,但我想我的观点现在已经很清楚了。Rust具有一组丰富的特性,现代JavaScript中也使用了这些特性。不同之处在于Rust是一种编译语言,这意味着没有运行时来执行Rust代码。应用程序只能在编译器(rustc)发挥其魔力后才能运行。这种方法的好处通常是更好的性能。幸运的是,Cargo负责为我们调用编译器。使用webpack,我们还可以将Cargo隐藏在npmrunbuild后面。通过本指南,只要您为项目设置Rust,就可以保留Web开发人员的正常工作流程。Rust是一种强类型语言,这意味着在编译时所有类型都必须匹配。例如,您不能调用参数类型错误或参数数量错误的函数。编译器会在您在运行时遇到此错误之前为您捕获它。最明显的比较是TypeScript,如果你喜欢TypeScript,你很可能会喜欢Rust。但别担心:如果你不喜欢TypeScript,Rust可能仍然适合你。Rust是近年来从头开始构建的,考虑到了人类在过去几十年中在编程语言设计中学到的一切。结果是一种令人耳目一新的简洁语言。Rust中的模式匹配是我最喜欢的功能之一,其他语言有switch和case来避免像这样的长链:if(x==1){//...}elseif(x==2){//...}elseif(x==3||x==4){//...}//...Rust使用更优雅的匹配如下:matchx{1=>{/*Dosomethingifx==1*/},2=>{/*Dosomethingifx==2*/},3|4=>{/*Dosomethingifx==3||x==4*/},5...10=>{/*Dosomethingifx>=5&&x<=10*/},_=>{/*Catchalothercases*/}}我认为这非常巧妙,我希望JavaScript开发人员也能欣赏这种语法扩展。不幸的是,我们仍然不得不谈论Rust的阴暗面。坦率地说,使用严格的类型系统有时会让人觉得很麻烦。如果您认为C++或Java具有严格的类型系统,请准备好在Rust中体验一番。就个人而言,我喜欢Rust的这一部分。我依赖于一个严格的类型系统,这样我就可以关闭大脑中每当我发现自己在编写JavaScript时就会猛烈燃烧的部分。但是我知道对于初学者来说总是和编译器作对是很烦人的。我们稍后会在Rust教程中看到其中的一些内容。HelloRust现在,让我们用Rust在浏览器中运行一个helloworld,我们首先要确保安装了所有必要的工具。该工具使用rustup安装Cargo+rustc。Rustup是推荐的Rust安装方式,它会安装最新稳定版的Rust编译器(rustc)和包管理器(Cargo)。它将安装最新稳定版本的Rust编译器(rustc)和包管理器(Cargo)。它还管理测试版和每晚构建,但对于本示例而言这不是必需的。在终端中键入cargo--version以检查安装,您应该会看到类似cargo1.48.0(65cbdd2dc2020-10-14)的内容。还要检查Rustup:rustup--version应该生成rustup1.23.0(00924c9ba2020-11-27)。安装wasm-pack。这是为了将编译器与npm集成。通过输入wasm-pack--version来检查安装,它应该会给你类似wasm-pack0.9.1的东西。我们还需要Node和npm。我们有一整篇文章[1]解释了安装这两个的最佳方法。编写Rust代码现在一切都已安装,让我们创建项目。最终代码也可以在这个GitHub存储库[2]中找到。我们从一个编译成npm包的Rust项目开始,然后是导入该包的JavaScript代码。要创建名为hello-world的Rust项目,请使用cargoinit--libhello-world。这将创建一个新目录并生成Rust库所需的所有文件:├──hello-world├──Cargo.toml├──src├──lib.rsRust代码将放在lib.rs中,之前我们必须调整Cargo.toml。它使用TOML[3]定义依赖项和其他包信息。如果您想在浏览器中看到helloworld,请在Cargo.toml的某处添加以下行(例如,在文件末尾)。[lib]crate-type=["cdylib"]这告诉编译器以C兼容模式创建一个库。显然我们在示例中没有使用C。C兼容只是意味着不是Rust特定的,这是我们在JavaScript中使用库所需要的。我们还需要两个外部库,将它们作为单独的行添加到依赖项部分。[dependencies]wasm-bindgen="0.2.68"web-sys={version="0.3.45",features=["console"]}这些是来自crates.io[4]的依赖项,这是Cargo的默认包存储库使用。wasm-bindgen[5]是创建入口点所必需的,我们稍后可以从JavaScript调用它。(您可以在此处找到完整的文档。)值“0.2.68”指定版本。web-sys[6]包含所有WebAPI的Rust绑定,这将使我们能够访问浏览器控制台。请注意,我们必须明确选择控制台功能,我们最终的二进制文件将仅包含如此选择的WebAPI绑定。接下来是lib.rs中的实际代码。可以删除自动生成的单元测试。只需将文件内容替换为以下代码:usewasm_bindgen::prelude::*;useweb_sys::console;#[wasm_bindgen]pubfnhello_world(){console::log_1("Helloworld");}顶部的use语句就是从Othermodules中读取import项目。这类似于JavaScript中的导入)。pubfnhello_world(){...}声明了一个函数。pub修饰符是“public”的缩写,其作用类似于JavaScript中的export。注意#[wasm_bindgen]Rust特定的编译为[WebAssembly(Wasm)](https://webassembly.org/"wasm_bindgen]“)”)。我们在这里需要它来确保编译器将包装函数公开给JavaScript。在函数体中,“Helloworld”被打印到控制台。Rust中的console::log_1()是调用console.log()的包装器。您是否注意到函数调用中的_1后缀?这是因为JavaScript允许可变数量的参数,而Rust不允许。为了解决这个问题,wasm_bindgen为每个参数数量生成一个函数。是的,这会很快变得丑陋!但它有效。web-sys文档[7]中提供了可以在Rust控制台中调用的完整函数列表。现在我们应该一切就绪,尝试使用以下命令编译它。这将下载所有依赖项并编译项目,第一次可能需要一段时间。cdhello-worldwasm-packbuild哈!Rust编译器对我们不满意。错误[E0308]:类型不匹配-->src\lib.rs:6:20|6|console::log_1("Helloworld");|^^^^^^^^^^^^^^expectedstruct`JsValue`,found`str`|=note:expectedreference`&JsValue`foundreference`&'staticstr注意:如果你看到其他错误(error:linkingwithccfailed:exitcode:1)并且你使用的是Linux,则交叉编译缺失依赖。sudoaptinstallgcc-multilib应该可以解决这个问题。正如我之前提到的,编译器是严格的。当它希望将对JsValue的引用作为函数参数时,它不会接受静态字符串。必须进行显式转换才能满足编译器的要求。console::log_1(&"Helloworld".into());方法[into()](https://doc.rust-lang.org/std/convert/trait.Into.html"into("into()")")将一个值转换为另一个值。Rust编译器足够聪明,可以推迟哪些类型参与转换,因为函数签名只留下一种可能性。在这种情况下,它将被转换为JsValue,这是一种由JavaScript管理的值的包装器类型。然后,我们必须添加&以通过引用而不是值传递,否则编译器会再次报错。尝试再次运行wasm-packbuild,如果一切顺利,最后一行应该如下所示:[INFO]::-)Yourwasmpkgisreadytopublish/home/username/intro-to-rust/hello-world/pkg。如果你可以通过这一步,你现在可以手动编译Rust。下一步,我们会将其与npm和webpack集成,它们会自动为我们完成这项工作。JavaScript集成在这个例子中,我决定将package.json放在hello-world目录中。我们也可以为Rust项目和JavaScript项目使用不同的目录,这是一个品味问题。下面是我的package.json文件。最简单的方法是复制它并运行npminstall,或运行npminit并仅复制开发依赖项:{"name":"hello-world","version":"1.0.0","description":"HelloworldappforRustinthebrowser.","main":"index.js","scripts":{"build":"webpack","serve":"webpackserve"},"author":"JakobMeier","license":"(MITORApache-2.0)","devDependencies":{"@wasm-tool/wasm-pack-plugin":"~1.3.1","@webpack-cli/serve":"^1.1.0","css-loader":"^5.0.1","style-loader":"^2.0.0","webpack":"~5.8.0","webpack-cli":"~4.2.0","webpack-dev-server":"~3.11.0"}}如您所见,我们使用的是webpack5。即使没有捆绑器,wasm-pack也可以与旧版本的webpack一起使用。但是每个设置的工作方式略有不同,我建议您在学习本Rust教程时使用完全相同的版本。另一个重要的依赖项是wasm-pack-plugin。这是一个专门用于加载使用wasm-pack构建的Rust包的Webpack插件。继续,我们还需要创建webpack.config.js文件来配置webpack。它应该是这样的:constpath=require('path');constwebpack=require('webpack');constWasmPackPlugin=require("@wasm-tool/wasm-pack-plugin");module.exports={entry:'./src/index.js',输出:{path:path.resolve(__dirname,'dist'),filename:'index.js',},plugins:[newWasmPackPlugin({crateDirectory:path.resolve(__dirname,".")}),devServer:{contentBase:"./src",hot:true,},module:{rules:[{test:/\.css$/i,use:["style-loader""css-loader"],},]},实验:{syncWebAssembly:true,},};所有路径都被并排配置为Rust代码和JavaScript代码。index.js将在src文件夹中,就在lib.rs旁边。如果您喜欢不同的设置,请随意调整这些设置。您还会注意到我们使用了webpackexperiments[8],这是webpack5引入的一个新选项。确保将syncWebAssembly设置为true。最后,我们必须创建JavaScript入口点src/index.js:import("../pkg").catch(e=>console.error("FailedloadingWasmmodule:",e)).then(rust=>rust。你好世界());我们必须异步加载Rust模块。调用rust.hello_world()会调用生成的包装函数,该函数又会调用lib.rs中定义的Rust函数hello_world。现在,运行npmrunserve应该会编译所有内容并启动开发服务器。我们没有定义HTML文件,因此页面上没有显示任何内容。您可能还必须手动转到http://localhost:8080/index,因为http://localhost:8080只是列出文件而不执行任何代码。打开空白页面后,打开开发人员控制台。应该有一个HelloWorld的日志条目。好吧,对于一个简单的helloworld来说,这是相当多的工作。但是现在一切就绪,我们可以轻松地扩展Rust代码而不必担心。将更改保存到lib.rs后,您应该会自动在浏览器中看到重新编译和实时更新,就像JavaScript一样。何时使用RustRust不是JavaScript的一般替代品。它只能通过Wasm在浏览器中运行,这在很大程度上限制了它的实用性。即使你可以用Rust替换几乎所有的JavaScript代码,如果你真的想这样做,那也不是一个好主意,而且这不是Wasm的目的。例如,Rust不太适合与您网站的UI进行交互。我认为Rust+Wasm是一个额外的选项,可用于更高效地运行CPU繁重的工作负载。Wasm以更大的下载量为代价,避免了JavaScript代码面临的解析和编译开销。这与编译器的强大优化相结合,可能会带来更好的性能。这通常是公司为特定项目选择Rust的原因。选择Rust的另一个原因可能是语言偏好,但这是一个完全不同的讨论,我不会在这里深入讨论。参考文献[1]文章:https://www.sitepoint.com/quick-tip-multiple-versions-node-nvm/[2]GitHub存储库:https://github.com/sitepoint-editors/rust-wasm-你好世界[3]TOML:https://github.com/toml-lang/toml[4]crates.io:https://crates.io/[5]wasm-bindgen:https://crates。io/crates/wasm-bindgen[6]web-sys:https://crates.io/crates/web-sys[7]web-sys文档:https://rustwasm.github.io/wasm-bindgen/api/web_sys/console/index.html[8]webpack实验:https://webpack.js.org/configuration/experiments/本文转载自微信公众号《前端全栈开发》,可以关注通过以下二维码。转载本文请联系前端全栈开发公众号。