跨境派

跨境派

跨境派,专注跨境行业新闻资讯、跨境电商知识分享!

当前位置:首页 > 卖家故事 > 作为前端TypeScript开发人员学习Rust的经验

作为前端TypeScript开发人员学习Rust的经验

时间:2024-04-22 14:10:39 来源:网络cs 作者:焦糖 栏目:卖家故事 阅读:

标签: 学习  经验 
阅读本书更多章节>>>>

​ Like many developers, I began my career in programming by focusing on web technologies. I believe this is a great place to start and JavaScript, the language of the internet and a lot besides, is an incredibly versatile choice.

像许多开发人员一样,我的编程生涯始于专注于前端Web技术。我相信这是一个很好的开始和JavaScript,互联网的语言和很多除此之外,是一个令人难以置信的多才多艺的选择。 ​

As I have become more experienced with high level languages like JavaScript, I have also become more interested in how they work: what choices and tradeoffs are they making, and what are the benefits and costs of higher level abstractions.
随着我对高级语言(如JavaScript)越来越有经验,我对它们如何工作也越来越感兴趣:它们正在做出什么样的选择和权衡,以及更高级别抽象的好处和成本是什么。

For me, one of the best ways to get this deeper understanding is to learn a low level programming language. After all, these are the languages that typically parse and interpret our JavaScript code. For example, both the V8 engine (used by Google Chrome and Node.js) and WebKit (used by Safari and Bun) are written in C++. But despite being a mainstay of low-level programming, C++ wasn’t my language of choice…
对我来说,获得这种更深入理解的最好方法之一是学习一门低级编程语言。毕竟,这些语言通常解析和解释我们的JavaScript代码。例如,V8引擎(Google Chrome和Node.js使用)和WebKit(Safari和Bun使用)都是用C++编写的。但是,尽管C++是低级编程的中流砥柱,但它并不是我的首选语言。

Why Rust? 为什么是Rust?

​ Of the great low level programming languages, Rust is the most exciting to me. Last year, for the eighth year in a row, Rust was the most admired programming language in Stack Overflow’s annual survey.

在所有优秀的底层编程语言中,Rust是最令我兴奋的。去年,Rust连续第八年成为Stack Overflow年度调查中最受赞赏的编程语言。 ​

The language promises runtime performance in the same league as C and C++, but with a strict type system, lots of memory safety features and a more proactive approach to error handling — so that you can avoid the overhead of garbage collector, safe from the risk of memory leaks that are much easier to create in a language like C.
该语言承诺与C和C++相同的运行时性能,但具有严格的类型系统,大量的内存安全功能和更主动的错误处理方法-这样您就可以避免垃圾收集器的开销,安全地避免内存泄漏的风险,这在C等语言中更容易创建。

​ Rust is versatile, featuring paradigms from both object-oriented and functional programming, for the third year running it has been the most popular language used to for Web Assembly, and it has even become an important language in the Linux Kernel. Rust is also making waves in the world of JavaScript, where it has been used to build important projects such as Deno and, more recently, LLRT (Amazon’s Low Latency Runtime for serverless functions). Rust是通用的,具有面向对象和函数式编程的范例,它已经连续第三年成为用于Web Assembly的最流行的语言,它甚至已经成为Linux内核中的重要语言。Rust也在JavaScript领域掀起了波澜,它已被用于构建重要的项目,如Deno和最近的LLRT(Amazon的无服务器函数低延迟扩展)。 ​

How to learn Rust 如何学习Rust

Much like any other programming language, I believe the best way to learn Rust is by trying to program something in the language.
就像任何其他编程语言一样,我相信学习Rust的最好方法是尝试用这种语言编程。

However, Rust’s initial learning curve seems steeper than other languages I have tried in recent years, so it’s worth taking longer going through introductory materials before jumping into using the language.
然而,Rust的初始学习曲线似乎比我近年来尝试过的其他语言更陡峭,因此在开始使用该语言之前,花更长的时间阅读介绍性材料是值得的。

