发布于 2025-12-08 2 阅读
0

我对 Rust 的第一印象 我喜欢 Rust 的哪些方面 我不喜欢 Rust 的哪些方面 挑剔之处 结论

我对 Rust 的第一印象

我喜欢 Rust 的哪些方面

我不喜欢 Rust 的地方

挑剔

结论

最初发表于deepu.tech。

所以我前一段时间开始学习 Rust,而且由于我关于我对 Go 的看法的帖子很受欢迎,所以我决定写下我对 Rust 的第一印象。

但与 Go 不同的是,我实际上并没有用 Rust 构建过任何实际应用,所以这里我的观点纯属个人观点,有些可能并不准确,因为我可能误解了某些内容。所以,请给予我一个 Rust 新手的尊重。如果您发现我在这里说的某些内容不准确,请告诉我。另外,一旦我开始更多地使用这门语言,我的印象可能会有所改变。如果真的改变了,我一定会更新这篇文章。

正如我在其他一些帖子中所说,我认为自己现在比年轻时更加务实。一些观点也源于务实的视角(至少我是这样认为的)。
我权衡了实用性、可读性和简洁性,而不是花哨的功能、语法糖和复杂性。此外,一些我不喜欢但觉得没什么大不了的事情,我会放在“挑剔”部分,而不是“不喜欢”部分,因为我认为这样更公平。

Rust 与 Go 等语言的一个不同之处在于,Rust 不进行垃圾收集,而且我知道许多语言特性/选择在设计时都考虑到了这一点。

Rust 主要面向过程式/命令式编程风格,但它也允许你进行一些函数式和面向对象编程风格。而这正是我最喜欢的混合风格。

那么,事不宜迟,让我们开始吧。


我喜欢 Rust 的哪些方面

我真正喜欢的东西,没有特别的顺序。

无垃圾收集

Rust 中你首先会注意到的一个特性,尤其是如果你之前使用过 Java 或 Golang 等支持垃圾回收机制的语言,那就是它缺乏垃圾回收机制。没错,Rust 中确实没有 GC,那么它是如何确保我的程序在给定的内存中高效运行,又是如何避免内存不足错误的呢?

Rust 中有一个叫做所有权 的东西,所以基本上 Rust 中的任何值都必须有一个变量作为其所有者(并且一次只能有一个所有者)。当所有者超出范围时,无论该值位于栈内存还是堆内存中,它都会被丢弃并释放内存。例如,在下面的示例中, 的值foo在方法执行完成后立即被丢弃,而 的值bar在代码块执行后立即被丢弃。

fn main() {
    let foo = "value"; // owner is foo and is valid within this method

    {
        let bar = "bar value"; // owner is bar and is valid within this block scope
        println!("value of bar is {}", bar); // bar is valid here
    }

    println!("value of foo is {}", foo); // foo is valid here
    println!("value of bar is {}", bar); // bar is not valid here as its out of scope
}
Enter fullscreen mode Exit fullscreen mode

因此,通过仔细地限定变量的作用域,我们可以确保内存使用得到优化,这也是为什么 Rust 允许您几乎在任何地方使用块作用域的原因。

此外,Rust 编译器还能帮你处理重复的指针引用等等。以下代码在 Rust 中无效,因为 Rustfoo现在使用的是堆内存而不是栈,并且将引用赋给变量会被视为移动。如果需要深度复制(开销较大),则必须使用clone执行复制而非移动的函数来执行。


fn main() {
    let foo = String::from("hello"); // owner is foo and is valid within this method

    {
        let bar = foo; // owner is bar and is valid within this block scope, foo in invalidated now
        println!("value of bar is {}", bar); // bar is valid here
    }

    println!("value of foo is {}", foo); // foo is invalid here as it has moved
}
Enter fullscreen mode Exit fullscreen mode

所有权概念可能有点难以习惯,特别是因为将变量传递给方法也会移动它(如果它不是文字或引用),但考虑到它可以使我们免于 GC,我认为它是值得的,并且编译器会在我们犯错误时帮助我们。

默认不可变

变量默认是不可变的。如果要修改变量,必须使用mut关键字 进行特殊标记。

let foo = "hello" // immutable
let mut bar = "hello" // mutable
Enter fullscreen mode Exit fullscreen mode

变量默认按值传递,如果要传递引用,则必须使用&符号。与 Golang 非常相似。

fn main() {
    let world = String::from("world");

    hello_ref(&world); // pass by reference. Keeps ownership
    // prints: Hello world
    hello_val(world); // pass by value and hence transfer ownership
    // prints: Hello world
}

