Rust 从 C++ 偷来的最佳特性

科技   2024-10-06 14:35   广东  

Rust 是一门现代系统编程语言,它以其安全性和性能而闻名。Rust 的设计理念是尽可能地安全,同时保持与 C++ 相似的性能。Rust 借鉴了 C++ 的许多优秀特性,并在此基础上进行了改进,使其更安全、更易用。

资源管理模型

C++ 引入了一种非常实用的设计模式,称为 RAII(资源获取即初始化)。RAII 的思想是将资源(如堆分配的内存、数据库连接和文件句柄)与对象的生存期绑定在一起。

当对象创建时,它应该获取它所需的资源,这在对象的构造函数中完成。当对象被销毁时,资源应该被释放,这由析构函数处理。这种设计模式可以确保资源管理的可靠性,并使代码更简洁。最重要的是,它降低了资源泄漏的风险,这就是为什么它在整个 C++ 标准库中被使用的原因。

例如,ofstream 在内部实现了 RAII,允许我们直接管理文件并自动清理堆分配的内存。我们也可以使用标准库中的unique_ptr 和第三方库来自动管理数据库连接。

RAII 非常有用,Rust 采用了这种设计模式,并将其作为语言的核心部分。Rust 标准库大量使用 RAII。例如,我们可以使用文件,句柄将自动释放。Box 智能指针用于自动清理堆内存,我们也可以使用第三方库来管理数据库连接。

然而,Rust 并没有止步于此。它在传统的 C++ RAII 模型上引入了两个重大改进。

首先,与 C++ 不同,Rust 阻止你意外地给自己添麻烦。在 C++ 中,你可以通过使用newdelete 手动分配内存来绕过 RAII。这会导致错误,例如在调用delete 后尝试访问指针时出现的“使用后释放”错误,如果意外地两次调用delete 则会出现“双重释放”错误,如果忘记调用delete 则会出现内存泄漏。

这些错误会损害软件的稳定性、安全性,并降低性能。微软曾报告称,其产品中 70% 的漏洞是由内存安全问题造成的。Rust 避免了这些问题,因为它没有像newdelete 这样的运算符。相反,它强制你使用 RAII 结构来分配资源。

Rust 对传统 RAII 模型的第二个重大改进是自动管理数据引用。当一个值被释放时,编译器会执行检查以确保对该值的引用是有效的。

通过执行编译时检查,编译器可以防止任何对已释放值的引用被再次使用。这意味着可以保证引用指向有效的内存,因此你无需担心诸如“使用后释放”错误和悬挂指针之类的臭名昭著的错误。

在 Rust 中,RAII 模型被称为所有权模型,与编译时引用验证检查一起,被称为借用模型。它们共同确保资源被自动清理,引用保持有效,从而消除了一整类内存安全错误。

零成本抽象

Rust 从 C++ 借鉴的第二个特性是零成本抽象原则。这意味着,与使用低级抽象(如循环和计数器)的代码相比,高级抽象(如泛型和集合)不应产生任何运行时开销或性能损失。这提供了良好的开发体验,同时保持了闪电般的速度。

让我们看看下面的 C++ 示例:

我们创建一个整数向量,并使用一个for 循环来查找向量中的最大值。这段代码涉及循环初始化、条件检查和元素比较,这使得它更难阅读,更容易出错。

现在,代码简单多了。我们只需要调用标准库中的max 函数。迭代器抽象封装了迭代和比较逻辑,使代码更简洁、更不容易出错,而且不会牺牲性能。

以下是我们在 Rust 中编写相同代码的方式:

首先,我们通过调用iter 方法将向量转换为迭代器,然后通过调用max 方法找到最大元素。在这个例子中,我们实际上使用了几个零成本抽象。第一个是迭代器抽象;第二个是Option 枚举;第三个是模式匹配。

Rust 的美妙之处在于它建立在零成本抽象原则的基础上,进一步强调了内存和线程安全性。如果我们回到 C++ 示例,在空向量上调用max 函数会导致未定义的行为,因为我们将返回对无法安全访问的对象的引用,但尝试在下一行引用它。

相反,在 Rust 示例中,max 方法返回一个Option 类型。因此,如果我们在空向量上调用它,它只会返回NoneOption 类型强制我们检查返回值是Some 还是None,这使得我们的代码更加健壮,同时保持了极快的性能。

泛型编程

Rust 从 C++ 借鉴的第三个特性是泛型编程。在 C++ 中,泛型编程是通过模板实现的。以下是一个示例:

模板允许你编写适用于任何数据类型的代码,提供了灵活性和可重用性。这个名为print 的模板函数接受一个泛型类型T 的参数,该参数受Printable 概念的约束。在这种情况下,为了使一个类型被认为是Printable,它必须能够输出到标准输出,并且该操作必须返回对ostream 的引用。通过使用这种概念作为泛型类型T 的约束,print 函数确保它只打印Printable 类型的实例。

受 C++ 模板的启发,Rust 采用了泛型的概念并对其进行了现代化。Rust 引入了两个关键改进:

首先,它简洁的语法使 Rust 泛型更直观,更容易阅读。

其次,Rust 将泛型与它的Trait 系统紧密地集成在一起。

以下是一个代码示例:

在这个例子中,我们明确声明泛型类型T 必须实现Display 特性。这类似于 C++ 中的概念,但它是 Rust 从一开始就不可或缺的一部分。由于 Rust 将泛型与特性紧密地集成在一起,如果你使用不符合所需约束的类型,你会收到清晰的错误消息。

Rust 的泛型方法也遵循我们之前讨论的零成本抽象原则——编译后的代码与为每个特定类型编写单独函数一样高效,这是 Rust 在编译时自动为你处理的。

此外,Rust 的所有权系统也适用于泛型代码,通过确保即使泛型函数也是内存安全和线程安全的,从而改进了 C++ 的模板系统。Rust 使泛型编程更易访问、更安全,同时保持了其强大的功能和性能优势。

结论

Rust 在改进 C++ 特性方面取得了重大进展,但它仍然面临重大限制。C++ 已经存在了几十年,拥有广泛的生态系统和广泛的行业采用。此外,C++ 与 C 几乎无缝兼容,这意味着你可以在几乎任何操作系统或硬件平台上运行你的应用程序。

作为一门较新的语言,Rust 仍在建立自己的生态系统,尽管它的行业采用率正在迅速增长。它仍处于早期阶段,虽然 Rust 与 C 的兼容性正在提高,但它不像 C++ 那样无缝。这种生态系统差距意味着,对于许多大型的现有项目或专门的行业,C++ 仍然是首选,尽管 Rust 具有安全优势。

为了真正与 C++ 竞争,Rust 需要继续扩展其生态系统,并提高与现有代码库的兼容性。尽管存在这些挑战,但 Rust 的未来看起来很光明。随着生态系统的发展和兼容性的提高,Rust 的采用只会继续增加。

文章精选

Tailspin:用 Rust 打造的炫彩日志查看器

Rust: 重塑系统编程的安全壁垒

Youki:用Rust编写的容器运行时,性能超越runc

使用C2Rust将C代码迁移到Rust

Rust语言中如何优雅地应对错误

Rust编程笔记
与你一起在Rust的世界里探索、学习、成长!
 最新文章