交叉编译和静态链接 Rust 库

Photo by Zun Zun from Pexels

在 CSIS 中,我们过去使用 Python 编写后端,同时为 Incident Response Toolkit 编写一些 C/C++ 代码。

几年前,主要是由于性能原因,我们开始用 Rust 替换 Python 重写了一些特定的后端服务,并取得了巨大的成功。现在,为了便于开发和测试,我们正在探索将 C/C++ 代码库的某些部分也迁移到 Rust 的方法。

为了做到这一点,我们决定尝试将Rust集成到现有的代码库中,而不是一次重写所有内容。

下面是我们实验的摘要,以及编写一个Rust库并从 C/C++ 应用程序调用它的框架。

继续阅读

微软:为啥安全的系统得用 Rust 写

图片来自 Microsoft Security Response Center

本系列文章中,我们探讨了主动采取一些措施消除一类漏洞的必要性,并介绍了在 Microsoft 的代码中发现的一些内存安全问题的例子,这些问题可以用其他语言避免。现在我们来看看为什么我们认为 Rust 是目前可用的 C 和 C++ 最好的替代品。

虽然,已经有许多非常好用的内存安全语言,广泛应用于微软内外,包括 .NET 语言(如 C# 和 F#)和其他语言(如 Swift, Go, and Python)。 我们鼓励正在使用 C 或 C++ 的人考虑使用这些语言中的一种。然而,我们正在讨论对安全的系统编程语言的需求(即,可以构建其他软件运行的系统的语言,如OS内核)。此类工作负载需要 C,C ++ 和 Rust 提供的速度和可预测的性能。通过垃圾回收实现内存安全的语言不是系统编程的理想选择,因为它们的运行时会导致不可预测的性能和不必要的开销。

性能和控制

在考虑为什么Rust是一个很好的替代方案时,最好考虑一下我们不能因为从 C 或 C++ 转换而放弃什么——即性能和控制。 Rust,就像 C 和 C++ 一样,有一个最小的和可选的“运行时”。Rust 的标准库依赖于 libc 来支持它的平台,就像 C 和 C++ 那样,但是标准库也是可选的,所以在没有操作系统的平台上也是可以运行的。

Rust 与 C 和 C++ 一样,也为程序员提供了对何时分配内存以及分配多少内存的细粒度控制,从而使程序员能够非常清楚地了解程序运行时将如何执行。这对于原始性能,控制和可预测性的性能意味着什么,Rust,C 和 C ++可以用类似的术语来思考。

继续阅读

在 Rust 中不能做什么

Photo by Elle Hughes from Pexels

编者注:上周 Armin 在自己的博客上首次发布了这个版本。如果你想再次阅读这篇文章,或者想看看 Armin 还在做什么,一定要去看看。

去年一直很有趣,因为我们用 Rust 建造了很多好东西,并且这是第一次在开发体验上没有更大的障碍。虽然过去的一年我们一直使用 Rust,但是现在感觉不同了,因为生态系统更加稳定,我们遇到的语言或者工具的问题也越来越少。

也就是说,与那些不熟悉 Rust 的人交谈——甚至与同事集体讨论 API——很难摆脱Rust可以成为令人心旷神怡的冒险的感觉。这就是为什么拥有无压力体验的最佳方式是,提前了解你不能(或不应该尝试)做的事情。 知道某些事情无法完成有助于让你的思想回到正确的轨道上。

所以根据我们的经验,这里有一些在 Rust 中不能做的事情,以及应该做什么,我认为应该更好地了解。

继续阅读

从 Rust 库中公开 FFI

Photo by Dominika Roseclay from Pexels

Wikipedia 将 FFI 定义为一种机制,通过这种机制,用一种编程语言编写的程序可以调用或使用用另一种编程语言编写的服务。

FFI 可用于加快程序执行(这在 Python 或 Ruby 这类动态语言中很常见),或者只是因为你想使用一些其他语言编写的库(例如 TensorFlow 的核心库是用 C++ 写的,并暴露了 C API,允许其他语言使用)。

为 Rust 库编写 FFI 并不难,但是却有一些挑战和可怕的部分,主要是你要使用指针和 unsafe1。这可能会脱离 Rust 的内存安全模型,换句话说,编译器无法检查一切是否正常,因此内存管理和安全保障取决于开发人员。

在这篇文章中,我将讲述我对 Rust 和 FFI 的经验,基于 battery-ffi ,它将 FFI 暴露给我的另一个 crate — battery。我想做的是提供一个 C 接口来创建特定于 Rust 的结构,并能够从它们获取数据。

继续阅读

Rust 零成本的抽象

图片来自 pexels.com

零成本抽象的概念对于某些编程语言非常重要,比如 Rust 和 C++,这些语言的目的是使用户能够用相对较少的努力编写具有出色性能的程序。因为这个概念是 Rust 设计和我的工作的基础,所以我想研究一下,零成本抽象到底是什么。

C++ 的最初开发者Bjarne Stroustrup在其原文中总结了这个想法:

你不用的东西,就不用付钱。而且: 你所使用的代码是最好的。

在这个定义中,有两个因素使某些东西成为一个适当的零成本抽象:

  • 没有全局开销: 零成本抽象不应该对不使用它的程序的性能产生负面影响。例如,它不能要求每个程序都带有一个沉重的语言运行时,以使唯一使用该特性的程序受益。
  • 最优性能: 一个零成本的抽象应该被编译成最佳的解决方案实现,而这个解决方案是有人用较低级别的原语编写的。它不能引入没有抽象就可以避免的额外成本。

然而,我认为重要的是要记住,第三个要求是什么东西是零成本抽象。它经常被忽视,因为它只是所有好的抽象的一个要求,无论是否为零:

  • 改进用户体验: 抽象的要点是提供一个新工具,它由较低级别的组件组装而成,使用户能够更容易地编写他们想要编写的程序。与所有抽象一样,零成本抽象实际上必须提供比其他抽象更好的体验。
继续阅读

在 Rust 中创建 C/C++ API

图片来自pexels.com

Rust 是一种神奇的语言,有着更好的生态系统。许多 Rust 的设计决策都非常适合向现有的C/C++系统添加新功能,或者逐步替换这些系统的部分!

当我尝试为 Rust 创建 C++ API 时,我发现,从 C/C++ 到 Rust 的绑定被更好地记录下来,并且比从 Rust 到 C/C++ 的结合有更平滑的体验。

正如我所发现的,事实并非如此!有一些很棒的工具可以帮助你创建C/C++ API。 这篇文章介绍了我在使用这些工具方面的一点经验,希望能帮助有同样追求的人 🙂

继续阅读

在 Rust 中调用 Java 代码

图片来自pexels.com

j4rs 代表 “Java for Rust” ,可以在 Rust 中轻松调用 Java 代码。


不久前,我需要在 Rust 中调用 Java 代码,就启动了 j4rs 这个项目。 其主要思想是实现一个 crate,让用户能够轻松调用 Java,这样他们就可以从庞大的 Java 生态系统中受益。

我说的“容易”,是指:

  • 注意 JNI 所以需要的配置(例如 jvm 包含/链接本地共享库)。
  • 创建一个直观、简单的 API 进行 Java 调用(Rust -> Java 方向)。
  • 允许 Java -> Rust 回调。
  • 无缝在在 Linux 或者 Windows 上使用 crate(当然,前提是安装了 Java)。
  • 遵循 “Rust-first” 方式: Rust 代码可以创建和管理 JVM,而不是反过来。

在此过程中,我还发现一些 other crates 可以调用 Java 代码,但是似乎可以尽量减少程序员对 “JNI 细节” 的干预。

我们中的许多人,Rust 程序员们, 都知道如何编写 Java,但是可能没有多少人知道(或者愿意去处理)JNI的特性和陷阱。

和往常一样,安慰伴随着一些牺牲。隐藏 JNI 细节意味着使用反射和对象序列化。这带来了一些性能损失。

j4rs 是为我们这些愿意付出这个代价的人准备的。

继续阅读

Rust 标准库学习:LinkedList

图片来自pexels.com

继续 Rust 标准库 学习系列, 这次是 LinkedList<T>。注意,实现来自 Rust stable v1.33.0.

拥有节点的双向列表。
LinkedList 永许在常量时间内从两端 pushpop 元素。
大多数情况最好用 VecVecDeque 替代 LinkedList。通常,基于数组的容器更快,内存效率更高,并且更好的利用 CPU 缓存。

Rust std doc

Vec<T> 不同的是

  1. 通过索引访问一个元素需要对列表进行线性迭代。
  2. append is O(1)。
  3. 有趣的是 linked_list::Iterlinked_list::IterMut 的不同之处。IterMut的不可变性用 &mutPhantomData<&'a Node<T>> 确保健全性 (更多关于 PhantomData,稍后说明)。

这儿有 一本书 (如果你没读过这本书,我强烈建议你去阅读它的细节) 向读者解释了为什么单链表难以实现,而且对于 Rust 新用户来说不是个好主意。

由于 Rust 的类型系统/所有权,实际上很难实现双向列表。 主要原因是一个节点似乎需要来自相邻节点的两个所有者。然而, 不过,这对于 NonNull<T> 是可能的,我们在 Vec<T> 学习 中讨论过。

继续阅读

Rust 标准库学习:VecDeque

继续 Rust 标准库学习系列, 这次是 VecDeque<T>。 由于它与 Vec相似,没有太多说的。

双端队列使用可扩张的 环形缓冲器 实现。这种类型的默认使用方法是,用 push_back 将元素添加到队列, 用 pop_front 从队列中移除元素。 extendappend 和以这种方式将元素追加到后面,并在VecDeque 上迭代。

Rust std doc
  • Vec 类似, VecDeque 对容器的两端进行 amortized O(1) 插入, 与Vec 不同的是, 它对容器的两端进行 amortized O(1) 移除。 (从 Vec 学习中回顾,删除严格为 O(1),不涉及回收因子)
  • Vec 类似,索引是 O(1)

Vec<T> study 类似,这里是精简的 VecDeque<T> 定义:

继续阅读

Rust 标准库学习:Vec

图片来自pexels.com

接下来的系列博客文章将包含我对Rust 标准库的研究。我已经在分散的地方编写了本系列的部分内容,希望将它们集中在一个地方,以便更好、更容易地访问。每当我发现一些有趣/重要的事情需要记住时,我就会更新。

我指的是Rust stable v1.33.0中的实现。

这篇文章涵盖了我认为重要的std::vec的一些细节。

Vec<T>

Vec<T> 是一个动态数组,它只会自动增长而不会自动收缩。

具有堆分配内容的连续可增长数组类型。

Rust std doc

注意其对应的堆栈分配的固定大小数组[T; N]之间的差异 (此时N需要是一个指定的非负整数。常量泛型有望很快出现)。

好的!让我们开始吧。Vec<T> 包含(指针容量长度)。

继续阅读