Search Posts

标签: Rust

Linux的层级架构与Rust的主要模块crate总结,值得转发收藏

Linux的层级架构

每个操作系统都有一个内核,内核封装了底层硬件设备管理、内存管理、网络数据协议转化和收发传输、文件系统读写等。从这个图可以看到,内核将系统硬件与应用程序进程连接起来,隐藏了上层下层交互的一些细节,各司其职。

这些分层包括:

  • 用户空间程序
  • 编译器
  • 终端
  • 防火墙
  • 系统调用的跨平台API(特定于平台的系统调用包装API)
  • Rust标准库
  • libc(或等效的API)
  • kernel,操作系统的核心模块
  • 系统资源
  • 内存
  • 文件系统
  • 网络
  • 硬件和其他设备(包括键盘、鼠标、监视器、磁盘驱动器)

Rust的标准库的功能划分

而Rust标准库,很好的利用了操作系统内核提供的API。

Rust标准库是Rust程序进入Linux操作系统内核函数的主要接口,它在内部使用libc(在Windows系统使用其他等效的库)来调用内核提供的系统调用。

从Rust程序中发起系统调用,以实现管理和操作各种系统资源(如图)。libc(或其变体)为类UNIX操作系统上的系统调用提供了一个包装器,如Linux内核实现了POSIX标准指定的数百个POSIX API(对于Windows,系统调用有等效的API,也实现了POSIX标准)。

作为标准库,Rust标准库是跨平台的,Rust标准库的系统调用的细节是从Rust开发人员那里抽象出来的。Rust也支持不依赖于标准库的运行方式(no_std 方式),Rust直接操控底层硬件(如应用在嵌入式系统开发场景),此时Rust就做了操作系统本身的工作。

对于大部分软件开发工程师而言,他们用Rust主要开发应用层软件,也就是运行在用户空间的程序。它们基于标准库编写,实现各种业务功能。应用层的软件并非所有模块和函数都涉及到系统调用(例如一些用于操作字符串和处理错误的函数,就无需调用系统调用)。

Rust标准库包括几大领域的模块,包括四大类:

第一类,Rust语言原语

即Rust Language Primitives:Rust 语言的基本元素或基本类型(如下图)。

如有符号整数、布尔值、浮点数、字符、字符串、数组、元组、切片。这些由Rust编译器负责实现。

Rust标准包括原语,并在它们之上构建。

第二类,alloc crate

与堆分配值的内存分配相关的类型、函数和特征。

包括集合(Vec、String等集合)、智能指针类型(Box<T>)、引用计数指针(Rc<T>)和原子引用计数指针(Arc<T>))。

第三类,core crate

作为Rust标准库的基础。充当Rust语言与标准库之间的链接,提供在Rust原语之上实现的类型、特征、常量和函数,并为所有Rust代码提供基础构建块,它是跨平台的,没有任何指向操作系统或其他外部依赖的链接。由于较少直接用到core crate,所以本文不做过多介绍。

第四类,模块(标准库的其他crate)

是标准库的一部分,模块crate包括针对并发、I/O,文件系统、网络、异步I/O、错误处理等功能,以及与特定操作系统相关的函数,Rust的官网对std有专门的文档。例如

  • 为用户程序在多个线程上并发运行的功能在std::thread模块中;
  • 用于处理同步I/O的功能在std::io模块中提供;
  • 针对特定os的模块,主要在std::os模块中实现。

下图展示了Rust标准库各个领域功能涉及到的具体std模块(如std::io、std::os等)

以下着重对第四类的主要 crate 做一介绍,并附上文档地址

Rust的并发控制相关模块 conurrency:

模块名 说明
std::env 模块 包含与环境变量交互的功能,包括读取、设置和删除环境变量。
std::sync 模块 提供了用于实现线程安全共享状态的同步原语,如互斥锁(Mutex)、原子操作(Atomic)和条件变量(Condvar)。
std::thread 模块 提供了创建和管理线程的功能,包括线程的创建、 join、spawn 和同步。
std::process 模块 提供了与操作系统进程交互的功能,包括运行外部命令、启动新进程以及与进程进行通信。

Rust的内存管理相关模块 memory management:

