2023年,Rust能干掉JavaScript吗?

作者 | Josh Mo

译者 | 核子可乐

策划 | 李冬梅

如果大家已经拥有一定的 Rust Web 开发经验,应该听说过在前端 Web 开发上用 Rust(通过 WASM)还是用 JavaScript 这个充满争议性的话题。不少人旗帜鲜明表示反对,认为 Rust“不适合生产”,而且速率“比 JavaScript 还慢”。

这种说法也有道理:从历史上看,因为 WASM 无法访问 DOM,所以从 JavaScript 调用 WASM 确实会产生额外开销。但目前这方面的影响已经很小,基准数据显示,像 Leptos 和 Dioxus 这样的 Rust WASM 框架(底层使用 Sledgehammer,属于速度前三甲级别的 JavaScript 框架)在性能上已经优于 React 和 Vue 等大部分 JS 框架。感兴趣的朋友可以参考原始基准测试。

如图片所见,各框架按性能排序分别为原始 Javascript、Sledgehammer(Dioxus 的底层引擎)、wasm-bindgen(允许 WASM 模块和 Javascript 实现互操作的库)、Solid.js ,Vue 和 RxJS,之后是 Leptos、Dioxus、LitJS,接下来是 Sycamore……排在最末的才是 Vue 和 React(还有 Yew)。很明显,其中一些 Rust 前端框架甚至比最流行的 JavaScript 框架性能还好。千万别抬杠说也可以不用框架,直接编写纯 JavaScript 代码——确实可以,但这明显偏离本文讨论的主题了。

TechEmpower 发布的后端性能基准测试:

在前 10 大后端框架中,有 5 个是用 Rust 编写的。很明显,Rust 在后端框架领域占据着突出的优势,甚至能与 C++ 正面较量。有人可能会说 Rust 用作后端服务有点太过了——但它确实能带来更高性能,占用的内存更小、服务的运行稳定性更好、引发崩溃的可能性也更低。这些都是不容低估的重要因素,毕竟从企业的角度来看,尽可能节约成本永远都是高优先级事项。

但也必须承认,在选择新框架时,速度和常规性能往往并不足以构成综合决策的充分因素。开发者体验如何、错误处理功能是否强大、怎样解决 SSR 问题等也都非常重要。要想做出明智的最终选择,必须先为这些问题找到合理答案。幸运的是,Rust 同样是有备而来。

开发者体验

不管大家主观判断如何,在 Web 开发方面,Rust 有着相对宽松的使用要求。其中很多代码的样式上跟 React 等 Web 框架中的 JavaScript 组件非常相似——比如 Leptos(一款 Rust Web 框架)中的组件代码:

use leptos::*;
#[component]pub fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView { // create a reactive signal with the initial value let (value, set_value) = create_signal(cx, initial_value);
// create event handlers for our buttons // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures// (for reference: closures are like anonymous/arrow functions in Javascript) let clear = move |_| set_value(0); let decrement = move |_| set_value.update(|value| *value -= 1); let increment = move |_| set_value.update(|value| *value += 1);
// create user interfaces with the declarative `view!` macro view! { cx, <div> <button on:click=clear>"Clear"</button> <button on:click=decrement>"-1"</button> <span>"Value: " {value} "!"</span> <button on:click=increment>"+1"</button> </div> }}
// Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setuppub fn main() { mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=3 /> })}

可以看到,这些代码其实跟 JSX 区别不大,最大的不同就是该组件不返回任何内容,而是用 Rust 宏来渲染 HTML。其 main 函数类似于 React、Vue 乃至其他 JS 框架当中作用于 root 文件的 index.js 脚本。再来看另一个来自 Dioxus 的例子:

// An example of a navbarfn navbar(cx: Scope) -> Element {    cx.render(rsx! {        ul {            // NEW            Link { to: "/", "Home"}            br {}            Link { to: "/blog", "Blog"}        }    })}
// An example of using URL parametersfn get_blog_post(id: &str) -> String { match id { "foo" => "Welcome to the foo blog post!".to_string(), "bar" => "This is the bar blog post!".to_string(), id => format!("Blog post '{id}' does not exist!") }

可以看到,RSX(相当于 Dioxus 中的 React JSX)的编写非常简单,甚至可能比使用 Leptos 还简单一些。而且很明显,React 的组件设计理念已经超越了特定编程语言,在 Rust 这边也已经有所体现。大家甚至可以把这些函数跟单元结构体(unit structs)结合起来,为各种函数提供命名空间,这样就能实现对 API 调用之类的捆绑了,例如:

// this is a unit structpub struct APICalls;
// we can implement the unit struct to bundle functions under it// like so:Impl APICalls { pub async fn get_dog_api_data() -> Json<Dog> {... some code here// this should probably return some json data} pub async fn get_cat_api_data() -> Json<Cat> {... some code here// this should probably return some json data}}
fn navbar (cx: Scope) -> Element {// now we can call the data like this, or something similar let dogs = APICalls::get_dog_api_data().await;}

如大家所见,哪怕只是稍稍触及 Rust 的浅表层次,也已经能够获得相当不错的开发效果。而且真正让人眼前一亮的,还要数 Rust 的错误处理机制,这也是其优于 JavaScript 甚至是 TypeScript 的关键亮点之一。通常,如果使用 TypeScript 进行编码,我们只有两个选择:类型检查和 try-catch 块。但对于拥有一定开发经验的朋友们来说,不断把代友打包到 try-catch 块中仍然有其隐患。毕竟 TypeScript 仍可被编译为 JavaScript,所以一旦不小心就会引发跟 JS 相关的问题(CJS 和 ECMAscript 兼容问题,运行时内随时可能出现的随机错误等)。

下面来看看 Rust 的基本错误处理机制:

async fn foo() -> Result<String, String>{let bar = String::from("foobar!");// return is implicit, no need to write "return"match bar.trim() {    "foobar!" => Ok(bar),      _ => Err("Was not foobar!".to_string())             }   }
#[tokio::main]fn main() -> Result<String, String> {let Ok(res) = foo().await else { return Err("Was not foobar :(".to_string());}
println!("The string was: {res}!");}

这里展示了两个示例:我们可以使用基础模式匹配来确定字符串是什么,如果结果匹配则返回 OK;如果属于其他内容(会加注下划线),则只返回一个具有 String 类型的错误(也会提示 std::error::Error -,我们可以将其作为错误类型来处理)。我们还可以声明一个变量,要求该变量必须是实际的 Result 类型,否则执行其他操作(在示例中为提前返回)。之后,我们就可以使用 res 本体了,因为它将被声明为 Result 中包含的值。

生态系统

虽然 JavaScript 的生态系统(Node/npm)要比 Rust 庞大得多,但 Rust 阵营也完全能够满足大多数项目的需求。Rust 目前对数据库、Redis 和 Web 应用程序中所需的各种服务都提供良好支持,不管用哪种编程语言都能使用。

如果您打算构建 SaaS,Rust 正好准备了几乎包罗万象的工具箱:用于 SMTP 的 lettre、用于 Stripe 支付的 async-stripe,用于处理社交网络账户登录的 OAuth 回调 oauth2,用于数据库(甚至是 airtable)的 SQLx(如果倾向于对象关系映射,还有 Diesel 或 SeaORM 可以选择)。当然,还有用于 GPT-3 的 openai_api。在 SaaS 投入运行之后,Rust 甚至支持用于 RabbitMQ 的 lapin 和用于 Kafka 的 rs-rdkafka。由此看来,如果大家想开发一项坚如磐石的高性能服务,Rust 的表现完全可以跟 JavaScript 正面抗衡。

根据个人经验,我发现 cargo 在对接各种工具时表现突出。以 clippy 为例,这是一款无需初始化就能使用的出色工具程序,只要输入 cargo clippy 即可启用,它能检测出不必要的借用等部分、帮助我们快速优化代码。更重要的是,如果需要把一个项目中的配置迁移至另一项目,也可以直接在根目录下创建一个 clippy.toml 文件并随意加以配置。

由于 Rust 本身并不是普及度最高的 Web 编程语言,所以生态系统中各厂商对它的支持态度可能没那么积极,比如开放相应服务 API。但因为大多数服务 API 采取的都是 HTTP REST Web 服务的形式,所以 Rust 也能用得起来,大家还可以使用 reqwest 等工具检索自己需要的数据。

部   署

在部署方面,Shuttle 是迄今为止最简单的 Rust 部署方法。后端部署确实要麻烦一点,要么需要鼓捣配置文件、要么通过网站上的 GUI 添加环境变量来接入需要使用的服务,或者是提供相应的静态文件。

Shuttle 的另一个优点就是采取基础设施即代码的实现理念,可以通过代码注释快速上手。只需简单通过 Rust 宏在 main 函数中声明,大家就能避免亲自动手鼓捣配置文件。我们可以借此交付数据库并支持静态文件,从能够编译为静态资产的 Next.js、React 等 JS 框架处添加编译前端,例如:

// main.rs#[shuttle_runtime::main]pub async fn axum (#[shuttle_shared_db::Postgres] postgres: PgPool,#[shuttle_secrets::Secrets] secrets: SecretStore,#[shuttle_static_folder] static: PathBuf) -> shuttle_axum::ShuttleAxum {// carry out database migrations (this assumes migrations are idempotent)    sqlx::migrate!().run(&postgres).await.expect("Migrations failed :(");
let hello_world = secrets.get("MY_VARIABLE") .expect("Is MY_VARIABLE set in Secrets.toml?");
// Make a router serving API routes that require a DB connection let api_router = create_api_router(postgres);
// Add a compiled frontend (like e.g. from Next.js, React, Vue etc) to the router let router = Router::new() .nest("/api", api_router) .nest_service("/", get_service(ServeDir::new(static) .handle_error(handle_error));
// Rust returns implicitly so writing "return" is not requiredOk(router.into())}

总   结

综上所述,Rust 无疑是一款值得用于 Web 开发的优秀语言。凭借着内存占用小、性能水平高、正常运行时间长和运维成本低等优势,Rust 将帮助您在前端领域节约下宝贵的时间和金钱。

原文链接:

https://joshuamo876.bearblog.dev/can-rust-beat-javascript-in-2023/

声明:本文为 InfoQ 翻译,未经许可禁止转载。

本文文字及图片出自 InfoQ

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注