​ The Rust organization’s website has great recommendations. Right now, these are The Book, the Rustlings course, and Rust by Example. I also recommend Rust by Practice, which is an interactive course similar to Rustlings. Rust组织的网站有很好的推荐。现在,这些是书,Rustlings课程,和Rust by Example。我还推荐Rust by Practice,这是一个类似于Rustlings的互动课程。 On YouTube, the NoBoilerplate channel helped get me excited about the language and is a great source of explanations about general Rust concepts. If you’re interested in hosting Rust, AWS has a good blog post about growing support for Rust on their platform. 在YouTube上,NoBoilerplate频道帮助我对这门语言感到兴奋,并且是解释一般Rust概念的重要来源。如果您对托管Rust感兴趣,AWS有一篇关于在其平台上增加对Rust的支持的好博客文章。 ​

The remainder of this article is not a beginner’s guide to Rust. If that’s what you’re looking for, I recommend following the links above. Instead, I share my thoughts on some of the most significant differences in developer experience using Rust in comparison to the language I use professionally every day, TypeScript.
本文的其余部分不是Rust的初学者指南。如果这就是你正在寻找的,我建议你点击上面的链接。相反,我分享了我对使用Rust的开发人员体验与我每天专业使用的语言TypeScript相比的一些最显著差异的想法。

The compiler 编译器

The Rust compiler is often cited as one of the best parts ofRust, though, for beginners, it can also feel like one of the most annoying!
Rust编译器经常被认为是Rust最好的部分之一,尽管对于初学者来说,它也可能是最烦人的部分之一!

Coming from TypeScript, I was surprised at how much the compiler changes the experience of coding. Like many developers, I typically eschew proper debugging tools in favour of liberally logging values. But in Rust, you can only log values once the compiler is happy.
从TypeScript开始,我对编译器改变编码体验的程度感到惊讶。像许多开发人员一样,我通常避开适当的调试工具,而倾向于自由地记录值。但在Rust中,只有编译器满意时才能记录值。

There are cases where this has proved frustrating: for example, I wanted to log a requested JSON payload before writing strict types for the deserialization step. (Later, I learned that this can be done with the serde_json::Value type).
在某些情况下,这被证明是令人沮丧的:例如,我想在为格式化步骤编写严格类型之前记录所请求的JSON有效负载。(后来,我了解到这可以用 serde_json::Value 类型来完成)。

But in general, working to satisfy the compiler has meant that typically, when I run my code, it works as I expected. The tradeoff here seems pretty clear. For beginners, at least, it does take longer to get code running, but when that code does run, it is safer, more predictable and more performant. You put in more work at the writing stage, but the chance of errors or issues with memory of performance seems lower — and those benefits feel increasingly important in the context of large, growing projects.
但一般来说,满足编译器的要求意味着,当我运行代码时,它通常会按照我的预期工作。这里的权衡似乎很清楚。至少对于初学者来说,让代码运行确实需要更长的时间,但是当代码运行时,它更安全,更可预测,性能更高。你在写作阶段投入了更多的工作,但错误或性能记忆问题的可能性似乎更低-这些好处在大型,不断增长的项目中变得越来越重要。

Developers coming from languages where error messages are less useful may find that they’re preconditioned to scan over errors quickly. But so far, I have found Rust compiler errors to be very good, often telling you exactly what you need to do to get the code running — the more experienced I become with the language and its types, the better I am becoming at understanding what the compiler is trying to tell me!
来自错误消息不太有用的语言的开发人员可能会发现,他们已经习惯于快速扫描错误。但到目前为止,我发现Rust编译器错误非常好,经常告诉你需要做什么才能让代码运行-我对语言及其类型的经验越丰富,我就越能理解编译器试图告诉我的东西!

The type system 类型系统