模块名 说明
std::alloc 模块 提供了内存分配器的功能,包括分配和释放动态内存。
std::convert 模块 提供了用于不同类型之间转换的工具函数。
std::ptr 模块 提供了对指针的操作和转换功能,包括对裸指针的操作。
std::borrow 模块 提供了用于管理借用的功能,包括&&mut借用运算符的实现。
std::default 模块 提供了默认 trait 实现的功能,用于为不提供具体实现的类型提供默认行为。
std::rc 模块 提供了引用计数(Reference Counting)的功能,用于实现线程安全的共享内存。
std::cell 模块 提供了可变性的 Cell 和 RefCell 类型,用于在多线程环境下安全地共享可变状态。
std::mem 模块 提供了与内存相关的功能,包括内存布局、内存对齐和内存操作。
std::clone 模块 提供了用于实现克隆(Clone) trait 的功能,用于复制和克隆复杂的数据结构。
std::pin 模块 提供了 Pin 类型,用于固定借用的生命周期,以避免悬垂指针和数据竞争问题。

Rust的文件系统操作相关模块 File system:

模块名 说明
std::fs 模块 提供了与文件系统操作相关的功能,包括文件和目录的创建、读取、写入和删除等操作。
std::path 模块 提供了与文件路径相关的功能,包括路径的解析、构造和操作。

Rust的数据处理相关模块 data processing:

模块名 说明
std::ascii 模块 提供了与 ASCII 码相关的功能,包括对 ASCII 字符的操作和转换。
std::fmt 模块 提供了格式化输出的功能,包括对各种数据类型的格式化和打印。
std::num 模块 提供了对数字类型的抽象和操作,包括整数、浮点数和复数等。
std::cmp 模块 提供了用于比较和排序值的工具,包括比较运算符的实现和排序函数。
std::hash 模块 提供了用于计算哈希值的功能,包括对各种数据类型的哈希函数实现。
std::ops 模块 提供了一些基本的运算符和操作符的实现,包括数学运算符、比较运算符和逻辑运算符等。
std::iter 模块 提供了迭代器(Iterator)的功能,包括创建和操作迭代器的方法,以及一些常见的迭代器类型。

Rust的错误处理相关模块 Error handling:

模块名 说明
std::error 模块 提供了错误处理的功能,包括定义错误类型和处理错误的方法。
std::panic 模块 提供了恐慌(Panic)机制,用于处理不可恢复的错误情况。
std::option 模块 提供了 Option 类型,用于表示可能存在或不存在的值,用于处理可能出现空值的情况。
std::result 模块 提供了 Result 类型,用于表示成功或失败的情况,通常用于处理可能出现错误的函数返回值。

Rust的编译处理相关模块 compiler:

模块名 说明
std::hint 模块 提供了一些用于编译器提示的宏,用于影响编译器的优化行为。
std::primitive 模块 提供了一些基本的类型和函数,用于处理数字、字符和布尔值等基本数据类型。
std::prelude 模块 包含了一些基本的函数和宏,这些函数和宏在 Rust 标准库中被广泛使用,并且在每个 Rust 程序中自动导入。

Rust的跨语言调用相关模块: FFI

模块名 说明
std::ffi 模块 提供了与外部函数接口(Foreign Function Interface,FFI)相关的功能,用于与其他语言或库进行交互。

Rust的网络处理功能模块 Networking:

模块名 说明
std::net 模块 提供了与网络编程相关的功能,包括网络协议、套接字(Socket)和网络地址等。

Rust的IO处理模块:

模块名 说明
std::io 模块 提供了与输入输出相关的功能,包括文件操作、缓冲、读写数据等。

Rust的OS特定的功能模块:

模块名 说明
std::os 模块 提供了与操作系统相关的功能,包括文件系统操作、进程管理和系统信息等。

Rust的时间处理模块:

模块名 说明
std::time 模块 提供了与时间和日期相关的功能,包括时间的表示、解析、转换和计算等。
查看余下内容

干货总结Rust的应用范围将会爆发,全栈开发者如何看清2023的历史临界点及未来趋势

Rust 诞生已经有 17 年了,最近我考察了将团队的开发技术栈从 Python 生态转到 Rust 生态的可行性。先说结论:99% 可行。

Rust 生态目前的规模

不是 100% 可行,因为 Python 生态的一些名库还未提供 Rust 版本,但请注意到,那些 Python 生态有的功能,Rust 生态里也大部分有了。毕竟 Rust 已经 17 岁了,经过 17 年的积累,Rust 终于快到 “成年” 了。比如 Rust 的线上 Crate 仓库 lib.rs 现有 127219 个包,Python 的 pypi.org 仓库现有 484174 个包(以上统计截至 2023 年 9 月 28 日)、而 Go 语言在 pkg.go.dev 上有超过 170000 个包(截至 2023 年 4 月)。