fn hello_val(msg: String) {
    println!("Hello {}", msg);
}

fn hello_ref(msg: &String) {
    println!("Hello {}", msg);
}
Enter fullscreen mode Exit fullscreen mode

当你传递一个引用时,它仍然是不可变的,所以我们必须像下面一样显式地将其标记为可变的。这使得意外的改变变得非常困难。编译器还确保在一个作用域中只能有一个可变引用。

fn main() {
    let mut world = String::from("world");

    hello_ref(&mut world); // pass by mutable reference. Keeps ownership
    // prints: Hello world!
    hello_val(world); // pass by value and hence transfer ownership
    // prints: Hello world!
}

fn hello_val(msg: String) {
    println!("Hello {}", msg);
}

fn hello_ref(msg: &mut String) {
    msg.push_str("!"); // mutate string
    println!("Hello {}", msg);
}
Enter fullscreen mode Exit fullscreen mode

模式匹配

Rust 对模式匹配具有一流的支持,这可用于控制流、错误处理、变量赋值等。模式匹配也可以用于语句、if循环和函数参数。whilefor

fn main() {
    let foo = String::from("200");

    let num: u32 = match foo.parse() {
        Ok(num) => num,
        Err(_) => {
            panic!("Cannot parse!");
        }
    };

    match num {
        200 => println!("two hundred"),
        _ => (),
    }
}
Enter fullscreen mode Exit fullscreen mode

泛型

我喜欢 Java 和 TypeScript 的一点是泛型。它使静态类型更加实用,并且更符合 DRY 原则。没有泛型的强类型语言(比如 Golang)用起来很烦人。幸运的是,Rust 对泛型提供了强大的支持。它可以用于类型、函数、结构体和枚举。更棒的是,Rust 在编译时会将泛型代码转换为特定代码,因此使用它们不会有任何性能损失。

struct Point<T> {
    x: T,
    y: T,
}

fn hello<T>(val: T) -> T {
    return val;
}

fn main() {
    let foo = hello(5);
    let foo = hello("5");

    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

Enter fullscreen mode Exit fullscreen mode

静态类型和高级类型声明

Rust 是一门严格类型语言,拥有静态类型系统。它还拥有强大的类型推断功能,这意味着我们无需手动定义所有类型。Rust 还允许复杂的类型定义。

type Kilometers = i32;

type Thunk = Box<dyn Fn() + Send + 'static>;

type Result<T> = std::result::Result<T, std::io::Error>;
Enter fullscreen mode Exit fullscreen mode

简洁明了的错误处理

Rust 的错误处理非常出色,有可恢复的错误和不可恢复的错误。对于可恢复的错误,你可以使用枚举的模式匹配Result或简单的 expect 语法来处理。Rust 甚至提供了一个简写运算符来从函数中传递错误。不妨试试 Go。

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");

    // or

    let f = match File::open("hello.txt") {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error)
        },
    };
}
Enter fullscreen mode Exit fullscreen mode

元组

Rust 内置了对元组的支持,当您必须从函数返回多个值或想要解开一个值等时,这非常有用。

块表达式

在 Rust 中,几乎可以在任何地方使用具有独立作用域的块表达式。它还允许在块表达式、if 语句、循环等中为变量赋值。

fn main() {
    let foo = {
        println!("Assigning foo");
        5
    };

    let bar = if foo > 5 { 6 } else { 10 };
}
Enter fullscreen mode Exit fullscreen mode

漂亮的编译器输出

Rust 的编译错误输出是我见过最好的。我觉得没有比这更好的了。它真的太有用了。

替代文本

内置工具

与许多现代编程语言一样,Rust 也提供了许多内置的标准工具,说实话,我认为这是我遇到过的最好的工具之一。Rust 内置了Cargo包管理器和构建系统。它是一款非常优秀的工具,能够处理所有常见的项目需求,例如编译、构建、测试等等。它甚至可以创建一个带有框架的新项目,并在全局和本地管理项目的包。这意味着你无需担心设置任何工具即可开始使用 Rust。我非常喜欢这个工具,它节省了大量的时间和精力。每种编程语言都应该拥有它。

Rust 还提供内置实用程序和断言来编写测试,然后可以使用 Cargo 执行。

在 Rust 中,相关功能被分组到模块中,模块被分组到 crate 中,crate 又被分组到包中。我们可以在一个模块中引用另一个模块中定义的项目。包由 cargo 管理。您可以在Cargo.toml文件中指定外部包。可重用的公共包可以发布到crates.io注册表中。