​ I know not every JavaScript developer loves TypeScript —see, for example, this (in)famous blog post — but I can’t imagine writing large JavaScript apps without types. However, TypeScript has its weaknesses, and I have recognised that there are many benefits to having a language where types are a first class citizen. Rust’s type system is often praised as one of its best features. 我知道并不是每个JavaScript开发者都喜欢TypeScript --例如,这篇著名的博客文章--但我无法想象编写没有类型的大型JavaScript应用程序。然而,TypeScript有它的弱点,我已经认识到拥有一种类型是一等公民的语言有很多好处。Rust的类型系统经常被称赞为它最好的特性之一。 ​

That said, the parts of Rust’s type system that were new to me are not unique to Rust, but a feature of pretty-much all low level languages. For example, like other low level languages, Rust allows us to be very specific about how much space in memory we want a variable to take up. If we know a numerical value will always be an integer between 0 and 255, we can assign it to u8 , which has an 8-bit length. Or if our number could be above 255, but we know if will be below 65,535, then we could assign it to the 16-bit u16 type— and so on.
也就是说,Rust的类型系统中对我来说是新的部分并不是Rust独有的,而是几乎所有低级语言的特性。例如,像其他低级语言一样,Rust允许我们非常具体地确定变量在内存中需要占用多少空间。如果我们知道一个数值总是0到255之间的整数,我们可以将它分配给 u8 ,它有8位长度。或者,如果我们的数字可能高于255,但我们知道如果将低于65,535,那么我们可以将其分配给16位 u16 类型-等等。

​ In some ways, though, Rust does goes further than other low level languages. For example, it provides at least eight string types versus the one char[] type of C, which help us avoid footguns. (Though, fear not, as most use-cases are covered by &str and String!) 在某些方面,Rust确实比其他低级语言走得更远。例如,它提供了至少八种字符串类型,而C只有一种 char[] 类型,这有助于我们避免使用footguns。(不过,不要担心,因为大多数用例都包含在 &str 和 String 中!) ​

TypeScript, of course, doesn’t offer anywhere near this level of granularity because, by design, JavaScript doesn’t want us to worry about memory management and so allocates memory for us. This saves us a job, but is less efficient, as the JavaScript engine must allocate memory dynamically while the program is running. On a small scale, this makes very little difference. But in a large application, more efficient and purposeful memory allocation can make a programmes take up a much smaller memory footprint.
当然,TypeScript并没有提供这种级别的粒度,因为根据设计,JavaScript不希望我们担心内存管理,因此为我们分配内存。这为我们节省了一项工作,但效率较低,因为JavaScript引擎必须在程序运行时动态分配内存。在小范围内,这几乎没有什么区别。但在大型应用程序中,更有效和有目的的内存分配可以使程序占用更小的内存。

Memory allocation 内存分配

In TypeScript, we superimpose type annotations on top of Javascript — a language which doesn’t read our types — and they are removed whenever our TypeScript code is built.
在TypeScript中,我们将类型注释放在JavaScript之上-一种不读取我们类型的语言-并且每当我们构建TypeScript代码时都会删除它们。

In Rust, like other languages where types are a first class citizen, a type annotation is more than just annotation—it allocates memory for that specific type and it assures us that the value will have the given type.
在Rust中,像其他语言一样,类型是一等公民,类型注释不仅仅是注释-它为特定类型分配内存,并确保值将具有给定的类型。

For example, if we pass the i8 type to the parse method below, it reserves 8 bits of memory for small_int .
例如,如果我们将 i8 类型传递给下面的 parse 方法,它将为 small_int 保留8位内存。

let small_int = "127".parse::<i8>().unwrap();

The parse method can even infer the type from the variable type, so we can also write:
parse 方法甚至可以从变量类型推断类型,所以我们也可以写:

let small_int: i8 = "127".parse().unwrap();

In this instance, the compiler will also shout at us if we try to go beyond the memory allowed by the given type. So if we try to parse the string "128" as an i8, we’ll be unable to compile.
在这种情况下,如果我们试图超出给定类型所允许的内存,编译器也会对我们大喊大叫。因此,如果我们试图将字符串 "128" 解析为 i8 ,我们将无法编译。