为什么我决定从 Python 转换到 Rust?如何通过Rust的强大生态工具,提高开发效率和软件质量?

在我的公号里总结分享了 Rust语言解决问题的能力、它能为使用者提高多少效率、最终创造多少价值。

目前(2023 年)正处在 Rust 大规模应用爆发的临界点。大量软件团队正在用 Rust/Go 重写软件。

下面从三个具有代表性的应用实例以及一些 Rust 语言大事件,向读者分享下这个 “Rust 临界点” 的观点。

详情网页链接查看余下内容

rust+Qt+PaddleOCR 实现的OCR桌面软件示例

功能效果

基于paddleOCR 的图片文字识别(OCR)桌面软件,使用Rust+QT开发。

带调试界面的运行效果:

src/ocr.rs 文件的来源:

安装 ruic 工具,https://github.com/jnbooth/ruic
然后

cd src/
ruic.exe -o uic.rs --all mainwindow.ui

就会得到src/uic.rs 。目前已包含 src/uic.rs 文件,所以无需执行上述 ruic 命令。

注意:

  1. ruic.exe 对 QT5 的Line 类 不支持。需要将uic.rs中重复的声明和 Line 相关的声明删掉。
  2. 编译环境要求:qt 5.14.0或低于该版本的qt。windows 10系统。rust qt在windows下的环境要求在有 msvc 编译器的环境,
    所以建议使用:打开【适用于 VS 2017 的 x64 本机工具命令提示】后cd到项目目录下进行cargo 操作。
  3. 在cargo build 之前,要确保按照 【PaddleOCR-json\说明.txt】 文件的要求放置好 PaddleOCR-json 的文件。
  4. main.rs 中 pub type OcrResult = Vec<Root>; 等代码参考了 https://gitee.com/toobo/PaddleOCRRust/blob/master/src/entity.rs ,在此向toobo( ZHao )表示感谢!感谢 PaddleOCR-json 项目

如何编译:

由于技术栈基于 Rust + Qt 5.14.0 + paddleOCR(C++),所以需要借助一些rust crate库实现。编译过程已通过build.rs 脚本进行了一键封装。

编译测试版

cargo build

编译生产版

cargo build --release

授权协议

Apache-2.0

rust+Qt 的其他项目:

  1. 使用 Rust + Qt 开发的仿有道词典的翻译 + 词典 工具.
  2. rust-qt的官方 examples

结论:

使用QT+Rust开发,主要是练手希望熟悉Rust,以及验证rust-qt的成熟度+OCR软件功能的MVP验证。

  1. 关于成熟度:QT C++能支持的功能,rust-qt也支持(目前rust-qt官方支持QT5.14.0 及以下版本的QT,提供了qt_core 、qt_gui、qt_widgets、 qt_ui_tools 等核心模块的crate库)。
  2. 关于开发效率: 对于熟悉Rust的人员,采用rust-qt的开发效率也可以很高,因为UI可以拖拽然后用 ruic 工具生成rust代码(参考 https://github.com/kerneltravel/rust_qt_gui_paddle_ocr_example
查看余下内容

Rust开发的项目达到一定的规模时,要如何组织代码以避免危机(上篇)

Rust开发的项目达到一定的规模时,要如何组织代码,特别是package、crate和module?这些概念在项目规模增大的时候尤其重要,甚至影响项目后续的生命力。

本文是来自 参考链接[1]的Rust专家的建议的《上篇》,可以帮助避免常见的陷阱、性能问题或编译问题。

中文文章内容如下:

IC (一个开源的区块链项目)的 Rust 代码库从 2019 年 6 月的空存储库增长到 2022 年初的近 350000 行代码。这种快速增长告诉我,对于相对较小的项目来说,运作良好的决策可能会随着时间的推移开始拖累项目。本文评估了 Rust 代码组织选项,并提出了有效使用它们的方法。

Rust的重要“角色”

Rust 的一些术语容易令人困惑,例如术语crate(中文“单元包”,在下文将保留为crate,不再翻译为 单元包)就不太直观。即使是令人尊敬的 《The Rust Programming Language》一书的第一版也包含以下误导性段落:

Rust 有两个与模块系统相关的不同术语:“crate”和“module”。crate在其他语言中是“库”或“包”的同义词。因此,“Cargo”作为 Rust 包裹管理工具的名称:您将crate 与 Cargo 一起分享给其他人。Crate 可以生成可执行文件或库(.so 文件 或 .dll 等都属于动态库),具体取决于项目。

然而,库和包是不同的概念,不是吗?混淆这些概念会导致挫败感,即使你已经有几个月的 Rust 经验。工具约定也会导致混乱:如果 Rust 包定义了库 crate, cargo 则会自动从包名派生库名称。您可以覆盖此行为,但请不要这样做。

接下来让我们熟悉经常打交道的几个概念。

Rust 的 Module (模块)

Module 模块 是代码组织的单元。它是函数、类型和嵌套模块的容器。模块还指定它们定义或重新导出的名称的可见性。

Rust 的 Crate (单元包)

Crate 是编译和链接的单位。Crates是语言的一部分( crate 是一个关键字),但你在源代码中没有太多提及它们。库和可执行文件是最常见的 crate 类型。

Rust 的 Package (包)

包是软件分发的单位。包不是语言的一部分,而是 Rust 包管理器 Cargo 的工件 。一个 Package 可以包含一个或多个 crate:最多一个库和任意数量的可执行文件。

再论 Modules 与 Crates

当您将大型代码库分解为组件时,有两种极端情况:拥有几个包含大量模块的大包(但包数量不多)或具有大量小包(包拆分后数量较多)。

对于前一种情况,即拥有少量包含大量模块的软件包,具有一些优点:

  1. 添加或删除模块比添加或删除包工作量更少。

  2. 模块更加灵活。例如,同一 crate 中的模块可以形成依赖循环:模块可以使用来自模块的定义,而模块又可以使用来自其他模块如 foo bar foo 的定义。相反,包依赖项关系图必须是非循环的。

  3. 您不必每次重新排列模块时都修改 Cargo.toml 文件。

在 Rust 即时编译的理想世界中,将存储库转换为包含许多模块的庞大包将是最方便的设置。目前痛苦的现实是,Rust 需要相当长的时间来编译,而模块并不能帮助你缩短编译时间:

编译的基本单元是一个crate,而不是一个模块。您必须重新编译 crate 中的所有模块,即使您只更改一个模块。放入crate的代码越多,编译所需的时间就越长。

编译项目时,cargo对不同的crate可以并行编译,而不是在逐个crate编译。所以如果你有几个大包,你就不能充分利用多核CPU的潜力。

这两种拆分方式,是便利性和编译速度之间的权衡。Modules模块很方便,但不能帮助编译器减少工作量。Package包不太方便,但随着代码库的增长,编译速度会更好。

项目代码结构的建议

拆分依赖项中心。

有两种类型的依赖项中心:

  1. 具有大量依赖项的包。例如IC代码库中的两个示例(examples)是包含集成测试辅助代码(proptest策略,模拟和伪造组件实现,帮助程序函数等)的 test-utils replica 包,以及实例化所有组件的包。

  2. 具有大量反向依赖项的包。例如IC代码库中的示例,包含通用类型定义的 types 封装,以及指定元件接口的 interfaces 封装。

IC项目的包依赖关系图的一部分。图中 types 和 interfaces 是二类依赖中心,relica是一类依赖中心,test-utils 既是一类,又是二类依赖中心。

依赖中心是及其关键的,因为它们会对增量编译速度产生重大影响。如果您修改具有许多反向依赖项的软件包(例如图中的 types ),cargo 必须重新编译所有这些依赖项以检查您的更改。

有时可以消除依赖关系中心。例如,包 test-utils 是一些独立实用程序的联合。我们可以按它们所属的测试组件对这些实用程序进行分组,并将对应的实用程序代码分解到多个 -test-utils 包中。

但是,更常见的是,依赖中心将不得不保留。某些 types 类型是普遍存在的。包含这些类型的包注定是二类依赖项中心。连接所有组件的 replica 包注定是一类依赖中心。您能做的最好的事情就是本地化连结并使它们小而稳定。

请考虑使用泛型和关联类型来消除依赖项。

这个建议需要一个例子,所以请耐心等待。

types 、 interfaces 和 replicated_state 是 IC 代码库中的首批封装之一。该 types 包,含有通用类型定义,interfaces包定义软件组件的特征,replicated_state 包定义 IC 的复制状态机数据结构, ReplicatedState 类型位于根目录。

但是为什么我们需要这个 types 包呢?既然Types是接口的一个组成部分,那为什么不在interfaces 包内部定义Types呢?

原因是某些接口引用了该 ReplicatedState 类型。 replicated_state 包依赖于types包中的类型定义。如果所有类型都存在于 interfaces 包中,可能导致 replicated_state 和 interfaces 之间存在循环依赖关系。

如图,types、 interfaces 和 replicated_state 包的依赖关系图。

当我们需要打破循环依赖时,我们可以将公共定义移动到新包中或合并一些包。 replicated_state 包很重;我们不想将其内容合并入interfaces包。因此,我们采用了第一个选项:将不同interface 和replicated_state 包之间共享的类型移动到 types 包中。

interfaces 包的特征定义有个特点:特征仅取决于 ReplicatedState 类型名称。这些特征不需要知道 ReplicatedState 的定义。

trait StateManager {
  fn get_latest_state(&self) -ReplicatedState;

  fn commit_state(&self, state: ReplicatedState, version: Version);
}

这段代码是interfaces包的特征定义的示例,它依赖于ReplicatedState类型。

interfaces 包中有个例子演示了依赖于 ReplicatedState 类型的特征定义。

此属性允许我们打破interfaces 与 replicated_state 之间的 直接依赖关系。我们只需要用泛型类型参数替换确切的类型。

trait StateManager {
  type State; //< We turned a specific type into an associated type.

  fn get_latest_state(&self) -> State;

  fn commit_state(&self, state: State, version: Version);
}

不依赖于 ReplicatedState 的 StateManager的特征定义的通用版本。

基于此,我们不再需要在每次向复制状态添加新字段时重新编译 interfaces 包及其众多依赖项。

运行时多态性是首选。

我们设计的考量之一是如何连接软件组件。我们应该像Arc的方式把组件的实例以运行时多态性传递,还是作为泛型类型参数(编译时多态性)传递 ?

pub struct Consensus {
  artifact_pool: Arc,
  state_manager: Arc,
}

上段代码是使用运行时多态性组合组件。

pub struct Consensus {
  artifact_pool: AP,
  state_manager: SM,
}

上段代码使用编译时多态性组合组件。

编译时多态性是必不可少的工具,更是重量级的工具。运行时多态性需要更少的代码,且有助于更少的二进制膨胀。大多数团队成员也发现该 dyn 版本(即上述第一段代码)更易于阅读。

首选显式依赖项。

新开发人员在开发频道上最常问的问题之一是“为什么我们要显式传递loggers?全局的loggers似乎也能工作得很好”。这是个好问题。如果回到2019年我也会问同样的问题!

全局变量很糟糕,但我以前的经验表明,日志对象loggers和指标接收器(metric sinks)很特殊。哦,好吧,其实也没有那么特殊。

隐式状态依赖的常见问题在 Rust 中尤为突出。

大多数 Rust 库不依赖于真正的全局变量。传递隐式状态的常用方法是使用线程局部变量,当您生成新线程时,这可能会成为问题。新线程倾向于继承并保留线程局部变量的意外值。

默认情况下,Cargo 在测试二进制文件中并行运行测试。如果不小心通过调用堆栈对loggers进行线程处理,测试输出可能会变得无形的混乱。当后台线程需要访问日志时,通常会出现此问题。而通过显式传递loggers的方式则可以消除该问题。

在多线程环境中,对依赖于隐式状态代码的测试很困难甚至不可能。记录指标的代码就是代码。它也值得测试。

如果使用依赖于隐式状态的库,则在依赖于不同包中不兼容的库版本时,可能会引入细微的BUG。

对于这个观点,迫切需要一个例子。这里有一个小合适的故事作为映证:

我们使用普罗米修斯软件包进行指标记录。此包可以将指标注册表保留在全局变量中。

突然有一天,我们遇到了一个错误:我们无法看到某些组件的指标。我们的代码看起来是正确的,但指标却缺失了。

其中一个软件包依赖于普罗米修斯版本 0.9 ,而所有其他软件包都使用 0.10 。根据semver的说法,这些版本是不兼容的,因此cargo将两个版本链接到二进制文件中,引入了两个隐式注册表。我们仅通过 HTTP 接口公开 0.10 版本注册表。正如您正确猜测的那样,缺少的组件将指标记录到注册表中 0.9 。

而传递loggers、指标注册表和异步运行时的方式会显式地将运行时 bug 转换为编译时错误。切换到显式传递指标注册表帮助我找到并修复了该错误。

古老的 slog 包的官方文档还建议明确传递loggers:

原因是:手动传递 Logger 提供了最大的灵活性。使用slog_scope 将日志记录数据结构绑定到堆栈跟踪,这与软件的逻辑结构不同。特别是库应该向用户展示充分的灵活性,而不是使用隐式日志记录行为。

通常 Logger 实例非常适合表示的代码中的资源的数据结构,因此在构造函数中传递它们并在任何地方使用,并不难,像这样:

info!(self.log,
查看余下内容

Rust的Crate和module都是模块和包,有什么区别?

在 Rust 中,Crate 和 module 都是组织代码的方式,但它们的概念和作用是不同的。

Crate 和 module 在作用上的区别:

如果用一句话概括,那就是:一个 Crate 是一个完整的编译单元,它可以包含一个或多个 Rust 模块。

一个 Crate 可以被编译成一个二进制文件或者一个库(静态库或动态库),并且可以被其他 Crate 依赖和使用。可以将 Crate 视为一个库或者一个可执行文件的项目。

一个 module 是一个命名空间,它可以包含 Rust 代码的定义和实现,包括常量、函数、结构体、枚举、trait 等等。通过使用 module,我们可以将相关的代码组织在一起,使得代码更易读、更易维护、更易扩展。一个 module 可以被嵌套在另一个 module 中,形成一个层次结构。

在 Rust 中,Crate 和 module 之间有一个非常重要的概念:路径。路径是用来访问 Rust 中的定义和实现的方式。一个路径可以是绝对路径或相对路径。绝对路径是从 Crate 根开始的路径,而相对路径是从当前 module 开始的路径。在 Rust 中,路径的起点通常是一个 Crate,从而实现了代码的组织和封装。

Crate 和 module 在代码中的应用举例:

下面是一个简单的例子,用来说明 Crate 和 module 在代码中的使用方式区别:

// src/main.rs

// 定义一个模块,包含一个函数
mod greeting {
    pub fn say_hello() {
        println!("Hello, world!");
    }
}

fn main() {
    // 调用模块中的函数
    greeting::say_hello();
}

在这个例子中,greeting 是一个 module,它定义了一个函数 say_hello。在 main 函数中,我们通过路径 greeting::say_hello() 来调用这个函数。

除了 module,Rust 还有另一个组织代码的方式:Crate。如果我们希望将 greeting 模块提取成一个单独的 Crate,可以按照如下方式进行:

// src/greeting.rs

// 定义一个模块,包含一个函数
pub mod greeting {
    pub fn say_hello() {
        println!("Hello,
查看余下内容

对比Javascript和Rust的并发异步机制:Promise与Future

JavaScript 的 Promise 和 Rust 的 Future 都是一种处理异步操作的机制,它们有一些相似的地方:

  1. 异步操作的封装:Promise 和 Future 都可以将异步操作封装成一个对象,并在操作完成后返回结果。在 JavaScript 中,Promise 对象可以用于处理异步操作,而在 Rust 中,Future 对象可以用于处理异步计算和 I/O 操作。

  2. 链式调用:Promise 和 Future 都支持链式调用,可以通过 .then() 或 .map() 等方法将多个异步操作组合起来,达到串行执行的效果。

  3. 错误处理:Promise 和 Future 都支持错误处理机制,可以通过 .catch() 或 .map_err() 等方法捕捉异常并进行处理。

  4. 非阻塞式调用:Promise 和 Future 都是非阻塞式的调用方式,可以避免在等待异步操作完成时阻塞程序的运行。

  5. 并发执行:Promise 和 Future 都支持并发执行多个异步操作,可以通过 Promise.all() 和 Future::join() 等方法将多个异步操作组合起来并行执行。

总体上,Promise 和 Future 都是用于处理异步操作的机制,它们具有一些相似的特点和用法。虽然它们是不同语言中的不同实现,但它们都是为了解决异步编程问题而设计的,可以让开发者更加方便地处理异步操作。… 查看余下内容

Rust的两种类型`Result` 和 `Option`的区别

ResultOption 是 Rust 中两种不同的类型,尽管它们在使用上有一些相似之处。

Option<T> 是 Rust 的一种枚举类型,可以有两个可能的值:Some(T)None。它通常用于表示值的缺失,或者表示计算中可能出现的一些情况,但这些情况不一定是错误。例如,在字符串上调用 parse 方法时,如果字符串不是一个有效的整数,它会返回一个 Result<i32, ParseIntError>,但如果字符串为空,则会返回一个 Option<i32>

Result<T, E> 也是 Rust 的一种枚举类型,可以有两个可能的值:Ok(T)Err(E)。它通常用于表示计算的成功或失败,而计算过程中可能会产生一个错误。例如,fs::read_to_string 函数返回一个 Result<String, std::io::Error>,其中要么是包含文件内容的 Ok(String),要么是表示读取文件时出现的 Err(std::io::Error)

尽管这两种类型都可以用于表示错误的可能性,但它们具有不同的语义,并在不同的上下文中使用。Option 用于表示值的缺失,而 Result 用于表示计算的成功或失败,而计算过程中可能会产生一个错误。… 查看余下内容

如何理解Rust的Option类型和Some()语法的关系,为什么Rust可以这样用if let Some(param) = somefunction() {}

  • Rust语言中经常看到的Some()是什么意思,跟Rust的Option是什么关系?
  • 在c++语言中等号有赋值作用所以等号前面不会是函数,而rust的 if let Some(param) = somefunction() {} 的写法,如果像C++情况下是无法理解的,因为无法将等号后面的内容赋值给函数。怎么解释?
  • if let语法是Rust的match分支匹配的常用到的语法,那为什么if let 又可以跟Some()结合使用?

本文下面将加以阐述:


Rust的Option类型是一种枚举类型(简称 “Option枚举”),它可以表示一个值存在或不存在的情况(表示一种可选值):每个 Option 均为 Some 并包含一个值,或者为 None,但不包含(有意义的值)。 Option 类型在 Rust 代码中非常常见,因为它们有多种用途:

  1. 初始值
  2. 未在整个输入范围内定义的函数的返回值 (部分函数)
  3. 返回值,用于报告否则将报告简单错误的错误,其中错误返回 None
  4. 可选的结构体字段
  5. 可借用或 “taken” 的结构体字段
  6. 可选的函数参数
  7. 可空指针
  8. 从困难的情况中交换东西
  9. 通常将 Option 与模式匹配配对,以查询值的存在并采取措施,始终考虑 None 的情况。
    代码示例:

    fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
    }
    fn main() {
    // 函数的返回值是一个选项
    let result = divide(2.0, 3.0);
    
    // 模式匹配以获取值
    match result {
        // 该划分有效
        Some(x) => println!("Result: {}", x),
        // 划分无效
        None    => println!("Cannot divide by 0"),
    }
    }

Some()是Option类型的一个变体,它包含一个具体的值。

在Rust中,使用if let Some(param) = somefunction() {}的语法可以方便地检查Option类型是否包含一个具体的值,并将该值绑定到变量param中。这种语法类似于C++中的if语句和手动从Option类型中提取值的方式。

作为对比,在C++中,等号用于赋值,因此不能像Rust的模式匹配语法那样使用。但是,C++也有一个类似的功能,称为结构化绑定声明,它允许您从类似元组的对象中提取多个值。

总之,Rust的if let Some(param) = somefunction() {}语法是一种方便的检查和提取Option类型中值的方式,而C++有一个类似的功能称为结构化绑定声明。… 查看余下内容

rust 的 assert!() 宏能否用于生产环境代码中?

一些情况下当assert!() 未通过时,好像会导致函数返回值不符合定义,甚至应用运行退出,那assert!() 看起来是不允许在生产代码里用?通过本文将对此做以解答:


assert!() 宏在 Rust 中是用于调试的工具,它用于检查代码中的条件是否为真。如果条件为假,assert!() 会打印一条错误消息并终止程序。例如

assert!(1==1); //则继续运行
assert!(1==2); //则终止运行

因此,assert!() 宏不应该用于生产环境代码中,因为它可能会导致应用程序崩溃或返回不符合定义的值。在生产环境中,应该使用其他方法来处理错误和异常情况,例如使用 Result 类型来处理可能的错误。… 查看余下内容

加好友请备注:chinaoss
您可以在微信公众号联系我们
我们将24小时内回复。
取消