甚至还有内置的离线文档,运行一下就能查看rustup docs,rustup docs --book真是太棒了。感谢 Mohamed ELIDRISSI指出这一点。

并发

Rust 对内存安全的并发编程提供了一流的支持。Rust 使用线程进行并发,并具有 1:1 的线程实现,即每个操作系统线程对应一个绿色线程。Rust 编译器在使用线程时保证内存安全。它提供诸如等待所有线程完成、使用move闭包或通道共享数据(类似于 Go)等功能。它还允许您使用共享状态和同步线程。

use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}
Enter fullscreen mode Exit fullscreen mode

宏和元编程

虽然我并非完全喜欢 Rust 中宏的方方面面,但这里的优点还是多于缺点。例如,注释宏就非常方便。但我不太喜欢过程宏。对于高级用户,你可以编写自己的宏规则并进行元编程。

特质

Traits 与 Java 中的接口同义,用于定义可在结构体上实现的共享行为。Traits 甚至可以指定默认方法。我唯一不喜欢的是它的间接实现。

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct NewsArticle {
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize_author(&self) -> String {
        format!("@{}", self.author)
    }
}

fn main() {
    let article = NewsArticle {
        author: String::from("Iceburgh"),
        content: String::from(
            "The Pittsburgh Penguins once again are the best hockey team in the NHL.",
        ),
    };

    println!("New article available! {}", article.summarize());
}

Enter fullscreen mode Exit fullscreen mode

必要时可以使用不安全的功能

  • 在你知道自己在做什么的高级用例中很有用。在我看来,这是必要之恶。我喜欢它,因为它只能在一个unsafe { }块内执行,非常明确。如果不是这样,我会把它移到不喜欢的部分。
  • 当你使用这些功能时,Rust 编译器无法保证内存和运行时的安全,你需要自行解决。因此,对于高级和经验丰富的用户来说,这绝对适用。

我不喜欢 Rust 的地方

我不太喜欢的东西,没有特别的顺序。

复杂

我不喜欢一门语言提供多种方法来实现相同的功能。这一点我认为 Golang 做得很好,它不会出现两种方法来实现相同的功能,因此人们在处理更大的代码库和代码审查时会更加轻松。而且,你不必总是思考最佳的实现方式。不幸的是,Rust 就是这样的,我并不喜欢它。在我看来,这会让语言变得更加复杂。

  • 迭代方式太多 -> 循环、while、for、迭代器。
  • 创建过程的方法太多了 -> 函数、闭包、宏

编辑:根据这里和Reddit上的讨论,我觉得我对复杂性的感知只会增加。感觉一旦你理解了所有细节,很多事情似乎需要花些时间才能理解。我敢肯定,如果你有 Rust 经验,这对你来说应该轻而易举,但这门语言确实相当复杂,尤其是函数和闭包在不同上下文中的行为方式,以及结构体中的生命周期等等。

同一上下文中的变量的阴影

Rust 可以让你做到这一点

{
    let foo = "hello";
    println!("{}", foo);
    let foo = "world";
    println!("{}", foo);
}
Enter fullscreen mode Exit fullscreen mode
  • 默认情况下是不可变的(我理解能够对不可变变量执行转换的理由,特别是在将引用传递给函数并取回它时)
  • 在我看来,这会让人们无意中养成不良习惯,我宁愿将变量标记为可变,因为我也考虑了上述变异。
  • 在 JavaScript 等语言中,你可能会意外地隐藏一个变量,就像你意外地改变一个变量一样
  • 给人们一把枪来射脚

补充:我在这里和Reddit上看到很多评论解释了为什么这个功能很好。虽然我同意它在很多场景下都很有用,但修改功能也同样有用。我认为如果没有这个功能也完全没问题,人们仍然会喜欢 Rust,而且所有人都会支持没有这个功能的决定。所以我对此的看法没有改变。

函数不是一等公民

虽然可以将一个函数传递给另一个函数,但它们并不像 JavaScript 或 Golang 那样是一等公民。 您不能从函数创建闭包,也不能将函数分配给变量闭包在 Rust 中与函数是分开的,在我看来,它们与 Java 的 lambda 表达式非常相似。虽然闭包足以执行一些函数式编程模式,但如果能只使用函数来实现,让语言更简单一些,那就更好了。