Compare TypeScript, where type markers are simply that — markers. They do not change the underlying type or the memory allocated. Below, TypeScript expects x to be a string. But in the underlying JavaScript, it will be a number.
比较TypeScript,其中类型标记只是标记。它们不会更改基础类型或分配的内存。下面,TypeScript期望 x 是字符串。但在底层JavaScript中,它将是一个数字。

const x = 10 as unknown as string;

This example is a little unfair on the TypeScript compiler; we are using unknownas an escape hatch to forcibly allocate the wrong type!
这个例子在TypeScript编译器上有点不公平;我们使用 unknown 作为退出舱口来强制分配错误的类型!

However, this is a simple example that is easy to catch. In real-world applications, when dealing with more complex data types or data fetched from third parties, it is easier for TypeScript to misrepresent reality.
然而,这是一个简单的例子,很容易理解。在现实世界的应用程序中,当处理更复杂的数据类型或从第三方获取的数据时,TypeScript更容易歪曲现实。

Error handling 错误处理

Let’s again take the example of converting a string to an integer. This time, let’s imagine our integer is a string supplied by the user, so we can no longer guarantee that we can parse it correctly.
让我们再举一个将字符串转换为整数的例子。这一次,让我们假设我们的整数是用户提供的字符串,所以我们不能再保证我们可以正确地解析它。

let parsed_int = submitted_str.parse::<i32>().unwrap();

Here, we are using unwrap to get the value of a successful parse. But this approach is generally discouraged. Instead, Rust provides us with the Result enum, which forces us to handle errors manually.
在这里,我们使用 unwrap 来获取成功解析的值。但这种方法通常不被鼓励。相反,Rust为我们提供了 Result 枚举,这迫使我们手动处理错误。

We can still cause our program to panic with the panic! macro, but we can pass a custom error message which will help us quickly understand what went wrong:
我们仍然可以使用 panic! 宏导致程序死机,但是我们可以传递一个自定义的错误消息,这将帮助我们快速了解错误所在:

let parsed_int_result = submitted_str.parse::<i32>();let parsed_int = match parsed_int_result {    Ok(data) => data,    Err(error) => panic!(        "The given string cannot be parsed to an integer: {:?}",        error    ),};

Or we can return a default value — in this case 0:
或者我们可以返回一个默认值-在本例中为 0 :

let parsed_int_result = submitted_str.parse::<i32>();let parsed_int = match parsed_int_result {    Ok(data) => data,    Err(error) => 0,};

There is also a shorthand method for this: unwrap_or_default .
也有一个简单的方法: unwrap_or_default 。

Of course, this sort of behaviour is possible in JavaScript, but the difference is that in JavaScript you have to opt in, whereas in Rust you have to opt out by using unwrap .
当然,这种行为在JavaScript中是可能的,但不同的是,在JavaScript中,你必须选择加入,而在Rust中,你必须使用 unwrap 选择退出。

Or, to put it another way, in JavaScript you have to consciously handle errors. Whereas in Rust, you are forced to either handle the errors or consciously decide you only care about the successful path.
或者,换句话说,在JavaScript中,你必须有意识地处理错误。而在Rust中,你被迫要么处理错误,要么有意识地决定你只关心成功的道路。

Optional values 可选值

Rust uses a similar approach to handling optional values. In TypeScript we can use the convenient ? to indicate a value may be undefined .
Rust使用类似的方法来处理可选值。在TypeScript中,我们可以使用方便的 ? 来指示值可能是 undefined 。

interface User {  _id: string;  name?: string;}function sayHello(user: User) {  return `Hello ${user.name}!`;}

This TypeScript code will compile without issues, even though there is a risk that we return something that we don’t want!
这段TypeScript代码编译起来不会有问题,即使有返回一些我们不想要的东西的风险!

But if we write something similar in Rust using the Optionenum, we’ll get a compile-time error.
但是如果我们在Rust中使用 Option enum编写类似的东西,我们会得到一个编译时错误。

