如果您喜欢本文,请多多点赞转发,以及请我喝杯咖啡,这样可以帮助我加强写作动力。感谢您!
Reddit 统计了九月 Top 15 的话题。本文是我对这十五篇帖子的内容的延伸性思考笔记。
????
操作符 The legendary ???? operator[4] 🔥 392众所周知,JetBrains 推出了 Rust 语言商业独立 IDE RustRover。这件事对 Rust 语言来说,算是一个里程碑。独立的商业 IDE 意味着 Rust 语言迈入了主流语言行列。这说明 JetBrains 应该有足够的理由和数据让他们相信 Rust 已经足够受欢迎,值得有一个全职开发团队支持一个独立的商业 IDE 开发。
“人们通常会问应该学习哪种语言。我总是会提出一个论点:“看看JetBrains是否为该语言提供了专用的IDE”。我认为JetBrains在预期语言的市场份额会增长时,会创建IDE而不是插件。最近的例子是Goland,自那以后,Go语言变得更加成熟,JetBrains的判断是正确的。总之,现在是学习Rust的时候了!
对于 Rust 语言来说,这是好事。但是,这件事在社区里也出现了一些反对的声音。因为 JetBrains 同时宣布将停止免费的插件版本的更新和维护。这让社区很多人认为这是从开源走向闭源。
VSCode 是开源的,JetBrains 是闭源的,但是两者的商业模式是完全不同的。VSCode 开源旨在向开发者推广 Azure 。而 JetBrains 是直接以软件交换金钱为业务的传统模式。
JetBrains 提供付费服务,意味着他们必须使其优于免费的替代品。付费本身是一种强大的激励机制,可以改进技术水平,从而为 Rust 开发者带来好处。好的工具可以极大地提升工作效率。
评论里还有人提到了 JetBrains 的俄罗斯“基因”。然而,JetBrains 成功地将大部分员工从俄罗斯搬迁出来。由于个人原因无法搬迁的人,不得不与他们分道扬镳。可以想象,搬迁800多人及其家人(还有宠物)是一项艰巨的任务。幸运的是,JetBrains 能够将人员分配到他们在欧洲的办公室,包括在阿姆斯特丹、慕尼黑和柏林的最大研发中心。还在塞浦路斯、塞尔维亚和亚美尼亚开设了新的办事处。从而免受其员工受战争影响。
目前 RustRover 还没有让很多 Rust 开发者满意,但既然 JetBrains 已经开了这个头,我想它应该会越来越好。
微软将Azure Quantum Development Kit(QDK)改用Rust进行重写,现在它的速度提升了100倍,体积缩小了100倍,并且可以在浏览器中运行(基于 WebAssembly)!
“Quantum Development Kit (QDK) 是与 Azure Quantum 服务进行交互所需的 SDK。它内置于 Azure Quantum 门户,可在其中使用免费托管的 Jupyter Notebook 开发程序。还可以在计算机上本地安装 QDK,以使用自己的本地开发环境,并使用 Azure Quantum 服务联机和脱机工作。使用 , Quantum Development Kit可以生成在 Azure Quantum[16] 中的量子硬件上运行的程序。
QDK 包括量子编程语言 Q#,这是一种高级 开源 编程语言,可用于将工作重点放在算法和应用程序级别以创建量子程序。
QDK 支持 Q#、 Qiskit[17] 和 Cirq[18] 进行量子计算,因此,如果你已经在使用其他开发语言,还可以在 Azure Quantum 上运行线路。
对于这三种语言,QDK 中尽量减少依赖关系,几乎所有的依赖关系都可以在存储库根目录下的Cargo.toml和package.json文件中找到。
那么,Azure 量子开发包可以用来构建什么样的应用程序?能够破解RSA类型的算法吗?
没有,即使是顶级的量子计算机也没有类似的东西,更不用说免费的了。通常它们用于学生研究量子计算。算法开发和测试,模拟等。
“所以,各位的钱包还是安全的
最近关于异步的讨论出现了一些反对的声音:
我理解,这是刚开始使用 Rust 的新人对 异步 Rust 的一种吐槽,但没吐槽到点子上。被社区的人教育之后,知道自己错在哪里了。
async/await
只是语法糖,语法糖之下隐藏了 异步 Rust 的复杂性。这也许是那位新人面对 Async Rust 的困惑之处。因为想完全熟练掌握 异步 Rust ,就必须了解这语法糖背后的复杂性。
如果想就此指责 异步 Rust 的设计,那就大可不必。因为目前 Async Rust 的设计已经是这么多年讨论和实践验证后的最佳设计了。改进空间肯定很大,但是除非你能给出更好的设计并且容易落地,否则就免开尊口吧。
异步 Rust 的设计牺牲了抗阻塞能力,以换取可嵌入性(可在微控制器上运行,可作为库从任何语言调用)和非常高的性能。而 Go / Erlang 这种含有抢占式调度器的绿色线程语言可以防止阻塞,但会增加 CPU 开销。这种设计也造成了异步 Rust 应用的难点所在,因为在开发时你需要防止阻塞。
当前 Rust 的异步设计,也造成了另一个生态问题,就是 异步运行时的耦合。当前 tokio 已经成为大部分场景中异步运行时的首选,所以生态中很多相关的库都是基于 tokio 来实现,而无法与其他运行时兼容。
async_std
是另外一个异步运行时。它的想法是完全复制一个异步的标准库,但目前它受到了一些阻碍。完全复制标准库的异步版本可能不太现实。但目前也有很多第三方库依赖着它。
目前也有一些吐槽 tokio 的声音。
“比如:Rust异步编程的原罪是默认将其变为多线程。如果过早优化是万恶之源,那么这就是所有过早优化的母亲,它会给你的代码带来不祥的
Send + 'static
,甚至更糟的是Send + Sync + 'static
,这将完全摧毁你写Rust的乐趣。— Maciej Hirsz[20]
吐槽背后的原因是 tokio 默认给使用者强加了一些设计,比如推荐使用多线程,使用 Arc
或 Mutex
,这样会导致与编写异步代码无关的意外复杂性。
整个异步生态似乎已经被绑死在 tokio 上面,当然,异步生态还在不断前行与探索。还有其他异步运行时也值得关注:
“一个忠告:新手应该谨慎使用异步。如果你不需要异步来提高性能,线程通常是更简单的选择。--- the Async Book[25]
无论如何,Rust 官方团队还在推动着 异步 Rust 迈向真正的零成本抽象。最近的稳定 PR 正在将 async fn
和 返回位置的 impl Trait
推向稳定。
// async fn in trait (AFIT)
trait Bar {
async fn bar(self);
}
impl Bar for () {
async fn bar(self) {}
}
// return-position impl Trait in trait (RPITIT)
trait Bar {
fn bar(self) -> impl Send;
}
impl Bar for () {
fn bar(self) -> impl Send {}
}
// async fn 魔法:
async fn foo() -> i32 { 100 }
// Equivalent to:
fn foo() -> impl Future<Output = i32> { async { 100 } }
async fn
稳定之后,现在异步代码中经常见的 #[async_trait]
就可以移除了。
????
操作符在 https://github.com/nix-rust/nix/blob/master/src/mount/linux.rs#L127[26] 有下面这段代码:
let res = with_opt_nix_path(source, |s| {
target.with_nix_path(|t| {
with_opt_nix_path(fstype, |ty| {
with_opt_nix_path(data, |d| unsafe {
libc::mount(
s,
t.as_ptr(),
ty,
flags.bits(),
d as *const libc::c_void,
)
})
})
})
})????;
代码中因为 Rust 错误处理,出现了四个并排的 ?
操作符,整个代码看上去非常难以理解。这应该是为了解开 Result<Result<Result<Result<T, _>, _>, _>, _>
这样的嵌套 Result 类型。
Reddit 社区的人甚至提出 Rust 可以引入 ?+
或 ?*
或 ?{n,m}
或 ?^
和 ?$
这样的操作符来解决此类问题。。。
也许我们真的需要 try { ... }
。
“是否一场清除 Go 代码运动似乎在悄悄打响?
前几天看到 envoy proxy 作者也是 bitdrift.io 的 CTO Matt 说从他们的技术栈里清除了最后一点 Go 代码。
今天看到 InfluxDB 已经删除了所有的Go代码,现在99.5%是 Rust!为此,InfluxDB的联合创始人兼首席技术官就此事现身说法:
评论中另外一家公司也正在从 Go 迁移到 Rust 。
一位 Reddit 用户,他在几年前尝试 Rust ,对 Rust 的感觉是:“什么狗屎玩意?”,然后就不再碰 Rust 了。
在过去的一年左右,他一直在深入学习C++、学习C语言、学习Crystal Lang(它的语法很像Ruby)。他开始对真正的计算机科学产生了浓厚的兴趣。在过去的一周里,他一直在尝试Rust,...
然后他说,他爱上了这门语言。
我想说,谁不是呢?评论区的一些回复:
你呢?
“一旦你开始使用静态类型,就再也回不去了。
Rust 为什么那么有魅力呢?自己去探索吧。
不出意外,这里是大家最喜欢的开源 Rust 工具列表:
cat
替代品(语法高亮、自动分页等)。gotop
的更好的平替工具find
平替工具cd
命令ls
选择yazi
](https://github.com/sxyazi/yazi[53]) ,用Rust编写的快速终端文件管理器,基于异步I/O欢迎补充。这里并没有列出全部。
SemVer(语义版本控制)是一个版本号命名规范,旨在使得版本号的分配和增加更加清晰和预测。它遵循的格式是MAJOR.MINOR.PATCH
,其中:
MAJOR
:大版本号,当你做了不兼容的API更改时,增加。MINOR
:小版本号,当你添加了向后兼容的新功能时,增加。PATCH
:补丁版本号,当你做了向后兼容的错误修复时,增加。SemVer 违规(Semver violations)通常指的是软件库或应用不遵守这些规则的情况。例如:
SemVer 违规可能会导致依赖该库的其他软件出现问题,因为它们可能期望某些功能或API保持不变,但实际上已经发生了变化。这可能会导致错误、程序崩溃或其他不可预见的问题。遵守 SemVer 规则可以帮助开发人员更好地理解库或应用的更改,并更好地管理他们的依赖关系。
语义化版本(semver)的错误是很常见的。即使是由经验丰富的维护者精心开发的项目也会发生这种情况。维护者并不应受到责备,改进的工具是我们前进的最佳途径。 cargo-semver-checks
是改进工具的一部分,因为它发现了我们在这里报告的每一个语义化版本违规。
对维护人员要求完美是幼稚、不合理和不公平的。每当勤奋、有责任心、出于善意的人犯错时,失败并不在于人,而在于系统。
“Rust 语言的哲学:人的错误无法避免, 用更好的工具来解决人的错误。
以下是对原文的 GPT 总结:
Tomasz Nowak 和 Predrag Gruevski 分析了Rust中的semver(语义版本)遵从性,发现即使在最受欢迎的crates中,偶尔也会出现 semver 违规。这些违规并非人为错误,而是工具的不足。文章通过使用 cargo-semver-checks工具对 crates.io 上下载量最高的 1000 个库进行检查,得到了大量有价值的数据。结果显示,semver 违规是很常见的,即使是由经验丰富的维护者运行的最仔细开发的项目中也会发生。维护者并不应该受到责备,改进工具是我们前进的最佳方式。
三个引人深思的问题或见解:
ring[59] 是比较知名的加密库,还有另外一个知名的加密库 Rust Crypto [60]。社区中的很多人都好奇为什么 ring 不与Rust Crypto项目合作?
人们之所以问这个问题,是因为 ring 和 Rust Crypto 实现了类似的功能,而 Rust Crypto 是 100% Rust 编写的,而 ring 则是 Rust、C 和汇编语言的混合。为什么这些东西存在并行而不是合作?
最近 ring 作者 Brian 发文希望与 Rust Crypto 合作,以便有一个单一的地方来存放所有由 ring 实现的纯 Rust 编写的基础密码学代码。而 RustCrypto 的负责人也非常乐意合作。
ring 作者对这次合作有一个愿望:我希望看到Ring继承的 Unsafe 汇编语言在性能方面被Rust Crypto的纯 Rust 实现所超越。对此,RustCrypto 负责人给出了回应:
“就个人而言,我对消除 unsafe
并不是那么狂热。我认为使用内部函数和 asm!
对于高效和正确实现加密算法至关重要。不幸的是,编译器可能会非常古怪和脆弱,正如这个问题所示(它们可能存在错误编译的漏洞 1[61], 2[62])。就性能而言,手写汇编通常也胜过“软”(即目标无关)的实现。虽然有一些新的发展可以帮助减少基于内部函数的代码中的 unsafe
数量,但我怀疑它会完全没有 unsafe
。显然,任何 unsafe
代码都应该受到严格审查和良好隔离(就像我们在 `block-buffer`[63] 和 `inout`[64]创建的情况下一样)。幸运的是,我们得到了越来越多来自第三方审计和依赖我们创建的公司(如谷歌)的内部审查的关注。我相信我们能够在不影响性能的情况下消除绝大部分 unsafe
。但是,我强调在 ring 中消除 unsafe
并不是对Rust Crypto使用 unsafe
的批评。“
“我认为在密码编码、内部函数和低级优化(例如在 unsafe
包中)中使用 asm!
是可以的。但我相信我们应该努力减少对C库的依赖(除了像 libc
这样的东西,这通常是与操作系统和其他系统通信的唯一稳定方式),以及由外部编译器编译的汇编文件。即使在安全方面之外,使用这些方法也会导致构建人性化和可移植性方面的各种问题,正如ring所清楚展示的那样。在RustCrypto中,我们有一个实验性的asm-hashes存储库,其中的crate使用 cc
来构建 .S
文件。但是这种方法存在一些问题,因此我们正在逐步弃用这些crate,并将它们替换为直接在哈希crate内部使用 asm!
后端。”
看来如何平衡 Unsafe 代码,在这个领域还是充满了挑战。
mrustc[65] 是 Cpp 实现的 Rust 编译器,旨在创建一个用于编译 rustc
的Rust编译器,而无需现有的 rustc
二进制文件。它能够从源代码编译出Rustc 1.19、1.29、1.39和1.54版本。但最终 mrustc 可能会成为一个完全独立的 Rust 编译器重新实现版本。
我很好奇,mrustc 可以用在哪些场景呢?我直接用 rustc 不行吗?作者在 reddit 评论中回答了该问题。
官方的Rust编译器本身就是用Rust编写的。所以要能够构建它,你需要已经能够以某种方式编译Rust代码。这是一个先有鸡还是先有蛋的问题。mrustc
解决了这个问题,因为它是用C++编写的。所以你可以拥有一个干净的引导链:汇编 -> 最小的C编译器 -> 正确的C编译器 -> C++编译器 -> mrustc -> 官方Rust编译器。
希望能够在没有预先存在的 Rust 编译器的情况下进行构建的主要原因是,希望在构建整个链时无需依赖于不可审计的二进制文件(或者只希望信任一个二进制文件,例如 C 编译器)。有一篇半著名的论文《对信任的思考》[66]提出了一种理论攻击,即对编译器进行修补,使其在某些进程(例如ssh服务器)中添加后门,并能够检测自身的源代码,并将这种逻辑复制到新构建的编译器中。从而使得这种妥协能够在尝试编译“干净”的编译器时存活下来。
类似的攻击事件已经发生过,这里只找到一个[例子](https://nakedsecurity.sophos.com/2009/08/18/compileavirus/[67])。随着 Rust 语言成为基础设施主流语言,这种攻击手段将变得越来越有价值。基础设施的一个漏洞可能会影响数百万用户的人,就好像最近被披露的两个 0Day 漏洞,直接影响 Chrome 和 Firefox 所有用户。
这么看来,mrustc 的价值还是很大的。
那么作者为什么选择 C++ 来开发 mrustc 呢?作者说 Cpp 是他的日常工作语言,而且非常通用。mrustc 最近已经支持了(一半)借用检查器,目前工作还在继续。
最近 Rust 社区多了线性类型和并发性的一些讨论。我们来了解下什么是线性类型?
“Prompt: 什么是 linear types? 线性类型和仿射类型区别? Rust 所有权是哪种类型?
线性类型(Linear Types)是类型系统中的一个概念,用于确保某个值在其生命周期中只被使用一次。这种“一次性使用”的特性对于某些应用场景,如资源管理和并发编程,非常有用。
以下是线性类型的一些关键点:
线性类型和仿射类型(Affine Types)都是类型系统中的概念,用于描述值的使用模式和约束。尽管它们在某些方面有所相似,但它们之间存在明确的区别:
简而言之,线性类型确保值恰好被使用一次,而仿射类型确保值最多被使用一次。这两种类型都可以帮助避免资源泄露、数据竞争和其他并发或资源管理问题,但它们的使用约束和应用场景有所不同。
Rust 的所有权系统最接近于仿射类型(Affine Types)。
在 Rust 中,每个值都有一个称为其“所有者”的变量。一次只能有一个所有者,这确保了在任何给定时刻,只有一个变量可以对值进行修改。当所有者超出作用域时,值将被丢弃。但是,Rust 允许你将值的所有权转移给另一个变量(称为移动),在这种情况下,原始变量将不再是值的所有者并且不能再次使用该值。这确保了每个值在其生命周期中最多只有一个所有者,这与仿射类型的定义相符。
值得注意的是,Rust 还提供了引用和借用的概念,允许多个变量同时访问同一个值,但这些引用必须遵循特定的规则,以确保内存安全和数据竞争的自由。
总的来说,尽管 Rust 的所有权系统具有仿射类型的特性,但它还包括其他概念和规则,如借用检查器,使其成为一个独特和强大的系统,专为内存安全和并发编程设计。
Rust 中引入线性类型,会有很多好处。比如保证析构函数将被调用,以便可以依靠它们来确保程序的正确性。所以 Rust 线性类型也可以被定义为保证其析构函数将被调用的类型。
Yoshua Wuyts 在三月份,提供了一个[关于"线性类型"的最小可行性设计](https://blog.yoshuawuyts.com/linear-types-one-pager/ "关于"线性类型"的最小可行性设计") 。
该设计中引入 Leak
trait,目的是为了提供一个机制,使得Rust的类型系统可以明确地处理线性类型,同时还保持与现有的非线性类型的兼容性。这种设计允许开发者在需要的地方使用线性类型,而在其他地方继续使用传统的Rust类型。
以下是关于 Leak
trait的主要点:
Leak
trait,可以明确标记哪些类型是线性的(即它们的析构函数必须被调用)和哪些类型可以被安全地泄露。+ Leak
边界,就像默认的+ Sized
边界一样。这意味着,除非明确指定,否则所有类型都被认为是可以安全泄露的。+ ?Leak
边界。这意味着这些函数可以处理线性类型,但也可以处理非线性类型。!Leak
或在字段中放置一个PhantomLeak
类型。mem::forget
,总是需要+ Leak
边界,因为它们允许值被安全地泄露。线性类型不能被安全地传递给这些API。Rust 有两种方式可以添加这个新 trait:
Send
和 Sync
Sized
。默认是所有类型都可以安全 Leak , mem::forget
也不需要添加新的 Leak trait,只有在要编写一个接受线性类型的泛型函数,你必须写一个 ?Leak
的限制。 <T>
<T: Leak> // needs leak
<T: !Leak> // needs not leak
<T: ?Leak> // don't care about leak
无论哪种方式,都对向后兼容提出了挑战。而 Rust 语言向后兼容是最重要的保证。在最近 GOSIM Rust 语言论坛上面 Rust 语言共同负责人 Josh 也说到即便到 2051 年,Rust 语言代码也是向后兼容的。
Rust Project 决定在 2024 Edition 中添加 Leak
trait。引入 Leak
trait 有助于简化结构化并发的 API 和 增强其安全性,许多系统 API 的安全封装可能会更容易。但是这个 Leak
trait 很可能带来破坏性更改,目前官方团队还没有什么解法。
withoutboats 坦言,如果回到 2015 年,他会强烈要求 Rust 团队在 1.0 时增加这个 trait。
话说回来,即便 withoutboats 回到 2015 年,他也不一定能改变什么。因为 Rust 团队中不存在 BDFL。
“善良的终身独裁者(BDFL, Benevolent dictator for life)是一个称号,赋予少数开源软件开发领导者,通常是项目创始人,在社区内争议或争论中保留最终决定权。
很显然,Rust 的创始人 Graydon 并非 BDFL 。因为在Graydon 的博客[68]上披露了他对现在 Rust 特性有很多不喜欢的设计。比如异步 Rust ,他想要的其实是类似于 Go 的绿色线程。他甚至也不喜欢 Rust 的 trait 机制。也许我们该庆幸,Graydon 不是 BDFL , Rust 团队中没有 BDFL。
回到 2015 年,那么 withoutboats 就能把 Leak trait 加进去吗?不一定。
这个话题我之前发过文章介绍了,可以移步:Reddit 观察:你何时会考虑使用 Cpp 而非 Rust ?
polonius[69] 是 Rust 团队正在进行的下一代 Rust 借用检查器。目前官方正在努力解决最后几个性能问题,并考虑用这个新的重新实现来替换现有的借用检查器。这项工作对于 Rust 用户来说基本是无感的。
“Polonius 这个名字来自于莎士比亚的《哈姆雷特》中人物波洛尼厄斯(Polonius)的名言:“不借钱,也不借给别人。”
Polonius 相比于目前的借用检查器的区别在于改变了生命周期分析的方式。NLL 将 'r
视为由一组程序点组成的生命周期,Polonius 中则将 'r
称为包含一组“借贷”(loans)的起源。换句话说,Polonius 不再追踪引用将被使用的代码部分,而是追踪引用可能来自的位置。
let mut x = 22;
let mut y = 44;
let mut p: &'0 u32 = &x; // 借用 `x`,借贷标记为 L0
y += 1; // (A) Mutate `y` -- is this ok?
let mut q: &'1 u32 = &y; // 借用 `y`,借贷标记为 L1
if something() {
p = q; // `p` 现在指向 `y`
x += 1; // (B) 修改 `x` -- is this ok?
} else {
y += 1; // (C) 修改 `y` -- is this ok?
}
y += 1; // (D) 修改 `y` -- is this ok?
read_value(p); // 再次使用 `p`
用不同的借用检查工具检查后得到下面结果:
代码位置 | 当前借用检查器 | Polonius | MiniRust |
---|---|---|---|
A | ✔️ | ✔️ | OK |
B | ✔️ | ✔️ | OK |
C | ❌ | ✔️ | OK |
D | ❌ | ❌ | Can cause UB, if true branch is taken |
“minirust[70] 是官方团队成员 RalfJung 开发的用于描述 Rust 语义规范的类型 MIR 语言。它也包含了类型检查和借用检查等功能。
分析:
x
和y
的借用。这两个借用都有关联的生命周期:'0
和'1
。&x
),我们实际上是在创建一个借贷(Loan)。这个借贷在某些代码段中是活跃的,这意味着我们不能修改被借用的值。y
,但唯一引用y
的变量是q
,它将不再被使用。当前的借用检查器报告错误是因为它过于保守。而Polonius则正确地处理了这种情况。活跃借贷的计算和重新分配是Polonius分析方法的核心部分。以下是详细的解释:
活跃借贷的计算
p
是活跃的,因为它在后面被用于read_value(p)
。由于p
可能引用L0或L1(取决于something()
的结果),所以L0和L1都被认为是活跃的。重新分配的影响
p = q;
时,变量p
被重新分配。这意味着,之前p
持有的L0引用不再活跃,因为它已经被L1替代。总的来说,活跃借贷的计算是确定在给定程序点哪些引用仍然有效的关键,而重新分配的影响则是确定哪些引用不再有效的关键。这两个概念共同为Polonius 提供了一个更准确和直观的方法来分析Rust代码中的借用和生命周期。
Polonius 也正在引入流敏感(Flow-sensitive)分析来提升精准度。流敏感的类型检查考虑了代码的执行顺序。这意味着它会根据代码的实际执行路径来检查类型,而不是简单地检查每一行代码。这种方法可以更准确地捕获潜在的类型错误,特别是在有条件语句或循环的代码中。
在 Rust 中,变体大小不同的枚举(或标记联合)容易导致严重的内存碎片化。因为需要内存对齐,所以要分配足够的数据来容纳最大的变体。
比如下面这个枚举:
pub enum Foo {
A(u8),
B(u16),
C(u32),
D(u64),
}
当将大量这样对枚举存到 Vec 或 HashMap 中时,会带来很大的麻烦。
以一个简单的例子来说明一个有3个变量的枚举类型,成员大小分别为8、16和32位。将它们存储在一个普通的Vec中将会是这样的:
为了降低内存碎片的一个优化策略就是标记索引, 比如许多运行时实现了标记指针来避免空间开销。
“本部分为GPT-4 创作。
“Prompt: 标记索引如何解决内存碎片化?
标记索引(Tagged Index)是一种优化技术,用于解决内存碎片化问题,特别是在处理不同大小的枚举变体时。这种技术的核心思想是使用一个小的标记(tag)来表示枚举的当前变体,而不是为每个可能的变体分配最大的空间。
总的来说,标记索引是一种有效的技术,可以显著减少内存碎片化,特别是在处理具有不同大小变体的枚举时。这种技术在高性能编程中尤为重要,因为它可以提高内存使用效率并减少缓存未命中的次数。
有一种更好的优化内存碎片的方法是 SOA 转换。
结构体的数组(Array of Structures, AoS)和结构体数组(Structure of Arrays, SoA)是两种常见的数据布局策略,它们在内存布局、访问模式和性能优化方面有着根本的区别。在某些情况下,从AoS转换为SoA可以带来显著的性能提升。
Rust 也有一个第三方库来完成这种转换 soa-derive[72] 。
#[derive(StructOfArray)]
pub struct Cheese {
pub smell: f64,
pub color: (f64, f64, f64),
pub with_mushrooms: bool,
pub name: String,
}
转换为
pub struct CheeseVec {
pub smell: Vec<f64>,
pub color: Vec<(f64, f64, f64)>,
pub with_mushrooms: Vec<bool>,
pub name: Vec<String>,
}
SOA 转换的好处有:
SoA布局特别适用于以下场景:
那么回到枚举中,采用 SOA 转换的思想,将枚举判别式和变体值分别存储在两个独立的分配中。为了真正消除值中的碎片化,我们可以为每个变体创建一个Vec。
这个转换在 Rust 中只可以依赖于过程宏来实现。
在 Zig 中创建这样的数据结构非常简单,依赖于 Zig 分阶段编译的特性。但是在Rust中使用过程宏创建这些示例基本上是不可能的 。原因是过程宏无法访问类型信息,如大小或对齐方式。虽然你可以使用过程宏生成一个计算特定枚举的簇的 const fn
,但是这个函数不能用于指定泛型类型的数组长度。
在 Rust 中编写非常高效的通用数据结构并不总是容易的 。在某些情况下,它们会带来许多意外的复杂性,而在其他情况下,它们基本上是不可能实现的。
分阶段编译,更通用的一个术语是多阶段(multi stage)编程。
一般而言,编译过程被划分为一系列(实际上是一个有向无环图)的编译阶段,每个阶段生成的代码被下一个阶段使用,而且每个阶段都是有效的代码并且经过了正确的类型检查。这就允许开发者编写基本上是类型安全的宏,并且可以访问部分编译程序的能力,这也是分阶段编译与类似 Lisp 宏的东西之间的区别所在。
Zig 的分阶段编译(Staged Compilation)是 Zig 编程语言中的一个核心特性,它允许在编译时执行代码并生成代码。这种能力使得 Zig 可以在编译时进行各种计算、代码生成和优化,从而为开发者提供了极大的灵活性和控制力。
以下是Zig的分阶段编译的一些关键点:
comptime
关键字表示在编译时执行某段代码。这意味着你可以在编译时进行计算、决策和代码生成,而不是在运行时。comptime
允许你在编译时进行复杂的条件判断,从而决定是否包含或排除某些代码。反观 Rust 语言,要实现编译期反射还需要一段路要走啊!
[1]
Introducing RustRover – A Standalone Rust IDE by JetBrains: https://reddit.com/r/rust/comments/16hiw6o/introducing_rustrover_a_standalone_rust_ide_by/
[2]
Microsoft rewrote Azure Quantum Development Kit (QDK) in Rust, now it is 100x faster, 100x smaller, and it runs in the browser!: https://reddit.com/r/rust/comments/16p86ww/microsoft_rewrote_azure_quantum_development_kit/
[3]
Stabilization PR for async fn in traits: https://reddit.com/r/rust/comments/16hv2i4/stabilization_pr_for_async_fn_in_traits/
[4]
The legendary ???? operator: https://reddit.com/r/rust/comments/16t8tap/the_legendary_operator/
[5]
influxdb officially made the switch from Go => Rust: https://reddit.com/r/rust/comments/16v13l5/influxdb_officially_made_the_switch_from_go_rust/
[6]
I'm in love with this fucking language: https://reddit.com/r/rust/comments/16jz0yz/im_in_love_with_this_fucking_language/
[7]
I never want to return to Python: https://reddit.com/r/rust/comments/16mv8bb/i_never_want_to_return_to_python/
[8]
What are your favorite (simple) Open Source tools written in Rust?: https://reddit.com/r/rust/comments/16g38gp/what_are_your_favorite_simple_open_source_tools/
[9]
Semver violations are common, better tooling is the answer: https://reddit.com/r/rust/comments/16cj1mo/semver_violations_are_common_better_tooling_is/
[10]
Why doesn’t the ring project work together with the Rust Crypto project?: https://reddit.com/r/rust/comments/16soday/why_doesnt_the_ring_project_work_together_with/
[11]
mrustc now has (half of) a borrow checker: https://reddit.com/r/rust/comments/168qvrt/mrustc_now_has_half_of_a_borrow_checker/
[12]
Changing the rules of Rust: https://reddit.com/r/rust/comments/16kzuvz/changing_the_rules_of_rust/
[13]
Considering C++ over Rust: https://reddit.com/r/rust/comments/16bkx6b/considering_c_over_rust/
[14]
Polonius revisited, part 1: the next generation of the Rust borrow checker: https://reddit.com/r/rust/comments/16pld9h/polonius_revisited_part_1_the_next_generation_of/
[15]
When Zig Outshines Rust - Memory Efficient Enum Arrays: https://reddit.com/r/rust/comments/16ltlsv/when_zig_outshines_rust_memory_efficient_enum/
[16]
Azure Quantum: https://learn.microsoft.com/zh-cn/azure/quantum/overview-azure-quantum
[17]
Qiskit: https://learn.microsoft.com/zh-cn/azure/quantum/quickstart-microsoft-qiskit-portal
[18]
Cirq: https://learn.microsoft.com/zh-cn/azure/quantum/quickstart-microsoft-cirq-portal
[19]
Async Rust is a Bad Language: https://www.reddit.com/r/rust/comments/16dk9ya/async_rust_is_a_bad_language/
[20]
Maciej Hirsz: https://maciej.codes/2022-06-09-local-async.html
[21]
smol: https://github.com/smol-rs/smol
[22]
embassy: https://github.com/embassy-rs/embassy
[23]
glommio: https://github.com/DataDog/glommio
[24]
volo: github.com/cloudwego/volo
[25]
the Async Book: https://rust-lang.github.io/async-book/01_getting_started/02_why_async.html#async-vs-threads-in-rust
[26]
https://github.com/nix-rust/nix/blob/master/src/mount/linux.rs#L127: https://github.com/nix-rust/nix/blob/master/src/mount/linux.rs#L127
[27]
starship: https://starship.rs/
[28]
ruff: https://github.com/astral-sh/ruff
[29]
polars: https://www.pola.rs/
[30]
typst: https://typst.app/
[31]
delta: https://dandavison.github.io/delta/
[32]
atuin
: https://github.com/atuinsh/atuin
[33]
bat: https://github.com/sharkdp/bat
[34]
bottom
: https://crates.io/crates/bottom
[35]
fd
: https://github.com/sharkdp/fd
[36]
hyperfine
: https://github.com/sharkdp/hyperfine
[37]
ripgrep
: https://github.com/BurntSushi/ripgrep
[38]
zoxide: https://github.com/ajeetdsouza/zoxide
[39]
eza: https://github.com/eza-community/eza
[40]
Helix: https://helix-editor.com/
[41]
Alacritty: https://github.com/alacritty/alacritty
[42]
zellij: https://github.com/zellij-org/zellij
[43]
mask: https://github.com/jacobdeichert/mask
[44]
Zola: https://github.com/getzola/zola
[45]
Cobalt: https://github.com/cobalt-org/cobalt.rs
[46]
Minisign: https://github.com/jedisct1/rust-minisign
[47]
Signify: https://github.com/badboy/signify-rs
[48]
Gitoxide (git based VCS): https://github.com/Byron/gitoxide
[49]
Pijul (New VCS): https://nest.pijul.com/pijul/pijul
[50]
Glide: https://github.com/philn/glide
[51]
Lemmy (Reddit Clone w/ Federation): https://github.com/LemmyNet/lemmy
[52]
Plume (Blogging): https://github.com/Plume-org/Plume
[53]
yazi
: https://github.com/sxyazi/yazi
[54]
typos: https://github.com/crate-ci/typos
[55]
hyperlink: https://github.com/untitaker/hyperlink
[56]
feroxbuster: https://github.com/epi052/feroxbuster
[57]
tokei: https://github.com/XAMPPRocky/tokei
[58]
RustQuant: https://github.com/avhz/RustQuant
[59]
ring: https://github.com/briansmith/ring
[60]
Rust Crypto : https://github.com/RustCrypto
[61]
1: https://github.com/rust-lang/rust/issues/101346
[62]
2: https://github.com/rust-lang/rust/issues/63791
[63]
block-buffer
: https://docs.rs/block-buffer
[64]
inout
: https://docs.rs/inout
[65]
mrustc: https://github.com/thepowersgang/mrustc
[66]
《对信任的思考》: https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_ReflectionsonTrustingTrust.pdf
[67]
例子: https://nakedsecurity.sophos.com/2009/08/18/compileavirus/
[68]
Graydon 的博客: https://graydon2.dreamwidth.org/307291.html
[69]
polonius: https://github.com/rust-lang/polonius/
[70]
minirust: https://github.com/minirust/minirust
[71]
tagged_index crate
: https://doc.rust-lang.org/stable/nightly-rustc/rustc_data_structures/tagged_ptr/index.html
[72]
soa-derive: https://github.com/lumol-org/soa-derive