编辑:天哪!这个观点在这里和Reddit上引发了很多讨论。所以看起来函数和闭包在上下文上既相似又不同,函数似乎也几乎像是一等公民,但如果你习惯于 Go 或 JavaScript 等函数更直接的语言,那么你将会陷入疯狂。Rust 中的函数似乎要复杂得多。许多评论的人似乎忽略了一个事实,即我的主要抱怨是,在大多数情况下,两个看起来和行为非常相似的构造(闭包和函数)会使事情变得更加复杂。至少在 Java 和 JS 中,有多个构造(箭头函数、lambda),这是因为它们是在很晚的时候添加到语言中的,而这些仍然是我在这些语言中不喜欢的东西。最好的解释来自 Yufan Lou 和 zesterer 。我不会将它从我不喜欢的东西中删除,因为我仍然不喜欢这里的复杂性。

特征的隐式实现

我不喜欢隐式实现,因为它更容易被滥用,而且更难理解。你可以在一个文件中定义一个结构体,然后在另一个文件中为该结构体实现一个 trait,这样会让它不那么明显。我更喜欢像 Java 那样,通过意图来实现,这样会更明显,更容易理解。虽然 Rust 的方式并不理想,但绝对比 Golang 更好,Golang 更加间接。


挑剔

最后,有些东西我仍然不喜欢,但我不认为它们是什么大问题。

  • 我不明白为什么默认使用constwhen 这个关键字let是不可变的。它看起来更像是老式常量概念的语法糖。黛安 指出了差异的const存在,这是有道理的。
  • 块表达式和隐式返回样式有点容易出错,而且难以习惯,我更喜欢显式返回。而且,在我看来,它的可读性不太好。
  • 如果你读过我之前关于 Go 的文章,你可能知道我不太喜欢结构体。Rust 中的结构体与 Golang 中的结构体非常相似。所以就像在 Go 中一样,很容易实现难以理解的结构体。但幸运的是,Rust 中的结构体看起来比 Go 好得多,因为它有函数,可以使用展开运算符、简写语法等等。此外,你可以创建元组形式的结构体。Rust 中的结构体更像 Java 的 POJO。如果在结构体中添加可选字段更容易实现,我会把这个移到“喜欢”部分。目前,你必须将内容包装在枚举中Optional才能做到这一点。还有生命周期 :(
  • 鉴于字符串是最常用的数据类型,如果能有一种更简单的方法来处理字符串(就像在 Golang 中一样),而不是使用 Vector 类型来处理可变字符串或使用切片类型来处理不可变字符串字面量,那就太好了。这会让代码变得比实际需要的更冗长。这更像是一种妥协,因为 Rust 没有垃圾回收机制,并且使用所有权的概念来管理内存。https ://doc.rust-lang.org/rust-by-example/std/str.html -编辑:在与robertorojasr讨论评论后,我已将这一点从“不喜欢”改为“挑剔”。

结论

我喜欢那些更注重简洁性,而不是花哨的语法糖和复杂功能的编程语言。在我的文章“我对 Golang 的思考”中,我解释了为什么我认为 Golang 对我来说太简单了。另一方面,Rust 则倾向于另一个极端。它虽然不像 Scala 那么复杂,但也不像 Go 那么简单。所以它介于两者之间,虽然不是最佳平衡点,但几乎接近最佳平衡点,可能与 JavaScript 的水平相当接近。

所以总的来说,Rust 中我喜欢的地方比不喜欢的地方多,这正是我对一门优秀编程语言的期望。另外,请记住,我并不是说 Rust 应该做任何不同的事情,或者有更好的方法来实现我所抱怨的事情。我只是说我不喜欢那些地方,但考虑到它的整体优势,我可以接受。此外,我完全理解为什么其中一些概念是这样的,这些主要是为了关注内存安全和性能而做出的权衡。

但是不要被你看到的东西所迷惑,在我看来,Rust 绝对不是你应该作为你的第一个编程语言开始的东西,因为它底层有很多复杂的概念和结构,但如果你已经熟悉编程,那么在你撞到门几次之后它应该不是问题:P

到目前为止,我可以说,即使没有用 Rust 实现真正的项目,我也更喜欢 Rust 而不是 Golang,并且对于系统编程用例和高性能要求,我可能会选择 Rust 而不是 Go。

注:在使用 Rust 之后,我的一些观点发生了改变。可以看看我关于此主题的新帖子。


如果您喜欢这篇文章,请点赞或留言。

您可以在Twitter和LinkedIn上关注我。

封面图片来源:图片来自Link Clark、Rust 团队,遵循Creative Commons Attribution Share-Alike License v3.0 许可。

文章来源:https://dev.to/deepu105/my-first-impressions-of-rust-1a8o