struct User {  _id: String,  name: Option<String>,}fn say_hello(user: User) -> String {    let name = user.name;    format!("Hello {name}!")}

The code above warns us that we cannot use Option inside our format! macro — preventing us from returning something unexpected. Instead, we are forced to handle this possibility. Here’s one solution, using match :
上面的代码警告我们不能在 format! 宏中使用 Option -防止我们返回意外的东西。相反,我们被迫处理这种可能性。这里有一个解决方案,使用 match :

struct User {  _id: String,  name: Option<String>,}fn say_hello(user: User) -> String {  let name: String = match user.name {    Some(name) => name,    None => "world".to_string(),  };  format!("Hello {name}!")}

Once again, this is achievable in TypeScript— and it’s much more concise. But the key difference between the two languages is that, in TypeScript, the onus is on the developer to recognise the potential problem, so we don’t end up returning "Hello undefined" . But in Rust, our code will not compile unless we handle the scenario that name is not available.
再一次,这在TypeScript中是可以实现的-而且它更简洁。但这两种语言之间的关键区别在于,在TypeScript中,开发人员有责任识别潜在的问题,所以我们最终不会返回 "Hello undefined" 。但在Rust中,我们的代码将无法编译,除非我们处理name不可用的情况。

In a simple example like this, it can be hard to recognise the benefits of the more long-winded approach, because it’s easy to see what could go wrong. But if you’ve ever worked on a large application, it’s clear that Rust’s opt-out approach can save us from lots of potential accidents.
在这样一个简单的例子中,可能很难认识到更冗长的方法的好处,因为很容易看到可能出错的地方。但如果你曾经在一个大型应用程序上工作过,很明显Rust的选择退出方法可以保存我们从许多潜在的事故。

Ownership and borrowing 所有权和借款

Finally, I’d like to talk about ownership and borrowing, concepts which are much more meaningful in the context of a low-level language like Rust than a high level language like TypeScript.
最后,我想谈谈所有权和借用,这些概念在Rust这样的低级语言中比在TypeScript这样的高级语言中更有意义。

In TypeScript, we need to be aware of whether we are mutating a value or cloning it.
在TypeScript中,我们需要知道我们是在改变一个值还是在克隆它。

const arrayToBeMutated: string[] = ["d", "c", "b", "a"];const arrayToBeCloned: string[] = ["d", "c", "b", "a"];arrayToBeMutated.sort();arrayToBeCloned.toSorted();console.log(arrayToBeMutated);  // ["a", "b", "c", "d"]console.log(arrayToBeCloned);   // ["d", "c", "b", "a"]

In the TypeScript code above, sort mutates the array in-place, changing the original value. But toSorted creates a clone, which we could assign to a new variable, and leaves the original array untouched.
在上面的TypeScript代码中, sort 就地改变了数组,改变了原始值。但是 toSorted 创建了一个克隆,我们可以将其分配给一个新变量,并保持原始数组不变。

In general, non-destructive methods like toSorted are often preferred in languages like TypeScript, because keeping track of mutated variables can be tricky and — unless there are clear benefits to memory or performance — it’s typically considered better to avoid doing it altogether.
一般来说,像 toSorted 这样的非破坏性方法在TypeScript这样的语言中通常是首选的,因为跟踪突变的变量可能很棘手-除非对内存或性能有明显的好处-通常认为最好避免这样做。

However, Rust allows us to go deeper and to be much more explicit about mutating or cloning values, with the benefit that we can be more efficient with memory and also free up memory more easily once a value has performed its glorious purpose.
然而,Rust允许我们更深入,更明确地改变或克隆值,其好处是我们可以更有效地使用内存,并且在值完成其光荣目的后更容易释放内存。

For a start, all variables are immutable by default, and must be explicitly marked as mutable with the mut keyword.
首先,所有变量在默认情况下都是不可变的,并且必须使用 mut 关键字显式地标记为可变的。

This code throws an error:
这段代码抛出一个错误:

let foo = 10;foo += 10;

This code doesn’t: 这段代码没有:

let mut foo = 10;foo += 10;

This feels roughly equivalent to let versus const in JavaScript. Bust Rust goes further.
这感觉大致相当于JavaScript中的 let 与 const 。Rust Rust走得更远。

For example, in JavaScript, certain variable types, such as arrays, are always mutable. Even if we instantiate them using const , we can push , pop and re-assign indexes. In Rust, we need mut to be able to do this:
例如,在JavaScript中,某些变量类型(如数组)始终是可变的。即使我们使用 const 实例化它们,我们也可以使用 push 、 pop 并重新分配索引。在Rust中,我们需要 mut 来做到这一点:

let mut nums: Vec<i32> = vec![1, 2, 3, 4, 5];nums.push(6);

Rust also allows us to move ownership of our values from one variable to another. Take the example below:
Rust还允许我们将值的所有权从一个变量转移到另一个变量。举个例子:

let nums: Vec<i32> = vec![1, 2, 3, 4, 5];let doubles: Vec<i32> = nums.into_iter().map(|n| n * 2).collect();dbg!(nums);     // this throwsdbg!(doubles);

This code throws, because the into_iter method creates a “consuming iterator”; in other words, takes ownership away from nums and gives it to doubles . We cannot, therefore, call dbg!(nums) after doubles has been created.
这段代码抛出,因为 into_iter 方法创建了一个“消费迭代器”;换句话说,从 nums 那里拿走所有权并将其交给 doubles 。因此,我们不能在创建了 doubles 之后调用 dbg!(nums) 。

If we want to maintain access to nums and instead clone its values, we can use the iter method instead of into_iter . What’s important is that Rust gives us a choice, and the ability to transfer ownership can help us be more efficient with memory allocation.
如果我们想保持对 nums 的访问,而不是克隆它的值,我们可以使用 iter 方法而不是 into_iter 。重要的是Rust给了我们一个选择,转移所有权的能力可以帮助我们更有效地分配内存。

We can also move simple values. In the code below, when our str variable is used as an argument for calculate_length , it is no longer accessible.
我们也可以移动简单的值。在下面的代码中,当我们的 str 变量被用作 calculate_length 的参数时,它不再是可访问的。

fn main() {    let str = String::from("Hello world!");    let len = calculate_length(str);    dbg!(str); // this throws}fn calculate_length(s: String) -> usize {    s.len()}

Here, we can fix this by using an ampersand & to pass a reference to our string, rather than passing the string itself. We’ll also need to update the function argument to expect a reference:
在这里,我们可以通过使用与号 & 来传递对字符串的引用,而不是传递字符串本身来解决这个问题。我们还需要更新函数参数以期望引用:

fn main() {    let str = String::from("hello");    let len = calculate_length(&str);    dbg!(str, len);}fn calculate_length(s: &String) -> usize {    s.len()}

To do the opposite, and de-reference a value, we can use an asterisk * . Together, these features help us control memory safely and efficiently, and because of this, Rust doesn’t need to depend on a garbage collector, allowing us to unlock a greater level of performance without the risks of languages like C, which places a greater onus on the developer to understand what they’re doing!
要做相反的事情,取消引用一个值,我们可以使用星号 * 。总之,这些功能帮助我们安全有效地控制内存,正因为如此,Rust不需要依赖垃圾收集器,允许我们解锁更高级别的性能,而没有像C这样的语言的风险,这给开发人员带来了更大的责任来理解他们在做什么!

​ If you’re new to Rust or curious about picking up the language — especially coming from a higher level language — then a hope you found this article useful.

So if you want to go further, head over to The Book and consider sharing your experience in the comments below! If you want to go further, please keep working hard​!

如果你是Rust的新手,或者对学习这门语言很好奇--特别是来自更高级别的语言--那么希望你发现这篇文章很有用。如果你想走得更远,请继续努力 !

阅读本书更多章节>>>>

本文链接:https://www.kjpai.cn/gushi/2024-04-22/161236.html,文章来源:网络cs,作者:焦糖,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。

文章评论