diff --git a/projects/doc_attr/Cargo.toml b/projects/doc_attr/Cargo.toml new file mode 100644 index 00000000..a0083b51 --- /dev/null +++ b/projects/doc_attr/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "doc_attr" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/projects/doc_attr/src/main.rs b/projects/doc_attr/src/main.rs new file mode 100644 index 00000000..7ad08dfe --- /dev/null +++ b/projects/doc_attr/src/main.rs @@ -0,0 +1,25 @@ +macro_rules! make_function { + ($name:ident, $value:expr) => { + // 这里使用 concat! 和 stringify! 构建文档注释 + #[doc = concat!("The `", stringify!($name), "` example.")] + /// + /// # Example + /// + /// ``` + #[doc = concat!( + "assert_eq!(", module_path!(), "::", stringify!($name), "(), ", + stringify!($value), ");") + ] + /// ``` + pub fn $name() -> i32 { + $value + } + }; +} + + +make_function! {func_name, 123} + +fn main() { + func_name(); +} \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9aaa17be..1c0b582c 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -237,7 +237,8 @@ - [Rust 技巧篇](./chapter_8/rust-tips.rs) - [真实世界的设计模式 | 单例模式 与 Sealed](./chapter_8/singleton_and_sealed.md) - [为 reqwest 增加中间件支持](./chapter_8/reqwest-middleware.md) - - [Rust 编写 GUI 框架?](./chapter_8/rust-gui-framwork.md) + - [想用 Rust 编写 GUI 框架吗?](./chapter_8/gui-framework-ingredients.md) + - [Trait Upcasting 系列 | 如何把子 trait 转成父 trait ?](./chapter_8/what-is-trait-upcasting.md) - [Trait Upcasting 系列 | Part II](./chapter_8/trait-upcasting-part2.md) - [GitHub 趋势榜](./chapter_8/github_trending.md) - [推荐项目 | 基础工具库](./chapter_8/tool_libs.md) diff --git a/src/chapter_8/ant-futures-compat.md b/src/chapter_8/ant-futures-compat.md index a2a04e1b..7f831dee 100644 --- a/src/chapter_8/ant-futures-compat.md +++ b/src/chapter_8/ant-futures-compat.md @@ -1 +1,217 @@ # 蚂蚁集团 | Trait Object 还是 Virtual Method Table + + +> Trait object 是 Rust 动态分发的实现方式。在 2021 年 4 月发刊的 Rust Magazine 中,Jiacai Liu 同学在《Trait 使用及实现分析》文章中介绍了 Rust 中 Ad-hoc 多态的使用方式,包括静态分发与动态分发,并且对 trait object 中的对象安全问题以及原因做出了详细解释。 +> 那么,使用 trait object 就是 Rust 中动态分发的终点吗?事实上我们发现,在很多 Rust 代码中使用了原始的虚表而不是 trait object,这其中的原因又是什么呢? +> 在本文中,会先简单介绍一下 trait object 与虚表,然后结合笔者挑选出的几个具有代表性的代码片段,讨论手动构造虚表而不使用 trait object 的优缺点。 + +## 简介 + +在 Rust 中使用 trait 实现多态有两种方式,静态分发或者动态分发。静态分发使用 trait bound 或者 impl trait 方式实现编译期单态化,根据类型参数生成对应的结构或者函数。动态分发使用 trait object 的方式实现,而由于 trait object 是动态大小类型,无法在编译期确定类型大小,所以一般会使用指向 trait object 的引用或者指针来操作 trait object。而指向 trait object 的引用或者指针本质上是一个胖指针,其中包含了指向擦除了具体类型的对象指针与虚函数表。所以每次调用 trait object 的方法时,需要解引用该胖指针,所以部分观点认为动态分发比静态分发开销更大,而相反的观点认为使用静态分发会导致编译时间变长,编译后二进制文件膨胀以及增加缓存失效概率等问题,所以具体使用哪种方式就见仁见智了。 + +![trait object](./image/ant/1.jpg) + +然而,标准的 `Trait` 结构也有着一些缺陷,比如由于对象安全的要求,一些 trait 无法通过 trait object 的方式使用。所以我们在使用或者阅读一些 Rust crate 的时候会发现,这些库实现了自己的 trait object 结构,比如标准库的 `RawWaker` 结构,`tokio` 的 `RawTask` 结构,`bytes` 的 `Bytes` 结构,`anyhow` 的 `ErrorImpl` 结构,以及关于类型擦除的内存分配器 [^1] 的讨论。在接下来的内容里,我对其中几个实现进行了一些粗浅的分析,并结合一些已有的讨论 [^2],尝试总结它们的共同点。笔者水平有限,如有错漏,烦请指出。 + +## Examples + + + +### `std` 中的 `RawWaker` {#RawWaker} + +Rust 异步编程的核心是 Executor 与 Reactor,其中 Reactor 部分主要是 `Waker` 结构。查看源代码发现 `Waker` 结构仅仅包装了 `RawWaker` 结构。而 `RawWaker` 的结构与指向 trait object 的胖指针十分相似,包含了一个指向任意类型的数据指针 `data` 与自定义此 `Waker` 行为的虚函数指针表 `vtable`。当调用 `Waker` 的相关方法时,实际上会调用虚表中对应的函数,并将 `data` 作为函数的第一个参数传入。 + +```Rust +/// A `RawWaker` allows the implementor of a task executor to create a [`Waker`] +/// which provides customized wakeup behavior. +/// +/// [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table +/// +/// It consists of a data pointer and a [virtual function pointer table (vtable)][vtable] +/// that customizes the behavior of the `RawWaker`. +#[derive(PartialEq, Debug)] +#[stable(feature = "futures_api", since = "1.36.0")] +pub struct RawWaker { + /// A data pointer, which can be used to store arbitrary data as required + /// by the executor. This could be e.g. a type-erased pointer to an `Arc` + /// that is associated with the task. + /// The value of this field gets passed to all functions that are part of + /// the vtable as the first parameter. + data: *const (), + /// Virtual function pointer table that customizes the behavior of this waker. + vtable: &'static RawWakerVTable, +} + +#[stable(feature = "futures_api", since = "1.36.0")] +#[derive(PartialEq, Copy, Clone, Debug)] +pub struct RawWakerVTable { + clone: unsafe fn(*const ()) -> RawWaker, + wake: unsafe fn(*const ()), + wake_by_ref: unsafe fn(*const ()), + drop: unsafe fn(*const ()), +} +``` + +在学习这部分代码的时候,我产生了一个疑问,为什么不使用 Rust 提供的 `Trait` 作为 `Waker` 的抽象,而是要手动实现一个类似 trait object 胖指针的复杂,危险且容易出错的 `RawWaker`。为了解开这个疑问,我尝试使用 `Trait` 来模拟 `RawWaker` 的功能。 + +```Rust +pub trait RawWaker: Send + Sync { + fn clone(&self) -> Box; + + fn wake(&self); + fn wake_by_ref(&self); +} + +impl Clone for Box { + fn clone(&self) -> Self { + RawWaker::clone(&**self) + } +} + +pub struct Waker { + waker: Box, +} +``` + +根据虚表 `RawWakerVTable` 中要求的方法,我们可以写出一个简单的 `RawWaker` trait。这里遇到了几个问题,首先,`RawWaker` 要求实现 `Clone`,这样做的原因在 Saoirse Shipwreckt [^3] 的博客文章中有过简单的总结: + +> 当事件源注册某个 `future` 将等待一个事件时,它必须存储 `Waker`,以便稍后调用 `wake` 方法。为了引入并发,能够同时等待多个事件是非常重要的,因此 `Waker` 不可能由单个事件源唯一拥有,所以 `Waker` 类型需要是可克隆的。 + +然而,`Clone` trait 本身不是对象安全的,因为它有着 `Sized` supertrait 限定。也就是说,如果我们使用 `pub trait RawWaker: Clone` 的写法,则该 `trait` 将无法作为 trait object 使用。所以在使用 `trait` 模拟的 `RawWaker` 中,我退而求其次的为 `Box` 实现了 `Clone`,并将具体的细节转移到了 `RawWaker::clone` 内,这样一来,每次调用 `clone` 方法都会构造一个新的 trait object,并且这些 trait object 会共享同一些数据。 + +其次,为了能够在多线程环境中使用,我要求 `RawWaker` 的 supertrait 为 `Send + Sync`,这样我们可以将其在多个线程间共享或者发送到某个线程中。 + +最后,为了通过指针使用 trait object,我们需要通过将该对象装箱在堆上。那么我们应该选用哪种智能指针呢?在上面的代码中,我使用了 `Box` 作为具体的指针类型,不使用 `Arc` 的原因是唤醒器中公共数据的共享方式应该由具体的实现决定。比如 `RawWaker` 的实现可以使用引用计数的方式跟踪另一个堆上的对象,或者全部指向静态全局的某个对象,比如: + +```Rust +use std::sync::Arc; + +#[derive(Debug, Default)] +struct RcWakerInner {} + +#[derive(Debug, Default)] +pub struct RcWaker { + inner: Arc, +} + +impl RawWaker for RcWaker { + fn clone(&self) -> Box { + Box::new(RcWaker { + inner: self.inner.clone(), + }) + } + + fn wake(&self) { + todo!() + } + + fn wake_by_ref(&self) { + todo!() + } +} + +static GLOBAL_RAW_WAKER: StaticWakerInner = StaticWakerInner {}; + +#[derive(Debug, Default)] +struct StaticWakerInner {} + +#[derive(Debug)] +pub struct StaticWaker { + global_raw_waker: &'static StaticWakerInner, +} + +impl Default for StaticWaker { + fn default() -> Self { + Self { + global_raw_waker: &GLOBAL_RAW_WAKER, + } + } +} + +impl RawWaker for StaticWaker { + fn clone(&self) -> Box { + Box::new(StaticWaker { + global_raw_waker: self.global_raw_waker, + }) + } + + fn wake(&self) { + todo!() + } + + fn wake_by_ref(&self) { + todo!() + } +} +``` + +接下来我们将标准库的 `RawWaker` 与上述实现方式进行一些对比可以发现: + +- 由于我们发现 `Box` 首先经过了一层指针的包装,用来实现 trait object,而具体的 `RawWaker` 实现也很可能会使用指针来共享同一个对象。这样的不仅会在堆内存中占用额外的存储空间,产生许多小对象,也会由于解引用多级指针而带来额外的时间开销。 +- 标准库的 `Waker` 位于 `core::task` 模块下,而 `Box` 与 `Arc` 等结构都位于 `alloc` 模块下,它们都是 `std` 的子集。在通常的 `std` 应用程序中,我们确实可以使用 `Arc` 等智能指针。但 Rust 不想在 `no_std` 的 futures 上妥协,所以我们必须使用特别的技巧来实现这种能力。 + +考虑到上面的这些原因,Rust 选择了使用数据指针与虚表的方式来实现高性能的动态分发。`std::task::RawWaker` 中的 `data` 指针既提供了类型擦除的能力,也实现了对象的共享,非常的灵活。 + +### `tokio` 与 `async-task` 中的 `RawTask` {#RawTask} + +在 `tokio` 与 `async-task` 的代码中,都将 `RawTask` 作为 `Task` 结构的具体实现。与刚才提到的 `RawWaker` 相似,`RawTask` 也通过虚表提供了类似于 trait object 的功能,然而,它们在内存布局上却有着不同的选择。下面以 `tokio::runtime::raw::RawTask` 为例。 + +```Rust +#[repr(C)] +pub(crate) struct Header { + pub(super) state: State, + pub(super) owned: UnsafeCell>, + pub(super) queue_next: UnsafeCell>>, + + /// Table of function pointers for executing actions on the task. + pub(super) vtable: &'static Vtable, + + pub(super) owner_id: UnsafeCell, + #[cfg(all(tokio_unstable, feature = "tracing"))] + pub(super) id: Option, +} + +/// Raw task handle +pub(super) struct RawTask { + ptr: NonNull
, +} +``` + +通过与 `RawWaker` 的结构相对比,我们发现 `RawTask` 将虚表部分移动到了数据指针 `ptr` 内。这么做的好处显而易见,`RawTask` 的内存结构更为紧凑,只需要占用一个指针的大小,缺点是多了一层解引用的开销。 +所以,通过自定义类似 trait object 胖指针的结构,我们可以控制内存布局,使指针更瘦,或者更胖(比如 `async_task::raw::RawTask`)。 + +### `bytes` 中的 `Bytes` {#Bytes} + +`bytes` crate 提供用于处理字节的抽象,它包含了一种高效的字节缓冲结构与相关的 traits。其中 `bytes::bytes::Bytes` 是用于存储和操作连续内存切片的高效容器,这是通过允许多个 `Bytes` 对象指向相同的底层内存来实现的。`Bytes` 结构充当了接口的功能,本身占用四个 `usize` 的大小,主要包含了内联的 trait object。 + +```Rust +pub struct Bytes { + ptr: *const u8, + len: usize, + // inlined "trait object" + data: AtomicPtr<()>, + vtable: &'static Vtable, +} +``` + +它的虚表主要是 `clone` 方法,这允许 `Bytes` 的具体实现来定义具体的克隆或者共享策略。`Bytes` 的文档中举了两个例子 + +- 对于 `Bytes` 引用常量内存(例如通过 `Bytes::from_static()` 创建)的实现,`clone` 实现将是空操作。 +- 对于 `Bytes` 指向引用计数共享存储(例如 `Arc<[u8]>`)的实现,将通过增加引用计数来实现共享。 + +可以看到,与 `std::task::RawWaker` 相似,`Bytes` 需要使用 `clone` 方法,并且具体的实现完全交给了实现方。如果选择 `Trait` 接口的方式,由于公共的数据部分已经是指针的形式,会引入额外的内存分配与解引用开销,感兴趣的同学可以尝试使用 `Trait` 的方式实现一下这两个例子,最终效果和上文中的 `RawWaker` 类似。而在内联了 trait object 之后,整个设计非常优雅,`data` 部分指向共享的内存,`vtable` 定义了如何进行 `clone`,其余字段作为独占的数据。 + +## 总结 + +Rust 提供了安全的抽象以避免产生安全问题或者错误。比如我们使用 `RC` 而不直接管理引用计数,使用 `Box` 而不是 `malloc/free` 直接管理内存分配。同样,`dyn Trait` 隐藏了复杂而又为危险的虚表实现,为我们提供了简单而又安全的动态分发。我们看到,上述手动实现虚表的代码中充斥着大量的 `unsafe`,稍有不慎,就会引入 bug。如果你的设计不能使用标准的 `dyn Trait` 结构来表达,那么你首先应该尝试重构你的程序,并参考以下理由来决定是否使用自定义的虚表。 + +- 你想要为一类指针对象实现多态,并且无法忍受多级指针解引用造成的性能开销,参考 [RawWaker](#RawWaker) 与 [Bytes](#Bytes)。 +- 你想要自定义内存布局,比如像 C++ 中虚表一样紧凑的内存结构(虚表指针位于对象内),参考 [RawTask](#RawTask)。 +- 你的 crate 需要在 `no_std` 环境中使用动态分发,参考 [RawWaker](#RawWaker)。 +- 或者,标准的 trait object 确实无法实现你的需求。 + +## 相关链接 + +[^1]: https://github.com/rust-lang/wg-allocators/issues/33 +[^2]: https://users.rust-lang.org/t/dyn-trait-vs-data-vtable/36127/3 +[^3]: https://boats.gitlab.io/blog/post/wakers-i/ \ No newline at end of file diff --git a/src/chapter_8/aws-lambda-rust-wasm-serverless.md b/src/chapter_8/aws-lambda-rust-wasm-serverless.md index fa6820a1..8d7fe5fb 100644 --- a/src/chapter_8/aws-lambda-rust-wasm-serverless.md +++ b/src/chapter_8/aws-lambda-rust-wasm-serverless.md @@ -1 +1,323 @@ # SecondState | AWS Lambda 中的 Rust 与 WebAssembly Serverless 函数 + +> 作者 [Robby Qiu](https://github.com/robnanarivo), [Second State](https://www.secondstate.io/) 开发与 [WasmEdge](https://github.com/WasmEdge/WasmEdge) 贡献者 + +--- + +Serverless 函数为开发者节省了管理后端基础设施的大量麻烦。Serverless 还简化了开发过程,因为开发者只需关注业务本身的逻辑。本文是有关如何在 Amazon 的 serverless 计算平台 AWS Lambda 上编写和部署 WebAssembly serverless 函数的分步指南。在我们的演示中,WebAssembly 函数使用 [WasmEdge](https://github.com/WasmEdge/WasmEdge) runtime 执行。下图显示了我们解决方案的整体架构。 + +![](https://oscimg.oschina.net/oscnet/up-5a87006d695461fc6aa257e7aee74f62e65.png) + +在本文的第一部分,我们将解释为什么 WebAssembly 是 serverless 函数极佳的 runtime。我们将 WebAssembly 字节码(通常由 Rust、C++ 编译得来)、高级编程语言(例如 Python 和 JavaScript)以及机器本机可执行文件(本机客户端或 NaCl)进行比较。然后,在第二部分,我们将演示两个 serverless 函数示例,都是用 Rust 编写并编译为 WebAssembly 进行部署。第一个示例展示了 WasmEdge 快速处理图像的能力,而第二个示例运行由 [WasmEdge 的 TensorFlow 扩展](https://www.secondstate.io/articles/wasi-tensorflow/)提供支持的 AI 推理。 + +## 为什么选择 WebAssembly? + +简单回答是 WebAssembly 快速、安全且可移植。那么具体是为什么呢?下面是详细回答。 + +### **WebAssembly vs. Python 和 JavaScript** + +[DataDog 最近的一项调查](https://www.datadoghq.com/state-of-serverless/)发现大部分 AWS Lambda serverless 函数是用 JavaScript 和 Python 写的。 二者是世界上最流行的两种编程语言,所以这并不出人意料。 + +但是,众所周知,高级语言运行速度非常慢。 事实上,根据发表在[Science上的一篇论文](https://science.sciencemag.org/content/368/6495/eaam9744) ,Python 比用 C 或 C++ 编写的相同程序最多慢 60,000 倍。 + +因此,虽然 JavaScript 和 Python 非常适合简单的函数,但它们不适合计算密集型任务,例如图像、视频、音频和自然语言处理,这些在现代应用程序中越来越普遍。 + +另一方面,WebAssembly 的性能与 C/C++ 编译的本机二进制文件 (NaCl) 相当,同时仍保持与高级语言 runtime 相关的可移植性、安全性和可管理性。 [WasmEdge](https://github.com/WasmEdge/WasmEdge) 是市场上目前[最快的](https://www.infoq.com/articles/arm-vs-x86-cloud-performance/)WebAssembly runtime 之一。 + +### **WebAssembly vs. 原生客户端** + +但是,当两者都在 Docker 容器或者 microVM 内部运行的时候, WebAssembly 相比 NaCl 的优势有哪些呢? + +> 我们对未来的愿景是在原生基础设施中, [WebAssembly 作为一个替代轻量级 runtime](https://www.computer.org/csdl/magazine/so/5555/01/09214403/1nHNGfu2Ypi),与 Docker 和 microVM 并行运行。与类似 Docker 的容器或 microVM 相比,WebAssembly 性能更加出色并且消耗的资源更少。但就目前而言,AWS Lambda 和许多其他平台仅支持在 microVM 内运行 WebAssembly。尽管如此,与运行容器化的 NaCl 程序相比,在 microVM 中运行 WebAssembly 函数仍然具有许多优势。 + +首先,WebAssembly 为单个函数提供了细粒度的 runtime 隔离。一个微服务可以有多个函数并支持在一个 microVM 中运行的服务。 WebAssembly 可以让微服务**更安全、更稳定**。 + +其次,WebAssembly 字节码是**可移植的**。即使在容器内,NaCl 仍然依赖于安装在 OS 上的底层 CPU、操作系统和动态库。 而 WebAssembly 字节码应用程序是跨平台的。开发者只需编写一次即可部署在任何云、任何容器和任何硬件平台上。 + +第三,WebAssembly 应用**易于部署和管理。**与 NaCl 动态库和可执行文件相比,它们的平台依赖性和复杂性要少得多。 + +最后,WebAssembly 是多语言的。 C/C++、Rust、Swift、Kotlin 程序都可以轻松编译成 WebAssembly。 WebAssembly 甚至支持 JavaScript。 [WasmEdge Tensorflow API](https://www.secondstate.io/articles/wasi-tensorflow/) 提供了以 Rust 编程语言执行 Tensorflow 模型的**最符合习惯的方式**。 + +我们能够看到,WebAssembly + WasmEdge 是一个更好的选择。为了实际见证这个结论,让我们深入示例,亲自上手吧! + +## **前期准备** + +由于我们的 demo WebAssembly 函数是用 Rust 写的,你需要安装一个 [Rust 编译器。](https://www.rust-lang.org/tools/install) 确保你添加了 `wasm32-wasi` 编译器目标(如下),从而生成 WebAssembly 字节码。 + +``` +$ rustup target add wasm32-wasi + +``` + +该 demo 应用程序前端是 [Next.js 写的](https://nextjs.org/),并部署在 AWS Lambda 上。我们假设你已经有使用 Next.js 和 Lambda 的基础知识了。 + +## 案例1:图像处理 + +我们的第一个 demo 应用程是让用户上传一个图像,然后用户调用 serverless 函数将其变成黑白的。 你可以查看已经通过 GitHub Pages 部署好的[实时 demo](https://second-state.github.io/aws-lambda-wasm-runtime/)。 + +> demo 链接: [https://second-state.github.io/aws-lambda-wasm-runtime/](https://second-state.github.io/aws-lambda-wasm-runtime/) + +![](https://oscimg.oschina.net/oscnet/up-cfc102f07e6485bf8e51456b749f796f6eb.gif) + +Fork [demo 应用程序的 GitHub repo](https://github.com/second-state/aws-lambda-wasm-runtime) ,就可以开始部署自己的函数了。将应用程序部署在 AWS Lambda 上的具体流程,请参考 repository 中的 [README](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/README.md) 教程。 + +> 模板 GitHub repo:[https://github.com/second-state/aws-lambda-wasm-runtime](https://github.com/second-state/aws-lambda-wasm-runtime) + +### 创建函数 + +模板 repo 是一个标准的 Next.js 应用程序。后端 serverless 函数是在 `api/functions/image_grayscale` 文件夹。 `src/main.rs` 文件包含 Rust 程序的源代码。 该 Rust 程序从 `STDIN` 读取数据,然后输出黑白图片到 `STDOUT。` + +``` +use hex; +use std::io::{self, Read}; +use image::{ImageOutputFormat, ImageFormat}; + +fn main() { + let mut buf = Vec::new(); + io::stdin().read_to_end(&mut buf).unwrap(); + + let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap(); + let img = image::load_from_memory(&buf).unwrap(); + let filtered = img.grayscale(); + let mut buf = vec![]; + match image_format_detected { + ImageFormat::Gif => { + filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap(); + }, + _ => { + filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap(); + }, + }; + io::stdout().write_all(&buf).unwrap(); + io::stdout().flush().unwrap(); +} + +``` + +可以使用 Rust 的 `cargo` 工具将 Rust 程序构建为 WebAssembly 字节码或者本机代码。 + +``` +$ cd api/functions/image-grayscale/ +$ cargo build --release --target wasm32-wasi + +``` + +将 build artifact 复制到 `api 文件夹。` + +``` +$ cp target/wasm32-wasi/release/grayscale.wasm ../../ + +``` + +> 当我们构建 docker 镜像时,会执行 `api/pre.sh`。 `pre.sh` 安装 WasmEdge runtime,然后将每个 WebAssembly 字节码程序编译为原生 `so` 库以加快执行速度。 + +### 创建服务脚本,加载函数 + +[`api/hello.js`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/main/api/hello.js) 脚本加载 WasmEdge runtime,在 WasmEdge 中启动编译了的 WebAssembly 程序,并将已上传的图片数据通过 `STDIN`传递。 注意 [`api/hello.js`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/main/api/hello.js) 运行已编译的由 [`api/pre.sh`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/main/api/pre.sh) 产生的 `grayscale.so` 文件,以达到更佳的性能。 + +``` +const { spawn } = require('child_process'); +const path = require('path'); + +function _runWasm(reqBody) { + return new Promise(resolve => { + const wasmedge = spawn(path.join(__dirname, 'wasmedge'), [path.join(__dirname, 'grayscale.so')]); + + let d = []; + wasmedge.stdout.on('data', (data) => { + d.push(data); + }); + + wasmedge.on('close', (code) => { + let buf = Buffer.concat(d); + resolve(buf); + }); + + wasmedge.stdin.write(reqBody); + wasmedge.stdin.end(''); + }); +} + +``` + +`hello.js`的 `exports.handler` 部分导出一个异步函数处理程序,用于每次调用 serverless 函数时处理不同的事件。 在这个例子中,我们只是通过调用上面的函数来处理图像并返回结果,但你可以根据需要定义更复杂的事件处理行为。 我们还需要返回一些 `Access-Control-Allow` header 以避免在从浏览器调用 servereless 时发生跨域资源共享 [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 错误。 如果你在复制我们的示例时遇到 CORS 错误,你可以在[此处](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors)查看更多有关 CORS 错误的信息。 + +``` +exports.handler = async function(event, context) { + var typedArray = new Uint8Array(event.body.match(/[\da-f]{2}/gi).map(function (h) { + return parseInt(h, 16); + })); + let buf = await _runWasm(typedArray); + return { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT" + }, + body: buf.toString('hex') + }; +} + +``` + +### 构建 Docker 镜像用于 Lambda 部署 + +现在我们有了 WebAssembly 字节码函数和脚本来加载和连接到 Web 请求。 为了将它们部署为 AWS Lambda 上的函数服务,仍然需要将整个内容打包到 Docker 镜像中。 + +我们不会详细介绍如何构建 Docker 镜像并在 AWS Lambda 上部署,你可以参考 [README 中的 deploy 部分](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/README.md#deploy) 。 但是,我们将突出显示 [`Dockerfile`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/api/Dockerfile) 中的一部分,以避免一些陷阱。 + +``` +FROM public.ecr.aws/lambda/nodejs:14 + +# Change directory to /var/task +WORKDIR /var/task + +RUN yum update -y && yum install -y curl tar gzip + +# Bundle and pre-compile the wasm files +COPY *.wasm ./ +COPY pre.sh ./ +RUN chmod +x pre.sh +RUN ./pre.sh + +# Bundle the JS files +COPY *.js ./ + +CMD [ "hello.handler" ] + +``` + +首先,我们从 [AWS Lambda 的 Node.js 基础镜像](https://hub.docker.com/r/amazon/aws-lambda-nodejs) 构建镜像。使用 AWS Lambda 基础镜像的优势在于它包含了 [Lambda Runtime 接口客户端 (RIC)](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client),当我们在 AWS Lambda 部署 Docker 镜像时需要这个。 Amazon Linux 使用 `yum` 作为包管理器。 + +> 这些基本镜像包含 Amazon Linux Base 操作系统、给定语言的 runtime、依赖项和 Lambda runtime 接口客户端 (RIC),它实现 Lambda [Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html)。 Lambda [Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html) 客户端允许你的 runtime 从 Lambda 服务接收请求并向其发送请求。 + +其次,我们需要将我们的函数及其所有依赖项放在 `/var/task` 目录中。 AWS Lambda 不会执行其他文件夹中的文件。 + +第三,我们需要在启动容器时定义默认命令。 `CMD [ "hello.handler" ]` 意味着只要调用 serverless 函数,我们就会调用 `hello.js` 中的 `handler` 函数。回想一下,我们在前面的步骤中通过 `hello.js` 中的 `exports.handler = ...` 定义并导出了 handler 函数。 + +### 可选:在本地测试 Docker 镜像 + +你可以按照 AWS 给出的[指南](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html)在本地测试从 AWS Lambda 的基础镜像中构建的 Docker 镜像。 本地测试需要 [AWS Lambda Runtime Interface Emulator (RIE)](https://github.com/aws/aws-lambda-runtime-interface-emulator) ,它已经安装在所有 AWS Lambda 的基础镜像中。 要测试你的镜像,首先,通过运行以下命令启动 Docker 容器: + +``` +docker run -p 9000:8080 myfunction:latest + +``` + +该命令在你的本地机器设置了一个函数端点 `http://localhost:9000/2015-03-31/functions/function/invocations`. + +然后从一个独立的终端窗口,运行: + +``` +curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' + +``` + +你应在终端中获得预期的输出。 + +如果你不想使用来自 AWS Lambda 的基础镜像,你也可以使用自己的基础镜像并在构建 Docker 镜像时安装 RIC 和/或 RIE。 只需按照 AWS 给出的[指南](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html),从替代基础镜像部分创建镜像即可。 + +就是这样! 构建 Docker 镜像后,你可以按照 repo 中的 [README](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/README.md#deploy) 概述的步骤将其解压到 AWS Lambda 。 现在,你的serverless 函数已准备就绪!让我们看看第二个高难度的函数 + +## 案例2: AI推理 + +[第二个 demo](https://robnanarivo.github.io/aws-lambda-wasm-runtime/) 应用程序是让用户上传图片,然后触发一个 serverless 函数对图片上的主要物品进行识别。 + +![](https://oscimg.oschina.net/oscnet/up-2fa3634d068885d957d400117eb3c0f6560.gif) + +它与上一个示例位于同一 [GitHub repo](https://github.com/second-state/aws-lambda-wasm-runtime/tree/tensorflow) 中,但位于 tensorflow 分支中。 用于图像分类的后端 serverless 函数位于 `tensorflow` 分支的 `api/functions/image-classification` 文件夹中。 `src/main.rs`文件包含 Rust 程序的源代码。 Rust 程序从 `STDIN` 读取图像数据,然后将脚本输出输出到 `STDOUT`。 它利用 WasmEdge Tensorflow API 来运行 AI 推理。 + +> AI 推理模板:[https://github.com/second-state/aws-lambda-wasm-runtime/tree/tensorflow](https://github.com/second-state/aws-lambda-wasm-runtime/tree/tensorflow) + +``` +pub fn main() { + // Step 1: Load the TFLite model + let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite"); + let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt"); + + // Step 2: Read image from STDIN + let mut buf = Vec::new(); + io::stdin().read_to_end(&mut buf).unwrap(); + + // Step 3: Resize the input image for the tensorflow model + let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224); + + // Step 4: AI inference + let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite); + session.add_input("input", &flat_img, &[1, 224, 224, 3]) + .run(); + let res_vec: Vec = session.get_output("MobilenetV1/Predictions/Reshape_1"); + + // Step 5: Find the food label that responds to the highest probability in res_vec + // ... ... + let mut label_lines = labels.lines(); + for _i in 0..max_index { + label_lines.next(); + } + + // Step 6: Generate the output text + let class_name = label_lines.next().unwrap().to_string(); + if max_value > 50 { + println!("It {} a {} in the picture", confidence.to_string(), class_name, class_name); + } else { + println!("It does not appears to be any food item in the picture."); + } +} + +``` + +你可以使用 `cargo` 工具构建 Rust 程序为 WebAssembly 字节码或本机代码。 + +``` +$ cd api/functions/image-classification/ +$ cargo build --release --target wasm32-wasi + +``` + +将 build artifacts 复制到 `api` 文件夹中。 + +``` +$ cp target/wasm32-wasi/release/classify.wasm ../../ + +``` + +同样,`api/pre.sh` 脚本会在此应用程序中安装 WasmEdge runtime 及其 Tensorflow 依赖项。 它还在部署时将 `classify.wasm` 字节码程序编译为 `classify.so` 原生共享库。 + +[`api/hello.js`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/api/hello.js) 脚本加载 WasmEdge runtime,在 WasmEdge 中启动已编译的 WebAssembly 程序 , 并通过 `STDIN` 传递上传的图像数据。 注意 [`api/hello.js`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/api/hello.js) 运行 [`api/pre.sh`](https://github.com/second-state/aws-lambda-wasm-runtime/blob/tensorflow/api/pre.sh) 生成的已编译的 `classify.so`文件以获得更好的性能。 Handler 函数和我们前面的例子类似,这里不再详述了。 + +``` +const { spawn } = require('child_process'); +const path = require('path'); + +function _runWasm(reqBody) { + return new Promise(resolve => { + const wasmedge = spawn( + path.join(__dirname, 'wasmedge-tensorflow-lite'), + [path.join(__dirname, 'classify.so')], + {env: {'LD_LIBRARY_PATH': __dirname}} + ); + + let d = []; + wasmedge.stdout.on('data', (data) => { + d.push(data); + }); + + wasmedge.on('close', (code) => { + resolve(d.join('')); + }); + + wasmedge.stdin.write(reqBody); + wasmedge.stdin.end(''); + }); +} + +exports.handler = ... // _runWasm(reqBody) is called in the handler + +``` + +你可以按照上一个示例中讲述的方式构建 Docker 镜像并部署该函数。 现在你已经创建了一个用于主题分类的 Web 应用程序! + +## 展望未来 + +从部署在 AWS Lambda 上的 Docker 容器运行 WasmEdge 是一种向 Web 应用程序添加高性能函数的简单方法。 展望未来,更好的方法是使用[WasmEdge](https://www.computer.org/csdl/magazine/so/5555/01/09214403/1nHNGfu2Ypi)作为容器本身。 这样就无需 Docker 和 Node.js 来装 WasmEdge。这样一来,我们运行 serverless 函数的效率就更高了。 WasmEdge [已经与 Docker 工具兼容](https://www.secondstate.io/articles/manage-webassembly-apps-in-wasmedge-using-docker-tools/)。 如果你有兴趣加入 WasmEdge 和 CNCF 一起进行这项激动人心的工作,[请告诉我们](https://github.com/WasmEdge/WasmEdge#contact)! \ No newline at end of file diff --git a/src/chapter_8/bianlifeng_embedded_rust.md b/src/chapter_8/bianlifeng_embedded_rust.md index 43b9f8cd..73418730 100644 --- a/src/chapter_8/bianlifeng_embedded_rust.md +++ b/src/chapter_8/bianlifeng_embedded_rust.md @@ -299,8 +299,7 @@ Rust 非常适合此类门店嵌入式场景,其完整易用的工具链、高 最后,便利蜂正在寻找优秀的伙伴,每一份简历我们都会认真对待,期待遇见。 -- 邮箱地址:[tech-hiring@bianlifeng.com](tech-hiring@bianlifeng.com) -- 邮件标题:运维 +- 邮箱地址:[zhen.pei@bianlifeng.com](zhen.pei@bianlifeng.com) ## 招聘官网 diff --git a/src/chapter_8/events.md b/src/chapter_8/events.md index 45af481d..8caeb389 100644 --- a/src/chapter_8/events.md +++ b/src/chapter_8/events.md @@ -54,6 +54,7 @@ - [https://www.bilibili.com/video/BV1Gv411N7Z7](https://www.bilibili.com/video/BV1Gv411N7Z7) + ---
🔥🔥🔥🔥 RustFriday 飞书群线上沙龙 🔥🔥🔥🔥
@@ -102,6 +103,29 @@ Rust 中文社群 飞书群 邀请你加入: - [https://www.bilibili.com/video/BV1Kb4y16742](https://www.bilibili.com/video/BV1Kb4y16742) + +## 第十八期 | 如何用 Rust 实现 RPC 框架 + +分享者:张汉东 + +【讨论主题】 + +1. 教程作者 茌海 分享了 Lust 框架的思路。为什么要用rust呢?因为go 服务的性能已经到了存量优化的一个瓶颈,不得不考虑使用rust重新实现从根本上消除go系统所带来的问题。 +2. 大家一起跟随教程学习 如果构建一个 rpc 框架。 该教程虽然比较简短,但其实内容很丰富,并且融合了很多生产实践的思考。 + - 从第二章到第三章是介绍如果抽象消息和协议, + - 第四章和第五章,介绍如何结合tokio codec 和 transport 来进一步整合消息和协议,以及使用tower 增加中间件支持,复用tokio生态里的工具。 + - 第六章 则介绍了如何使用nom来解析 thrift idl,以及通过过程宏来自动生成代码。 + - 第七和第八章则进一步基于tower来增加服务发现/负债均衡/自定义中间件等功能。麻雀虽小,五脏俱全,非常值得学习。 + +【参考资料】 + + [https://github.com/mini-lust/tutorials](https://github.com/mini-lust/tutorials) + +【回放】 + +- [https://www.bilibili.com/video/BV1Pg411V7FM/](https://www.bilibili.com/video/BV1Pg411V7FM/) + + ---
🔥🔥🔥🔥 北京-杭州MeetUp-20210808 🔥🔥🔥🔥
diff --git a/src/chapter_8/frameworks.md b/src/chapter_8/frameworks.md index 85fbb964..374c528f 100644 --- a/src/chapter_8/frameworks.md +++ b/src/chapter_8/frameworks.md @@ -1 +1,77 @@ # 推荐项目 | 框架引擎 + +编辑: 张汉东 + +--- + +## Poem是一个基于tokio/hyper的WEB服务端框架 + +以下为作者自述: + +为什么要做一个新的web框架: + +- actix-web目前仅维护状态,和tokio1兼容的新版本始终出不来(就算出来和其它生态结合也有问题,因为用得单线程runtime) +- tide也和 actix-web 类似 +- warp太复杂,就算是一个rust老手也会经常被整的死去活来 +- axum目前比较火,但是在我深入研究之后,发现也存在和warp同样的问题。 +- 在和社区的朋友聊axum的过程中,发现大家都不太玩得明白,我突然就想做一个用起来简单点的。 + +Poem简单在哪里: + +warp 复杂在于大量的泛型造成代码难以理解,甚至连IDE都无法正确识别类型造成编码的困难。 + +但简单不代表没有泛型,而是去掉一些不必要的泛型。 + +Poem在对性能影响不大的地方尽量减少泛型的使用,定义IDE友好,容易理解的API。 + +Poem的当前状态: + +完全覆盖warp的功能,API已经基本稳定。 + +Poem的后续目标 + +- 更完善的文档以及使用手册。 +- 覆盖更全面的测试用例。 +- 提供更多开箱即用的功能。 +- 内置openapi(swagger)的支持。 + +感谢: + +感谢张汉东提供的Poem注册名,小丁制作的网站,以及社区各位朋友提供的意见和PR。 + +[https://github.com/poem-web/poem](https://github.com/poem-web/poem) + +## pgx : 方便用 Rust 扩展 PostgreSQL 的框架 + +pgx 是一个在 Rust 中开发 PostgreSQL 扩展的框架,并力求尽可能地惯用和安全。 + +特点: + +- 通过一系列 cargo 子命令来管理开放环境。 +- 支持 Postgres v10、v11、v12 和 v13。 +- 使用Rust features 来使用特定版本的API。 +- 自动生成 Schema。 +- 为常见的SQL对象生成DDL。 +- 安全第一:将Rust的恐慌转化为Postgres的ERROR,中止事务,而不是中止进程。 + +[https://github.com/zombodb/pgx](https://github.com/zombodb/pgx) + + +## rg3d 游戏引擎发布v0.22 - 同时发布了一个展示版本特色的视屏 + +rg3d 最近发布了 0.22 版,作者在 Reddit 发布了一段 rg3d 的演示视频,看起来非常棒!可能是目前最成熟的一个 Rust 3D 游戏引擎了。 + +视频:[https://www.youtube.com/watch?v=N8kmZ9aBtZs](https://www.youtube.com/watch?v=N8kmZ9aBtZs) + +[https://github.com/rg3dengine/rg3d](https://github.com/rg3dengine/rg3d) + + +## Rust Search Extension 1.3 发布 + +小编的 Rust 插件发布新版本半个多月了,一直忘了发帖了,今天补上。欢迎大家体验! + +更新说明:https://github.com/huhu/rust-search-extension/wiki/V1.3.0-Release-Candidate-(zh_CN) + +Changelog: https://rust.extension.sh/changelog + + diff --git a/src/chapter_8/gui-framework-ingredients.md b/src/chapter_8/gui-framework-ingredients.md index a8654783..f6604f83 100644 --- a/src/chapter_8/gui-framework-ingredients.md +++ b/src/chapter_8/gui-framework-ingredients.md @@ -1,6 +1,6 @@ -# 您想要编写一个 GUI 框架吗? +# 想用 Rust 编写 GUI 框架吗? -> 原文链接:https://www.cmyr.net/blog/gui-framework-ingredients.html +> 原文链接:[https://www.cmyr.net/blog/gui-framework-ingredients.html](https://www.cmyr.net/blog/gui-framework-ingredients.html) 通过最近关于 Rust 中 GUI 编程的几次讨论,我留下了这样的印象:“GUI”这个词对不同的人来说意味着截然不同的东西。 diff --git a/src/chapter_8/hots.md b/src/chapter_8/hots.md index 61e19c85..d65b278a 100644 --- a/src/chapter_8/hots.md +++ b/src/chapter_8/hots.md @@ -1 +1,72 @@ # 社区热点 + +编辑:张汉东 + +--- + +## RustChinaConf 2021 正式启动 + +好消息,2021 Rust China Conf 要来了! + +本次大会初步定于2021年10月16、17日在上海市徐汇区云锦路701号西岸智塔AI Tower 45层 举行。 + +详情:[RustChinaConf 2021 正式启动](./rust_china_conf_2021.md) + +RustChinaConf 2021 议题开放申请[https://shimo.im/forms/xqpwpdXw6YxrJTj9/fill](https://shimo.im/forms/xqpwpdXw6YxrJTj9/fill) + +## Rust 毫无意外地,连续六年成为了 Stackoverflow 榜单最受喜爱语言。 + +[https://insights.stackoverflow.com/survey/2021#technology-most-loved-dreaded-and-wanted](https://insights.stackoverflow.com/survey/2021#technology-most-loved-dreaded-and-wanted) + +## RIIR (Rewrite it in Rust ) Rome ! + +其官网写道: + +> Rome 是一家工具开发公司,为 JavaScript 和 Web 开发构建了第一个一体化工具。 我们希望让产品开发人员专注于构建他们的产品,而不是为其提供工具和配置。 我们已收到 450 万美元的资金,并致力于开源社区。我们正在壮大我们的团队,以从头开始用 Rust 重写 Roma,并为我们未来的所有工作奠定基础。 + +- [https://rome-tools-inc.breezy.hr/](https://rome-tools-inc.breezy.hr/) +- [https://rome.tools/](https://rome.tools/) + +## Bevy 发布一周年 + +其中作者如图写道。 + +在微软上班一个月目测 至少 1w6 美刀/月, 但是这哥们现在准备考虑以开源为职业目标了,4000刀/月甚至可以让他存一点钱了,这就是真爱。 + +https://bevyengine.org/news/bevys-first-birthday/ + +## Next.js 11.1 最新版发布 + +亮点:使用 基于 Rust 的工具 swc 替代了 babel和terser。 + +将 swc 作者也招募到团队里了。 + +[https://nextjs.org/blog/next-11-1](https://nextjs.org/blog/next-11-1) + +## 第一个非官方的 Rust 编程语言游戏Jam + +这个Jam更侧重于使用 Rust 而不是其他任何东西。 这意味着您不受设计、音乐或图形的限制,只要您使用 Rust 来制作它! + +[https://itch.io/jam/rusty-jam](https://itch.io/jam/rusty-jam) + +## Rust 云原生组织(github 组织)成立 + +[Rust 云原生组织(https://rust-cloud-native.github.io/)]成立,用于推动 Rust 在云原生领域的生态发展。 + +其实 Rust 生态中已经有一些有关云原生的项目了: + +- Bottlerocket OS +- Cloud Native Rust Day +- Firecracker +- kube-rs +- Kubewarden +- Krustlet +- Rust Foundation +- TiKV + +这个组织的存在是为了使Rust在 "云 "中的使用成为可能,其重点是基础设施软件和相关组件。你可能对 "云 "技术很熟悉,比如Docker和Kubernetes。该组织的存在是为了促进存在于同一技术领域的项目。 + +这份博客通告,只是一种呼吁。由于目前还没有一个关于云原生Rust的中心位置,组织者主要是想启动一些东西。 + +详情请参考:[https://nickgerace.dev/post/launching-rust-cloud-native](https://nickgerace.dev/post/launching-rust-cloud-native) + diff --git a/src/chapter_8/hw-rust-simd.md b/src/chapter_8/hw-rust-simd.md index 502c8fc9..54d8ea1f 100644 --- a/src/chapter_8/hw-rust-simd.md +++ b/src/chapter_8/hw-rust-simd.md @@ -1 +1,295 @@ # 华为 | Rust语言中SIMD计算加速指令的使用 + +作者:李原 + +--- + +## 一. SIMD简介 + +SIMD全称Single Instruction Multiple Data,即单指令多数据流,是一种基于特定CPU指令集的计算性能优化技术。顾名思义,指的是在一条CPU指令的执行期间,可以同时进行多条数据的计算。使得在科学计算、多媒体应用等数据密集型运算场景下达到数倍乃至数十倍的性能提升。 + +## 二. Rust语言官方SIMD加速库介绍 + +Rust语言是一种可以选择不同编译后端的编程语言,当然,目前业界绝大部分Rust项目都是以编译器默认选择的llvm作为编译后端。值得注意的是,llvm本身已经集成了绝大部分主流CPU架构的包含SIMD在内的各类指令集。这为Rust语言使用SIMD提供了天然的方便,因为Rust可以在编译器甚至用户代码中以静态链接的形式直接使用llvm提供的SIMD函数接口,而不用像Go、C等语言一样由开发者自己编写汇编代码。 + +Rust语言在官方github项目群中提供了两个simd加速库:[stdarch](https://github.com/rust-lang/stdarch)及[stdsimd](https://github.com/rust-lang/stdsimd)。这里给出了它们的github仓库地址。stdarch以模块化的方式为每种不同的CPU架构提供了各自的专用simd加速指令集,比如x86架构的AVX、AVX512、SSE、SSE2等指令集;ARM/Aarch64平台的NEON、SVE指令集;以及RISCV、WASM等架构的simd指令集等,使用时用户必须对自己所使用的CPU架构及该架构所提供的simd指令集功能有所了解。而stdsimd则是提供了大量各平台通用的抽象simd函数接口,如向量加减乘除、位移、类型转换等。读者不必对自己所使用的硬件架构以及指令集有所了解,使用起来相对更方便,但在使用功能上会有所限制。这两个项目在功能设计的出发点上有所不同,而且各自项目的维护者也有所不同。下面会具体介绍他们的设计以及使用。 + +## 三. 多架构通用加速库stdsimd的使用 + +stdsimd提供了各平台通用的simd加速接口,其实现依赖于Rust编译器所提供的platform-intrinsic接口集合。该接口集合又是对llvm所提供的各平台指令集的封装,因此它们之间的关系应该是: + +**stdsimd** —封装→ **Rust编译器** —封装→ **llvm** + +stdsimd项目因功能尚未集成完全,目前尚未集成到Rust标准库中,读者可以通过将源码克隆到自己的项目中进行使用,或者使用stdsimd的社区版本[packed_simd](https://crates.io/crates/packed_simd_2)(在Cargo.toml文件中加入`packed_simd = { version = "0.3.4", package = "packed_simd_2" }`)。下面的使用也基于社区版本进行介绍。 + +packed_simd项目提供了一系列向量数据类型Simd<[T; N]>,即由N个T元素组成的向量,并为他们提供了简单易懂的的类型别名,比如f32x4类型,就代表着Simd<[f32; 4]>。packed_simd所提供的SIMD加速功能,也都是基于这种向量数据类型所实现的。 + +packed_simd一共提供了以下几种SIMD数据类型(element_width代表数据的大小和数量,比如32x4、64x8): + +- `i{element_width}`: 有符号整数类型 +- `u{element_width}`: 无符号整数类型 +- `f{element_width}`: 浮点数类型 +- `m{element_width}`: bool类型 +- `*{const,mut} T`: 可变或不可变SIMD类型指针 + +默认情况下,对向量结构的操作是“垂直”的,即它们独立于其他向量应用于每个向量通道,比如下面这个例子: + +```rust +let a = i32x4::new(1, 2, 3, 4); +let b = i32x4::new(5, 6, 7, 8); +assert_eq!(a + b, i32x4::new(6, 8, 10, 12)); +``` + +该例子声明了两个i32x4向量,并通过加法运算符重载计算出了它们的和。另一方面,“水平”的操作当然也是有提供的,比如下面的例子: + +```rust +assert_eq!(a.wrapping_sum(), 10); +``` + +总体上,"垂直"的操作总是最快的,而"水平"的操作相对会较慢。也就是说,在计算一个数组的和时,最快的方法是使用多次"垂直"运算加一次"水平"运算,如下所示: + +```rust +fn reduce(x: &[i32]) -> i32 { + assert!(x.len() % 4 == 0); + let mut sum = i32x4::splat(0); // [0, 0, 0, 0] + for i in (0..x.len()).step_by(4) { + sum += i32x4::from_slice_unaligned(&x[i..]); + } + sum.wrapping_sum() +} + +let x = [0, 1, 2, 3, 4, 5, 6, 7]; +assert_eq!(reduce(&x), 28); +``` + +下面再给出一些常见的用例: + +```rust +// 生成元素全为0的i32x4向量: +let a = i32x4::splat(0); + +// 由数组中的前4个元素生成i32x4向量: +let mut arr = [0, 0, 0, 1, 2, 3, 4, 5]; +let b = i32x4::from_slice_unaligned(&arr); + +// 读取向量中的元素: +assert_eq!(b.extract(3), 1); + +// 替换向量中对应位置的元素: +let a = a.replace(3, 1); +assert_eq!(a, b); + +// 将向量写入数组中: +let a = a.replace(2, 1); +a.write_to_slice_unaligned(&mut arr[4..]); +assert_eq!(arr, [0, 0, 0, 1, 0, 0, 1, 1]); +``` + +除此之外,packed_simd还提供了向量的条件运算,比如下面的代码表示根据m中元素是否为真进行向量中对应元素的+1操作: + +```rust +let a = i32x4::new(1, 1, 2, 2); + +// 将a中的前两个元素进行+1操作. +let m = m16x4::new(true, true, false, false); +let a = m.select(a + 1, a); +assert_eq!(a, i32x4::splat(2)); +``` + +由此可以衍生出更灵活的使用方法,比如由两个向量中各个位置的较大值组成新的向量 + +```rust +let a = i32x4::new(1, 1, 3, 3); +let b = i32x4::new(2, 2, 0, 0); + +// ge: 大于等于计算,生成bool元素类型的向量 +let m = a.ge(i32x4::splat(2)); + +if m.any() { + // 根据m中的结果选择a或b中的元素 + let d = m.select(a, b); + assert_eq!(d, i32x4::new(2, 2, 3, 3)); +} +``` + +以上就是stdsimd(packed_simd)的基础使用方法,该项目可以让开发者方便地通过SIMD类型数据结构享受到SIMD加速的效果。但该项目也存在一定的缺陷,比如用户必须手动选择向量的长度。因为大多数CPU架构都至少提供了128位SIMD指令集,因此选择128位的向量长度总是合理的。但当CPU提供了更高级的SIMD指令集(比如AVX512)时,选择更长的指令集会获得更好的效果。因此当开发者拥有一定的CPU架构及SIMD相关的知识储备时,使用起来会有事半功倍的效果。 + +## 四. 专用指令加速库stdarch的使用 + +stdarch已经集成到了Rust语言的标准库中,可以在代码中通过`use std::arch`语句进行使用。这里要注意的是,目前只有x86_64以及x86两种架构已经发布了stable版本,因此其他结构比如arm、aarch64等必须将Rust编译器切换到nightly版本(在命令行输入rustup default nightly命令)方可编译及使用。因此下面主要使用stable版本可用的x86_64(x86)为例进行介绍。 + +stdarch以静态链接的方式封装了诸多llvm所提供的SIMD指令集,并以模块的方式提供了各种主流架构下的SIMD指令集,如下所示。每种架构下可用的SIMD函数接口可以点进相应的链接进行查阅。 + +- [x86](https://docs.rs/core_arch/0.1.5/core_arch/x86/index.html) +- [x86_64](https://docs.rs/core_arch/0.1.5/core_arch/x86_64/index.html) +- [arm](https://docs.rs/core_arch/0.1.5/core_arch/arm/index.html) +- [aarch64](https://docs.rs/core_arch/0.1.5/core_arch/aarch64/index.html) +- [mips](https://docs.rs/core_arch/0.1.5/core_arch/mips/index.html) +- [mips64](https://docs.rs/core_arch/0.1.5/core_arch/mips64/index.html) +- [powerpc](https://docs.rs/core_arch/0.1.5/core_arch/powerpc/index.html) +- [powerpc64](https://docs.rs/core_arch/0.1.5/core_arch/powerpc64/index.html) +- [nvptx](https://docs.rs/core_arch/0.1.5/core_arch/nvptx/index.html) +- [wasm32](https://docs.rs/core_arch/0.1.5/core_arch/wasm32/index.html) + +相比于stdsimd,stdarch对开发者的CPU架构知识储备有着更高的要求。因为stdarch对每个主流的CPU架构都提供了上千个不同功能的SIMD指令,开发者需要手动识别哪一条指令是自己最需要的。 + +比如下面这个例子: + +```rust +#[cfg( + all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "avx2" + ) +)] +fn foo() { + #[cfg(target_arch = "x86")] + use std::arch::x86::_mm256_add_epi64; + #[cfg(target_arch = "x86_64")] + use std::arch::x86_64::_mm256_add_epi64; + + unsafe { + _mm256_add_epi64(...); + } +} +``` + +这段代码首先使用Rust语言原生提供的CPU特征检测功能,即target_arch属性宏,来检测开发环境是否为x86_64或者x86,再使用target_feature属性宏检测avx2指令集是否可用。在以上条件都满足时,才会编译下面的foo函数。而在foo函数内部,则会根据CPU为x86_64还是x86架构选择相应的simd指令。 + +或者开发者可以使用动态的特征检测语句`is_x86_feature_detected!`,如下所示: + +```rust +fn foo() { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + if is_x86_feature_detected!("avx2") { + return unsafe { foo_avx2() }; + } + } + + // return without using AVX2 +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[target_feature(enable = "avx2")] +unsafe fn foo_avx2() { + #[cfg(target_arch = "x86")] + use std::arch::x86::_mm256_add_epi64; + #[cfg(target_arch = "x86_64")] + use std::arch::x86_64::_mm256_add_epi64; + + _mm256_add_epi64(...); +} +``` + +stdarch本身存在着大量类似的条件编译代码。因此相应的指令集模块只有在满足环境的需求时才可用。比如x86_64架构下可以使用`use std::arch::x86_64`语句,却不能使用`use std::arch::x86_64`或者`use std::arch::arm`语句。 + +下面通过一个具体的例子,即16进制编码函数的simd实现来介绍stdarch的具体使用。这个例子中主要使用了x86及x86_64下的SSE4.1指令集。 + +具体的代码实现如下,其中使用到的各类SIMD指令及用途都可以在注释或上文中对应模块(x86或x86_64)的链接文档中进行查阅。 + +```rust +fn main() { + let mut dst = [0; 32]; + hex_encode(b"\x01\x02\x03", &mut dst); + assert_eq!(&dst[..6], b"010203"); + + let mut src = [0; 16]; + for i in 0..16 { + src[i] = (i + 1) as u8; + } + hex_encode(&src, &mut dst); + assert_eq!(&dst, b"0102030405060708090a0b0c0d0e0f10"); +} + +pub fn hex_encode(src: &[u8], dst: &mut [u8]) { + let len = src.len().checked_mul(2).unwrap(); + assert!(dst.len() >= len); + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + if is_x86_feature_detected!("sse4.1") { + return unsafe { hex_encode_sse41(src, dst) }; + } + } + + hex_encode_fallback(src, dst) +} + +#[target_feature(enable = "sse4.1")] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +unsafe fn hex_encode_sse41(mut src: &[u8], dst: &mut [u8]) { + #[cfg(target_arch = "x86")] + use std::arch::x86::*; + #[cfg(target_arch = "x86_64")] + use std::arch::x86_64::*; + + // 生成包含16个int8类型的向量,并将全部值设为字符'0'的ascii编号 + let ascii_zero = _mm_set1_epi8(b'0' as i8); + // 生成包含16个int8类型的向量,并将全部值设为整数9 + let nines = _mm_set1_epi8(9); + // 生成包含16个int8类型的向量,并将全部值设为字符'a'的ascii编号减去10 + let ascii_a = _mm_set1_epi8((b'a' - 9 - 1) as i8); + // 生成包含16个int8类型的向量,并将全部值设为二进制数00001111 + let and4bits = _mm_set1_epi8(0xf); + + let mut i = 0_isize; + while src.len() >= 16 { + // 从指针中读取128位整数,组成一个128位的向量(可以转化为int8x16、int32x4等形式的向量) + let invec = _mm_loadu_si128(src.as_ptr() as *const _); + + // 将该128位向量类型转化为int8x16类型的向量,并将其中每个元素和二进制数00001111进行与操作 + let masked1 = _mm_and_si128(invec, and4bits); + // 将该128位向量类型转化为int8x16类型的向量,再将每个元素逻辑右移4位,随后将其中每个元素和二进制数00001111进行与操作 + let masked2 = _mm_and_si128(_mm_srli_epi64(invec, 4), and4bits); + + // 向量对应元素比较大小,获取向量中所有大于9的元素的位置 + let cmpmask1 = _mm_cmpgt_epi8(masked1, nines); + let cmpmask2 = _mm_cmpgt_epi8(masked2, nines); + + // _mm_blendv_epi8表示生成一个新的向量,该向量中的元素是根据cmpmask1中对应位置是否为true选择ascii_zero或者ascii_a中的元素 + // _mm_add_epi8则表示向量对应位置元素相加,结果表示最终生成的十六进制编码的ascii编号 + let masked1 = _mm_add_epi8( + masked1, + _mm_blendv_epi8(ascii_zero, ascii_a, cmpmask1), + ); + let masked2 = _mm_add_epi8( + masked2, + _mm_blendv_epi8(ascii_zero, ascii_a, cmpmask2), + ); + + // 生成一个新的向量,其中偶数位置元素(从0开始)来自于masked2,奇数位置元素来自于masked1 + // 该向量共有256位,所以将前128位放入res1中,后128位放入res2中 + let res1 = _mm_unpacklo_epi8(masked2, masked1); + let res2 = _mm_unpackhi_epi8(masked2, masked1); + + // 将结果向量写入目标指针中 + _mm_storeu_si128(dst.as_mut_ptr().offset(i * 2) as *mut _, res1); + _mm_storeu_si128( + dst.as_mut_ptr().offset(i * 2 + 16) as *mut _, + res2, + ); + src = &src[16..]; + i += 16; + } + + let i = i as usize; + hex_encode_fallback(src, &mut dst[i * 2..]); +} + +fn hex_encode_fallback(src: &[u8], dst: &mut [u8]) { + fn hex(byte: u8) -> u8 { + static TABLE: &[u8] = b"0123456789abcdef"; + TABLE[byte as usize] + } + + for (byte, slots) in src.iter().zip(dst.chunks_mut(2)) { + slots[0] = hex((*byte >> 4) & 0xf); + slots[1] = hex(*byte & 0xf); + } +} +``` + +此处通过这个具体的例子简单呈现了stdarch中SIMD加速指令的用法。可以看出,专用指令的使用相比于stdsimd来说对开发者的SIMD经验要求高上许多,但提供的功能和适用的场景也会更加完备。 + +以上就是Rust语言中官方SIMD加速库的简单使用介绍,希望能对各位读者的学习开发有所启示和帮助。 diff --git a/src/chapter_8/image/2020conf/1.png b/src/chapter_8/image/2020conf/1.png new file mode 100644 index 00000000..2b6cd412 Binary files /dev/null and b/src/chapter_8/image/2020conf/1.png differ diff --git a/src/chapter_8/image/ant/1.jpeg b/src/chapter_8/image/ant/1.jpeg new file mode 100644 index 00000000..4edaa790 Binary files /dev/null and b/src/chapter_8/image/ant/1.jpeg differ diff --git a/src/chapter_8/jobs.md b/src/chapter_8/jobs.md index c486b134..5f3a571d 100644 --- a/src/chapter_8/jobs.md +++ b/src/chapter_8/jobs.md @@ -1 +1,26 @@ # 本月招聘 + +## 非凸科技:算法开发工程师 + +非凸科技正成为国内金融市场智能投资交易平台的引领者,潜心打造了智能算法交易平台,在原有基础上全面升级到互联网新一代技术架构,结合机器学习等新兴技术,逐步完成各类交易算法的研发迭代,正持续为券商、量化私募等众多大型金融机构提供优质的算法服务。 + +岗位职责 : + +1.设计并开发基于RUST的高性能,低时延算法交易系统; +2.设计并开发数据处理平台,监控运维平台; +3.设计并开发面向客户的高可用交易工具等; +4.设计并开发策略相关的回测平台。 + +岗位要求 : + +1.本科及以上学历(985优先)。编程基础扎实,具 有良好的计算机理论基础; +2.熟练掌握Linux操作,性能分析,具备 Rust/C++/Java/Go丰富开发经验,熟悉常用的设计 模式,有分布式相关经验加分; +3.有研发高性能,低时延系统经验加分; +4.对技术充满热情,思考深入。自我驱动,能快速学 习新鲜事物。 + +联系方式: + +- 联系人:Schiele +- 联系电话:150 0928 5657 +- 地址:上海市漕河泾开发区 凯科国际大厦 + diff --git a/src/chapter_8/lang.md b/src/chapter_8/lang.md index 8098166c..c733db02 100644 --- a/src/chapter_8/lang.md +++ b/src/chapter_8/lang.md @@ -1 +1,143 @@ # 官方动态 + +编辑:张汉东 + +--- + +## 官方 | 来推动 GAT 稳定吧 + +GAT RFC 从2016年启动,到今年已经五年了,现在终于接近稳定状态了。GAT 是 Rust github 仓库里期待率最高的一个问题。 + +现在经过编译器的大量修改,终于让 GAT 达到了一个 「完整」状态,虽然还有一些诊断问题,但现在你在 nightly 下使用 GAT 将不会看到 “generic_associated_types is incomplete”这种错误。 但是现在想 稳定 GAT ,还需要大家来帮助测试此功能,为你发现但任何错误或潜在的诊断改进提出问题。并且官方还希望可以在 GAT 上实现一些有趣的模式。 + +如果不出意外,未来几个月内稳定应该会比较顺利。 + +详细内容请看原文吧,原文也介绍了什么是 GAT 。 + +[https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html) + +## wasm-bindgen 发布新版本 + +虽然官方 Rust WebAssembly 工作组的博客已经停更快两年了,但是实际他们的工作还是在继续的。 + +最近的更新: + +- 升级 webpack 示例中 npm 包的依赖版本 +- 添加 `no_deref` 属性以选择不为导入的类型生成 `deref` 实现 +- 通过非零初始化缓冲区来提高 `TypedArray::to_vec` 性能 +- 升级最新的WebGPU WebIDL + +[https://github.com/rustwasm/wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) + +## Rustdoc 中源码页面中支持跳转到定义功能 + +比如,在标准库文档中源码(src)页面看到类似下面的代码: + +```rust +mod other_module; +struct Foo; +fn bar() {} + +fn x(f: Foo, g: other_module::Whatever, t: &T) { + let f: Foo = Foo; + bar(); + f.some_method(); +} +``` + +其中,`other_module::Trait`, `Foo`, `other_module::Whatever`, `bar` 和 `some_method` 都会出现链接,点击链接可以跳转到其定义页面。 + +如果有来自另一个crate 的类型,它会链接到它的文档页面而不是它的定义(但你可以点击 `[src]`)。 + +[https://github.com/rust-lang/rust/pull/84176](https://github.com/rust-lang/rust/pull/84176) + +## [CVE-2021-29922] Rust 标准库net 模块漏洞: 前导零改变 IP 地址 + +本周,在DEF CON上,安全研究人员Cheng Xu、Victor Viale、Sick Codes、Nick Sahler、Kelly Kaoudis、opennota和John Jackson披露了Go和Rust语言的net模块的一个缺陷。CVE-2021-29922(针对Rust) 和 CVE-2021-29923(针对Golang)。 + +IP地址可以用多种格式表示,包括十六进制和整数,不过最常见的IPv4地址是用十进制格式表示的。 + +例如,BleepingComputer的IPv4地址以十进制格式表示为`104.20.59.209`,但同样的地址也可以以八进制格式表示为:`0150.0024.0073.0321`。 + +假设你得到一个十进制格式的IP地址,`127.0.0.1`,这被广泛理解为本地回环地址或`localhost`。 + +如果你在它前面加上一个0,应用程序是否仍应将0127.0.0.1解析为`127.0.0.1`或其他什么?在Chrome的地址栏中输入`0127.0.0.1`,浏览器会将其视为八进制格式的IP。在按下回车键或返回键时,该IP实际上变成了十进制的`87.0.0.1`,这就是大多数应用程序应该处理这种模糊的IP地址的方式。 + +根据IETF的原始规范,IPv4地址的部分内容如果前缀为 "0",可以解释为八进制。 + +但是Go和Rust中的net模块都忽略了这一点,将部分地址视为十进制。 + +rust 1.52.1 `std::net` 及以下版本中IP地址输入未按八进制处理而导致不确定的 SSRF 和 RFI 漏洞。 + +例如,攻击者向依赖`std::net::IpAddr`的网络程序提交IP地址,可以通过输入位组的输入数据引起 SSRF; + +如果位组(octet)是3位,攻击者可以提交可利用的IP地址,最小可利用的位组是08(拒绝服务),最大可利用的位组是099(拒绝服务)。 + +例如,攻击者可以提交`010.8.8.8`,也就是`8.8.8.8`(RFI),然而`std::net::IpAddr`将计算为`10.8.8.8`。同样,攻击者可以输入127.0.026.1,这实际上是127.0.22.1,但Rust将其计算为127.0.26.1。 + +- SSRF是Server-side Request Forge的缩写,中文翻译为服务端请求伪造。 +- RFI 是Remote File Inclusion的缩写,客户端可控制网页包含远程文件。 + +受影响 Rust 版本: 1.52.1 及以下。 + +该漏洞已于三月份修复: [https://github.com/rust-lang/rust/pull/83652](https://github.com/rust-lang/rust/pull/83652 ) + +PoC 代码: + +```rust +// ##!/usr/bin/env rustc +// # Authors: https://twitter.com/sickcodes, https://twitter.com/kaoudis +// # License: GPLv3+ + +use std::net::IpAddr; + +fn main() { + let addr = "127.026.0.1".parse::().unwrap(); + println!("{}", addr.to_string()); + let addr1 = "127.0.026.1".parse::().unwrap(); + println!("{}", addr1.to_string()); + let addr2 = "127.0.0.093".parse::().unwrap(); + println!("{}", addr2.to_string()); + let addr3 = "099.0.0.01".parse::().unwrap(); + println!("{}", addr3.to_string()); +} + +// $ rustc -o main main.rs +// $ ./main +// 127.26.0.1 +// 127.0.26.1 +// 127.0.0.93 +// 99.0.0.1 +``` + +- [https://github.com/sickcodes/security/blob/master/advisories/SICK-2021-015.md](https://github.com/sickcodes/security/blob/master/advisories/SICK-2021-015.md) +- [https://www.bleepingcomputer.com/news/security/go-rust-net-library-affected-by-critical-ip-address-validation-vulnerability/](https://www.bleepingcomputer.com/news/security/go-rust-net-library-affected-by-critical-ip-address-validation-vulnerability/) + +相关: + +黑客大会 defconf29 演讲之一 : 烂代码、老化标准和 IPv4 解析 + +针对 Rust / Go 前导零改变 IP 地址相关漏洞的演讲 + +[https://www.youtube.com/watch?v=_o1RPJAe4kU](https://www.youtube.com/watch?v=_o1RPJAe4kU) + +## Deprecate llvm_asm! 的 pr 已经被合并了 + +过期 `llvm_asm!`,而用新的 `asm!`来代替。 + +[https://github.com/rust-lang/rust/pull/87590#issuecomment-899111280](https://github.com/rust-lang/rust/pull/87590#issuecomment-899111280) + +## Rust IO Safety RFC 已经被实现 + +这是为操作系统资源增加所有权语义的第一步! + +[https://github.com/rust-lang/rust/pull/87329](https://github.com/rust-lang/rust/pull/87329) + +## Gcc Rust 月报 + +注意:GCC Rust(gccrs) 是给 gcc 增加 Rust 前端,而现在 Rust 后端正在准备合并的是 `rustc_codegen_gcc`,是给 Rust 增加 gcc 后端。 + +两者目标不同。 + +- [https://github.com/Rust-GCC/gccrs](https://github.com/Rust-GCC/gccrs) +- [https://thephilbert.io/2021/08/02/gcc-rust-monthly-report-8-july-2021/](https://thephilbert.io/2021/08/02/gcc-rust-monthly-report-8-july-2021/) \ No newline at end of file diff --git a/src/chapter_8/learn.md b/src/chapter_8/learn.md index e0fd2134..b8298277 100644 --- a/src/chapter_8/learn.md +++ b/src/chapter_8/learn.md @@ -1 +1,185 @@ # 学习资源 + +编辑: 张汉东 + +--- + +## 《Rust In Action》书籍第一版发布 + +作何 Tim McNamara 是一位经验丰富的程序员,对自然语言处理、文本挖掘以及更广泛的机器学习和人工智能形式有着浓厚的兴趣。 他在包括新西兰开源协会在内的开源社区中非常活跃。Rust in Action 是使用 Rust 进行系统编程的实践指南,它是为具有好奇心的程序员编写的,提供了远远超出语法和结构的实际用例。 + +国外最近的Rust的书籍,除了《Rust In Action》还有另外两本,《Refactor to Rust》和 《Rust Servers, Services, and Apps》。 + +国内翻译版也在路上了。 + +[Amazon](https://www.amazon.com/dp/1617294551/ref=cm_sw_r_cp_awdb_imm_VJ4HZ4859SDB7K5B7VQK) + +## 使用 Rust 进行端到端加密 + +《End-to-End Encryption with Rust》是一本`ockam-network/ockam`实践指南, 在本指南中,我们将创建两个名为 Alice 和 Bob 的小型 Rust 程序。 Alice 和 Bob 将通过云服务通过网络相互发送消息。 在我们的代码示例中,Alice 和 Bob 将相互进行身份验证,并将获得加密保证,以确保其消息的完整性、真实性和机密性得到端到端的保护。 + +网络上的中间云服务和攻击者将无法看到或更改途中消息的内容。 在后面的示例中,我们还将看到即使当 Alice 和 Bob 之间的通信路径更复杂 - 具有多个传输连接、各种传输协议和许多中介时,我们如何才能实现这种端到端保护。 + +[https://github.com/ockam-network/ockam/tree/develop/documentation/use-cases/end-to-end-encryption-with-rust#readme](https://github.com/ockam-network/ockam/tree/develop/documentation/use-cases/end-to-end-encryption-with-rust#readme) + +## 两张图展示当前 Rust Web 生态 + +微信: [https://mp.weixin.qq.com/s/eIOMI0JvpOkdmiTqJfWkRg](https://mp.weixin.qq.com/s/eIOMI0JvpOkdmiTqJfWkRg) +知乎: [https://zhuanlan.zhihu.com/p/398232138](https://zhuanlan.zhihu.com/p/398232138) + + +## 创意!用 Rust crate 作为自己的简历 + +如果你觉得学习 Rust 不知道该做些什么好?那不如从做自己简历开始。 + +[https://yozhgoor.github.io/yohan_boogaert_1995/](https://yozhgoor.github.io/yohan_boogaert_1995/) + +## Mini Lust 系列教程: + +好奇如何从零造出来一个 RPC 框架?本教程将带你一步一步写出来一个 Rust 版 Thrift RPC 框架。 + +1.前言部分,RPC 相关概念介绍 +2. Thrift IDL 介绍 +3. 序列化/反序列化的抽象 +4. Codec 和 Transport 抽象 +5. 客户端和服务端实现 +6. Thrift IDL 解析和代码生成 +7. 基于 tower 的服务发现和负载均衡 +8. 中间件支持 + +[https://github.com/mini-lust/tutorials](https://github.com/mini-lust/tutorials) + +## Rust 公开课 | 《 Rust 异步编程二: Tokio 入门运行时介绍》|Vol. 6 + +这节课预计 9.5 号晚上8点,感兴趣的可以去听听。 + +该系列课程大纲 + +1、回顾 Rust 异步编程模型. +2、谈谈对 Rust 异步框架的认识 ( futures-rs、async-std、tokio ) . +3、Tokio 介绍 +4、Tokio 里的 Executor、Reactor、Future 如何使用. +5、使用 Tokio 实现一个简单的服务端与客户端程序. + +[https://mp.weixin.qq.com/s/23YDZdwJNOAu15AIBDnWuQ](https://mp.weixin.qq.com/s/23YDZdwJNOAu15AIBDnWuQ) + +## Clippy 1.54 增加 `disallowed-methods` 配置 + +允许你在 `clippy.toml` 中配置不允许的方法: + +```rust +# clippy.toml +disallowed-methods = ["std::vec::Vec::leak", "std::time::Instant::now"] +``` + +不良代码: + +```rust +// 该代码将要被警告 + +let xs = vec![1, 2, 3, 4]; +xs.leak(); // Vec::leak is disallowed in the config. + +let _now = Instant::now(); // Instant::now is disallowed in the config. +``` + +应该用此代替: + +```rust +// Example code which does not raise clippy warning +let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config. +xs.push(123); // Vec::push is _not_ disallowed in the config. +``` + +## 5000倍速度提升的 CRDT + +CRDT 全称 Conflict-Free Replicated Data types. 主要用于在线合作文档编辑等方面. + +作者详细介绍了如何提升相关实现和算法的一些过程,并且最终使得提升了 5000 倍的速度. + +[https://josephg.com/blog/crdts-go-brrr/](https://josephg.com/blog/crdts-go-brrr/) + +## 如何写出运行缓慢的 Rust 代码 + +用Rust写代码并不意味着你的代码会快得不得了。你很容易犯错并获得相当慢的性能。正如这篇博文所显示的,你甚至可能需要付出相当多的汗水才能打败Common Lisp和Java。 + +作者分享了自己如何使用 Rust 重写自己的 Lisp 代码, 如何成功的写出更慢的代码 并且 修复他们的故事. + +[https://renato.athaydes.com/posts/how-to-write-slow-rust-code.html](https://renato.athaydes.com/posts/how-to-write-slow-rust-code.html) + +## RustCast: Rust 系列教学视频 + +一系列 Rust 学习系列视频,希望能坚持下去。 + +[https://www.youtube.com/channel/UCZSy_LFJOtOPPcsE64KxDkw](https://www.youtube.com/channel/UCZSy_LFJOtOPPcsE64KxDkw) + +## 用Rust重写我的手机游戏,并且编译到 wasm + +作者的游戏之前是用 C++ 写的。这篇文章详细记录了他决心使用rust重写的心路历程和一些idea的发展。 + +推荐阅读: + +[https://itnext.io/rewriting-my-mobile-game-in-rust-targeting-wasm-1f9f82751830](https://itnext.io/rewriting-my-mobile-game-in-rust-targeting-wasm-1f9f82751830) + +## 使用 Rust 从头开始​​实现 Base64 + +文章仔细研究 Base64 算法,并使用 Rust 编程语言从头开始实现编码器和解码器。 + +[https://dev.to/tiemen/implementing-base64-from-scratch-in-rust-kb1](https://dev.to/tiemen/implementing-base64-from-scratch-in-rust-kb1) + +## Async Rust 从头开始​​:一个简单的 Web 服务器 + +[https://ibraheem.ca/writings/a-simple-web-server/](https://ibraheem.ca/writings/a-simple-web-server/) + +## 一个网络应用程序,可以学习使用 AI(遗传算法)构建车辆,使用Rust编写 + +它在你的浏览器中运行,使用人工智能(具体来说:遗传算法)来尝试制造越来越好的车辆。车辆必须克服障碍路线,从一些小山坡开始,然后是陡峭的山坡,最后是一些跳跃。车辆由面板和轮子制成,连接在一起,类似于Besiege游戏。 + +[https://github.com/Bauxitedev/vehicle_evolver_deluxe](https://github.com/Bauxitedev/vehicle_evolver_deluxe) + +## 当零成本抽象不再是零成本 +Rust 是围绕着“零成本抽象”的概念构建的。其理念是,您可以编写人机友好的高级代码,而编译器将为您提供至少与您自己编写的任何优化的低级别代码一样好的性能。使用零成本抽象,您不再需要在可维护性和性能之间进行权衡。 + +不幸的是,很难确保零成本抽象是真正的零成本,并且在实践中Rust经常不能满足这个崇高的理想。在这篇文章中,我将展示两个例子,在这两个例子中,即使看似简单的零成本抽象实际上也不是零成本。 + +[https://blog.polybdenum.com/2021/08/09/when-zero-cost-abstractions-aren-t-zero-cost.html](https://blog.polybdenum.com/2021/08/09/when-zero-cost-abstractions-aren-t-zero-cost.html) + +## 【系列】Rust 每周一模块 + +这是一个系列博客,目前只发了两篇文章,每周讲一个模块: + +比如第二周:Rust 标准库中`std::fs`模块 + +`std::fs` 是Rust标准库中操作文件系统的模块,包括创建、读取、更新、删除等常见操作。由于不同操作系统支持的API不尽相同,本文仅展示了与平台无关的一些例子: + +- 通过修改时间(mtime)来聚合相同年份、月份乃至日期的文件; +- 硬链接(hard link)一个路径至另一个路径; +- 递归创建目录; +- 递归删除文件夹; +- 拷贝文件; + +[https://motw.rs/](https://motw.rs/) + +## 【书籍】Black Hat Rust 早期访问版 + +Black Hat Rust 是一本深入研究使用 Rust 编程语言的进攻性安全(Offensive Security)的书籍,支持PDF,Kindle 和 Epub。 + +这本书是一项正在进行的工作。它可以在早期访问计划的背景下使用,这意味着各章节将在写完后立即发送给你,我们非常感谢你的反馈。当前状态: + +可访问页数:250+ 代码进度:~90% [https://github.com/skerkour/black-hat-rust](https://github.com/skerkour/black-hat-rust) 预计最终出版:Q3 2021 估计的页数:~320 + +备注:作者为感谢所有帮助其完成这本书的人,所有早期访问的买家还将获得以下奖励:一个高级恶意软件分析的策划清单。在开发自己的攻击性工具时,会在里面找到巨大的灵感。 + +[https://academy.kerkour.com/black-hat-rust?coupon=BLOG](https://academy.kerkour.com/black-hat-rust?coupon=BLOG) + +## 如何写出高效的 Rust 代码 + +该文作者对如何写出高效 Rust 代码给出了一些建议,内容还比较长,感兴趣可以看看。 + +[https://renato.athaydes.com/posts/how-to-write-fast-rust-code.html](https://renato.athaydes.com/posts/how-to-write-fast-rust-code.html) + +## 理解 `#[derive(Clone)]` 宏 + +你可能不知道这个宏背后发生的事,这篇文章带你探索一下。 + +[https://stegosaurusdormant.com/understanding-derive-clone/](https://stegosaurusdormant.com/understanding-derive-clone/) \ No newline at end of file diff --git a/src/chapter_8/paper-rudra.md b/src/chapter_8/paper-rudra.md index 0901ef27..2bcc653b 100644 --- a/src/chapter_8/paper-rudra.md +++ b/src/chapter_8/paper-rudra.md @@ -1 +1,340 @@ # 论文导读 | Rudra : 查找 Rust 生态系统中的内存安全 Bug + +作者:张汉东 + +--- + +## 引子 + +[美国佐治亚理工学院的系统软件安全实验室](https://github.com/sslab-gatech)开源了[`Rudra`](https://github.com/sslab-gatech/Rudra) ,用于分析和报告 Unsafe Rust 代码中潜在的内存安全和漏洞,为此他们也将在 2021 年第 28 届 ACM 操作系统原则研讨会论文集上发表相关论文,该论文目前在 Rudra 源码仓库中提供[下载](https://github.com/sslab-gatech/Rudra-Artifacts/raw/master/paper/sosp21-paper341.pdf)。 + +说明:本篇文章不是论文的翻译,而是本人对该论文的梳理和总结。 + +## 概要 + +Rust 语言关注内存安全和性能,Rust 目前已经在传统的系统软件中得到了广泛的应用,如操作系统、嵌入式系统、网络框架、浏览器等,在这些领域,安全和性能都是不可或缺的。 + +Rust 内存安全的思想是在编译时验证内存的所有权,具体而言是验证内存分配对象的访问和生存期。Rust 编译器对值的共享和独占引用通过借用检查提供两个保证: + +1. 引用的生存期不能长于其拥有者变量的生存期。为了避免 use-after-free (UAF) 。 +2. 共享和独占引用不能同时存在,排除了并发读写同一个值的风险。 + +不幸的是,这些安全规则太过限制。在某些需要调用底层硬件系统,或需要获得更好性能时,需要暂时绕过安全规则。这些需求无法被 Safe Rust 解决,但是对于系统开发却是必不可少的,所以 Unsafe Rust 被引入。Unsafe Rust 意味着,编译器的安全检查职责被暂时委托给了程序员。 + +Unsafe Rust代码的健全性(soundness )对于整个程序的内存安全是至关重要的,因为大多数系统软件,如操作系统或标准库,都离不开它。 + +有些人可能比较天真地以为,Unsafe Rust 只要在审查源码的时候就可以排除它的风险。然而,问题的关键在于,健全性的推理是非常微妙的,且很容易出错,原因有三: + +1. 健全性的错误会顺道破坏Rust的安全边界,这意味着所有的外部代码,包括标准库都应该是健全的。 +2. Safe 和 Unsafe 的代码是相互依赖的。 +3. 编译器插入的所有不可见的代码路径都需要由程序员正确推理。 + +为了让 Rust 有一个健全性的基础,已经有了很多研究型项目,比如形式化类型系统和操作语义,验证其正确性,并且建立模型用于检查。这些都是非常重要的,但还不够实用,因为它没有覆盖到整个生态系统。另外还有一些动态方法,比如 Miri 和 Fuzz 模糊测试,但是这些方法不太容易被大规模使用,因为它需要大量的计算资源。 + +当前,Rust 语言正在变得流行,Unsafe Rust 的包也逐渐变多。因此,设计一个实用的检测内存安全的算法就很重要了。 + +这篇论文介绍了三种重要的Bug模式,并介绍了 Unsafe 代码,以及提供 Rudra 这样的工具。该论文作者的工作一共有三个贡献: + +1. 确定了三种 Unsafe Rust 中的 Bug 模式,并且设计了两种新的算法可以发现它们。 +2. 使用 Rudra 在Rust 生态系统中发现263个新的内存安全漏洞。这代表了自2016年以来RustSec中所有bug的41.4%。 +3. 开源。Rudra 是开源的,我们计划 贡献其核心算法到官方的Rust linter中。 + +## Rudra + +[`Rudra`](https://github.com/sslab-gatech/Rudra) 用于分析和报告Unsafe Rust 代码中潜在的内存安全漏洞。 由于Unsafe 代码中的错误威胁到 Rust 安全保证的基础,`Rudra` 的主要重点是将我们的分析扩展到 Rust 包注册仓库(比如 `crates.io`)中托管的所有程序和库。`Rudra` 可以在 6.5 小时内扫描整个注册仓库(`43k` 包)并识别出 263 个以前未知的内存安全漏洞,提交 98 个 `RustSec` 公告和 74 个` CVE`,占自 2016 年以来报告给 `RustSec` 的所有漏洞的 41.4%。 + +![rudra](D:\Rust 工作报告\文档报告\Rust\image\rudra.PNG) + +`Rudra` 发现的新漏洞很微妙,它们存在于Rust 专家的库中:两个在 `std` 库中,一个在官方 `futures` 库中,一个在 Rust 编译器 `rustc` 中。 `Rudra` 已经开源, 并计划将其算法集成到官方 Rust linter 中。 + +> Rudra, 这个名称来自于 梵文,译为鲁特罗(或楼陀罗),印度神话中司风暴、狩猎、死亡和自然界之神。他在暴怒时会滥伤人畜;他又擅长以草药来给人治病。其名意为“狂吼”或“咆哮”(可能是飓风或暴风雨)。 + +`Rudra` 和 `Miri` 的区别 : + +> `Rudra` 是静态分析,无需执行即可分析源码。`Miri` 是解释器,需要执行代码。 +> +> 两者可以结合使用。 + +## 关于 Unsafe Rust + +因为 `unsafe` 关键字的存在,引出了一个有趣的 API 设计领域: 如何交流 API 的安全性。 + +通常有两种方法: + +1. 内部 Unsafe API 直接暴露给 API 用户,但是使用 unsafe 关键字来声明该 API 是不安全的,也需要添加安全边界的注释。 +2. 对 API 进行安全封装(安全抽象),即在内部使用断言来保证在越过安全边界时可以Panic,从而避免 UB 的产生。 + +第二种方法,即将 Unsafe 因素隐藏在安全 API 之下的安全抽象,已经成为 Rust 社区的一种约定俗成。 + +Safe 和 Unsafe 的分离,可以让我们区分出谁为安全漏洞负责。Safe Rust 意味着,无论如何都不可能导致未定义行为。换句话说,Safe API 的职责是,确保任何有效的输入不会破坏内部封装的 Unsafe 代码的行为预期。 + +这与C或C++形成了鲜明的对比,在C或C++中,用户的责任是正确遵守 API 的预期用法。 + +比如,在 `libc` 中的`printf()`,当它调用一个错误的指针而导致段错误的时候,没有人会指责它。然而这个问题却导致了一系列的内存安全问题:格式字符串漏洞(format-string vulnerability)。还记得前段时间 苹果手机因为加入一个经过特别构造名字的Wifi就变砖的漏洞否? + +而在 Rust 中,`println!()` 就不应该也不可能导致一个段错误。此外,如果一个输入确实导致了段错误,那么它会被认为是 API 开发者的错误。 + +### Rust 中内存安全Bug 的定义 + +在 Rust 中有两类 Unsafe 定义: Unsafe 函数 和 Unsafe 特质(trait)。 + +Unsafe 函数希望调用者在调用该函数时,可以确保其安全性。 + +Unsafe 特质则希望实现该 trait 的时候提供额外的语义保证。比如标准库里的 `pub unsafe trait TrustedLen: Iterator { }`,该 trait 要求必须检查 `Iterator::size_hint()` 的上界,才能保证 `TrustedLen` 所表达的“可信的长度”语义。 + +该论文对 内存安全 Bug 提供了一个清晰的一致性的定义,而非 Rust 操作语义: + +**定义 1**: 类型(Type)和值(Value)是以常规方式定义的。类型是值的集合。 + +**定义2**: 对于 类型 T, `safe-value(T)` 被定义为可以安全创建的值。例如 Rust 里的字符串是内部表示为字节的数组,但它在通过 安全 API 创建的时候只能包含 `UTF-8` 编码的值。 + +**定义3**:函数 F 是接收类型为 `arg(F)`的值,并返回一个类型为 `ret(F)` 的值。对于多个参数,我们将其看作元组。 + +**定义4**: 如果 在 `safe-value(arg(F))`集合中存在`v` (记为:`∃𝑣 ∈ safe-value(𝑎𝑟𝑔(𝐹)) `),使得当调用 `F(v)`时触发违反内存安全的行为,或者返回一个不属于` safe-value(𝑟𝑒𝑡(𝐹))` 集合中的返回值`𝑣𝑟𝑒𝑡 `时(记为:𝑣𝑟𝑒𝑡 ∉ safe-value(𝑟𝑒𝑡(𝐹))),则 函数 F 有内存安全缺陷。 + +**定义5**: 对于一个泛型函数`Λ`,`pred(Λ)`被定义为满足`Λ`的类型谓词(指trait 限定)的类型集合。给定一个类型`𝑇∈pred(Λ)`,`resolve(Λ,𝑇)`将泛型函数实例化为具体函数𝐹。 + +**定义6**: 如果一个泛型函数`Λ`可以被实例化为一个具有内存安全缺陷的函数,即,`∃𝑇 ∈ pred(Λ)`,使得`𝐹=resolve(Λ,𝑇)`具有内存安全缺陷,则该泛型函数具有内存安全缺陷。 + +**定义7**:如果一个类型的`Send`实现不能跨越线程边界传输,那么该类型就有内存安全问题。 + +**定义8**: 如果一个类型的`Sync`实现不能通过别名指针(aliased pointer)并发地访问该类型,那么它就有内存安全问题。即,定义了一个非线程安全的方法,该方法接收`&self`。 + + + +## Unsafe Rust 中三类重要 Bug 模式 + +论文通过对已知漏洞进行定性分析,总结出 Unsafe Rust 中三类重要的 Bug 模式: + +1. Panic Safety (恐慌安全): 由恐慌导致的内存安全 Bug。 +2. Higher-order Safety Invariant(高阶安全不变性 ):由高阶类型没有给定安全保证而引发的 Bug。 +3. Propagating Send/Sync in Generic Types(泛型中`Send/Sync`传播):由泛型内部类型不正确的手工`Send/Sync`实现引起泛型 `Send/Sync` 约束不正确而引发的 Bug。 + + + +### Panic Safety + +这与其他编程语言(如C++)中的异常安全的概念类似。Rust 中类似其他编程语言中异常(Exception)的概念叫 恐慌(Panic)。恐慌一般在程序达到不可恢复的状态才用,当然在 Rust 中也可以对一些实现了 `UnwindSafe` trait 的类型捕获恐慌。 + +当 Panic 发生时,会引发栈回退(stack unwind),调用栈分配对象的析构函数,并将控制流转移给恐慌处理程序中。所以,当恐慌发生的时候,当前存活变量的析构函数将会被调用,从而导致一些内存安全问题,比如释放已经释放过的内存。 + +但是想要正确的推理在 Unsafe 代码中的恐慌安全,是非常困难且易于出错的。通常, 封装的Unsafe 代码可能会暂时绕过所有权检查,而且,安全封装的 API 在内部unsafe 代码的值返回之前,会根据安全边界条件确保它不会违反安全规则。但是,假如封装的Unsafe 代码发生了恐慌,则其外部安全检查可能不会执行。这很可能导致类似 C/C++ 中 未初始化(Uninitialized )或双重释放(Double Free)的内存不安全问题。 + +**论文对此给出定义:** + +如果一个函数`𝐹` `Drop`一个类型为`𝑇`的值`𝑣`,使得`𝑣`在Unwind 过程中 `𝑣 ∉ safe-value(𝑇)`,并导致违反内存安全,则说明该函数存在恐慌性安全漏洞。 + +```rust +// 标准库 `String::retain()` 曝出的 CVE-2020-36317 Panic safety bug + +pub fn retain(&mut self, mut f: F) +where + F: FnMut(char) -> bool +{ + let len = self.len(); + let mut del_bytes = 0; + let mut idx = 0; + + unsafe { self.vec.set_len(0); } // + 修复bug 的代码 + while idx < len { + let ch = unsafe { + self.get_unchecked(idx..len).chars().next().unwrap() + }; + let ch_len = ch.len_utf8(); + + // self is left in an inconsistent state if f() panics + // 此处如果 f() 发生了恐慌,self 的长度就会不一致 + if !f(ch) { + del_bytes += ch_len; + } else if del_bytes > 0 { + unsafe { + ptr::copy(self.vec.as_ptr().add(idx), + self.vec.as_mut_ptr().add(idx - del_bytes), + ch_len); + } + } + idx += ch_len; // point idx to the next char + } + unsafe { self.vec.set_len(len - del_bytes); } // + 修复bug 的代码 ,如果 while 里发生panic,则将返回长度设置为 0 +} + +fn main(){ + // PoC: creates a non-utf-8 string in the unwinding path + // 此处传入一个 非 UTF-8 编码字符串引发恐慌 + "0è0".to_string().retain(|_| { + match the_number_of_invocation() { + 1 => false, + 2 => true, + _ => panic!(), + } + }); +} + +``` + + + +### Higher-order Safety Invariant + +一个函数应该安全地执行所有安全的输入,包括参数数据类型、泛型类型参数以及外部传入的闭包。 + +换句话说,一个安全的函数不应该提供比 Rust 编译器提供的安全不变式更多的东西。所谓 安全不变式就是指 Rust 里的安全函数,在任何有效输入的情况下,都不应该发生任何未定义行为。 + +例如,Rust 里的 sort 函数,不应该触发任何未定义行为,哪怕用户提供的比较器不遵循全序关系,也不会发生段错误。但是 Cpp 中的排序函数,当用户提供一个不兼容当前的比较器的情况下,就会发生段错误。 + +Rust 为 高阶类型提供的唯一安全不变式是 类型签名的正确性。然而常见的错误是,对调用者提供的函数在以下方面产生了不正确的假设: + +1. 逻辑一致性:比如,sort函数遵循全序关系。 +2. 纯洁性:对相同的输入总是返回相同的输出。 +3. 语义约束:只针对参数,因为它可能包含未初始化字节。 + +对于 Unsafe 代码,必须自己检查这些属性,或者指定正确的约束(例如,用Unafe 的特质)让调用者义务检查这些属性。 + +在 Rust 类型系统下,执行高阶类型的安全不变式是很困难的。比如,将一个未初始化的缓冲区传给一个调用者提供的 Read 实现。 + +不幸的是,许多Rust程序员为调用者提供的函数提供一个未初始化的缓冲区来优化性能,而没有意识到其固有的不健全性。由于其普遍性和微妙性,Rust标准库现在[明确指出](https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read),用一个未初始化的缓冲区调用`read() `本身就是不健全的行为。 + +**论文对此给出定义:** + +高阶不变性bug是指函数中的内存安全bug,它是由假设保证高阶不变性引起的,而 Rust 的类型系统对调用者提供的代码没有保证。 + + + +```rust +1 // CVE-2020-36323: a higher-order invariant bug in join() +2 fn join_generic_copy(slice: &[S], sep: &[T]) -> Vec +3 where T: Copy, B: AsRef<[T]> + ?Sized, S: Borrow +4 { +5 let mut iter = slice.iter(); +6 +7 // `slice`is converted for the first time +8 // during the buffer size calculation. +9 let len = ...; // `slice` 在这里第一次被转换 +10 let mut result = Vec::with_capacity(len); +11 ... +12 unsafe { +13 let pos = result.len(); +14 let target = result.get_unchecked_mut(pos..len); +15 +16 // `slice`is converted for the second time in macro +17 // while copying the rest of the components. +18 spezialize_for_lengths!(sep, target, iter; // `slice` 第二次被转换 +19 0, 1, 2, 3, 4); +20 +21 // Indicate that the vector is initialized +22 result.set_len(len); +23 } +24 result +25 } +26 +27 // PoC: a benign join() can trigger a memory safety issue +28 impl Borrow for InconsistentBorrow { +29 fn borrow(&self) -> &str { +30 if self.is_first_time() { +31 "123456" +32 } else { +33 "0" +34 } +35 } +36 } +37 +38 let arr: [InconsistentBorrow; 3] = Default::default(); +39 arr.join("-"); +``` + +该代码是为 `Borrow`实现 join 方法内部调用的一个函数 `join_generic_copy`的展示。 在 `join_generic_copy` 内部,会对 `slice` 进行两次转换,而在 `spezialize_for_lengths!` 宏内部,调用了`.borrow()`方法,如果第二次转换和第一次不一样,而会返回一个未初始化字节的字符串。 + +这里, ` Borrow` 是高阶类型,它内部 `borrow` 的一致性其实并没有保证,可能会返回不同的slice,如果不做处理,很可能会暴露出未初始化的字节给调用者。 + +### Propagating Send/Sync in Generic Types + +当涉及泛型时候, `Send/Sync` 的规则会变得很复杂,如图: + +![rudra2](D:\Rust 工作报告\文档报告\Rust\image\rudra2.PNG) + +通常 `Send/Sync` 会由编译器自动实现,但是当开发者涉及 Unsafe 时,可能需要手动实现这俩 trait。手动实现 `Send/Sync` 想要正确很困难。一个不懂 `Send/Sync` 如何手动实现的开发者,很容易在代码中引入 Bug。 + +**论文对此给出定义:** + +如果泛型在实现`Send/Sync`类型时,如果它对内部类型上指定了不正确的`Send/Sync`约束,那么泛型的`Send/Sync`约束就会变得不正确。这就是 泛型中 `Send/Sync` 传播引发的不安全 Bug。 + +```rust +1 // CVE-2020-35905: incorrect uses of Send/Sync on Rust's futures +2 pub struct MappedMutexGuard<'a, T: ?Sized, U: ?Sized> { +3 mutex: &'a Mutex, +4 value: *mut U, +5 _marker: PhantomData<&'a mut U>, // + 修复代码 +6 } +7 +8 impl<'a, T: ?Sized> MutexGuard<'a, T> { +9 pub fn map(this: Self, f: F) +10 -> MappedMutexGuard<'a, T, U> +11 where F: FnOnce(&mut T) -> &mut U { +12 let mutex = this.mutex; +13 let value = f(unsafe { &mut *this.mutex.value.get() }); +14 mem::forget(this); +15 // MappedMutexGuard { mutex, value } +16 MappedMutexGuard { mutex, value, _marker: PhantomData } // + 修复代码 +17 } +18 } +19 +20 // unsafe impl Send +21 unsafe impl Send // + 修复代码 +22 for MappedMutexGuard<'_, T, U> {} +23 //unsafe impl Sync +24 unsafe impl Sync // + 修复代码 +25 for MappedMutexGuard<'_, T, U> {} +26 +27 // PoC: this safe Rust code allows race on reference counter +28 * MutexGuard::map(guard, |_| Box::leak(Box::new(Rc::new(true)))); +``` + +Rust futures 库中发现的问题,错误的手工 `Send/Sync`实现 破坏了线程安全保证。 + +受影响的版本中,`MappedMutexGuard`的`Send/Sync`实现只考虑了`T`上的差异,而`MappedMutexGuard`则取消了对`U`的引用。 + +当`MutexGuard::map()`中使用的闭包返回与`T`无关的`U`时,这可能导致安全Rust代码中的数据竞争。 + +这个问题通过修正`Send/Sync`的实现,以及在`MappedMutexGuard`类型中添加一个`PhantomData<&'a mut U>`标记来告诉编译器,这个防护也是在U之上。 + + + +## Rudra 的设计 + +整体设计图如下: + + + +![rudra3](D:\Rust 工作报告\文档报告\Rust\image\rudra3.PNG) + + + +Rudra 通过 `HIR` 来获取 crate 的代码结构(包括 trait定义、函数签名、Unsafe 块等),通过 `MIR` 来获取代码语义(数据流、控制流图、调用依赖等)。为什么不使用 LLVM IR 呢?因为在这个层面上 Rust 的抽象已经消失了。 + + 然后通过内部的 `Unsafe Dataflow Checker (UD)` 来检查 `Panic Safety Bug` 和 `Higher-order Invariant Bug`,通过 `Send/Sync Variance Checker(SV)`来检查 `Send/Sync Variance Bug`。最终将结果按优先级汇总输出报告。 + + `Unsafe Dataflow Checker (UD)` 和 `Send/Sync Variance Checker(SV) ` 对应两套算法,具体可参加论文和代码。 + + + +## 关于安全性相关英文术语解释 + +英文中关于安全性有多个单词,比如 Security和Safety,但是中文只有“安全性”这一个词。所以这里需要说明一下: + +1. Security,通常指信息安全、网络安全之类。 +2. Safety,通常指功能性安全。 + +通常,会因为功能性的漏洞,而造成信息安全问题。 + +## 小结 + +该论文的最后一章,还包含了很多数据来证明 Rudra 的效果,以及 Rudra 和 Fuzz 测试、Miri 和其他 Rust 静态分析工具的比较等结果。 + +![rudra4](D:\Rust 工作报告\文档报告\Rust\image\rudra4.PNG) + +上图是论文作者们使用 Rudra 对 Rust 实现的几个操作系统进行检查的结果,详细内容参加论文。 + +这篇论文非常值得一看,对于我们真正理解 Rust 的安全理念有所帮助。该论文也为 Rust 语言的安全状况提供了新的视角,也提供了一个静态检查工具,值得我们关注。 + + + diff --git a/src/chapter_8/rust-gui-framwork.md b/src/chapter_8/rust-gui-framwork.md deleted file mode 100644 index 01980b69..00000000 --- a/src/chapter_8/rust-gui-framwork.md +++ /dev/null @@ -1 +0,0 @@ -# Rust 编写 GUI 框架? diff --git a/src/chapter_8/rust-tips.rs b/src/chapter_8/rust-tips.rs index 38d8ebc8..3bb60228 100644 --- a/src/chapter_8/rust-tips.rs +++ b/src/chapter_8/rust-tips.rs @@ -1 +1,90 @@ -# Rust 技巧篇 +# Rust 技巧篇 | 用 `#[doc]` 属性宏改善你的文档注释 + +编辑: 张汉东 + +> 说明: 本文是在原文基础上的梳理,也引入了其他内容。 + +--- + +## 属性宏的新特性介绍 + +从 [Rust 1.54](https://blog.rust-lang.org/2021/07/29/Rust-1.54.0.html) 开始,属性宏增加了类函数宏的支持。 + +类函数的宏可以是基于`macro_rules!`的声明宏,也可以是像`macro!(...)`那样被调用的过程宏。这对于文档注释相当有好处。如果你的项目的 README 是一个很好的文档注释,你可以使用`include_str!`来直接纳入其内容。以前,各种变通方法允许类似的功能,但从`1.54`开始,这就更符合人体工程学了。 + +```rust +#![doc = include_str!("README.md")] +``` + +如果你看过一些 Rust 开源项目,你应该在 `lib.rs` 中看到过一大堆文档注释吧?这些注释太长,导致真正的代码被挤到到最下面。有了这个功能,就可以解决这类问题了。 + +```rust +macro_rules! make_function { + ($name:ident, $value:expr) => { + // 这里使用 concat! 和 stringify! 构建文档注释 + #[doc = concat!("The `", stringify!($name), "` example.")] + /// + /// # Example + /// + /// ``` + #[doc = concat!( + "assert_eq!(", module_path!(), "::", stringify!($name), "(), ", + stringify!($value), ");") + ] + /// ``` + pub fn $name() -> i32 { + $value + } + }; +} + +make_function! {func_name, 123} +``` + +也可以像这样,在属性中嵌入宏调用来构建文档注释。可以对比下展开后的代码: + +```rust +///The `func_name` example. +/// +/// # Example +/// +/// ``` +///assert_eq!(doc_attr::func_name(), 123); +/// ``` +pub fn func_name() -> i32 { + 123 +} + +``` + +这样的话,文档也可以复用了。当然你也可以扩展出其他用法。 + +## 其他用法 + +在 [国外社区朋友的这篇文章](https://blog.guillaume-gomez.fr/articles/2021-08-03+Improvements+for+%23%5Bdoc%5D+attributes+in+Rust)中,他列举了一些应用场合。 + + +### 用文档测试扩展测试能力 + +Rust 的文档测试相当灵活,假如你写了一些函数或者宏,你想确保它在输入某个值的时候不能编译。使用单元测试比较麻烦,但是用文档测试就很方便了。 + +```rust +/// ```compile_fail +#[doc = include_str!("compile_fail.rs")] +/// ``` +mod doc_test {} +``` + +你可以把相关测试放到 `complile_fail.rs` 中,然后使用 文档注释将其包括进来,这样在 cargo 执行测试的时候就可以进行测试了。而且对于 Rust 代码整体增加了可读性和可维护性。同样,你也可以检查 panic 等。 + +我们也不希望这种注释出现在最终用户的文档中,或者是编译文件中,所以需要使用 `cfg(doctest)` 来将其隐藏: + +```rust +#[cfg(doctest)] +/// ```compile_fail +#[doc = include_str!("compile_fail.rs")] +/// ``` +mod doc_test {} +``` + + diff --git a/src/chapter_8/rust_china_conf_2021.md b/src/chapter_8/rust_china_conf_2021.md index d68c0eed..00bc0ac0 100644 --- a/src/chapter_8/rust_china_conf_2021.md +++ b/src/chapter_8/rust_china_conf_2021.md @@ -1 +1,89 @@ # RustChinaConf 2021 正式启动 + +好消息,2021 Rust China Conf 要来了! + +本次大会初步定于2021年10月16、17日在上海市徐汇区云锦路701号西岸智塔AI Tower 45层 举行。 + + +## 大会介绍 + +2021 Rust China Conf 由 Rust 中文社区(RUSTCC)发起主办、知名企业和开源组织联合协办,是年度国内规模最大的 Rust 线下会议,深受 Rust 中文社区开发者与相关企业的喜爱与推崇。 + +本次大会为线下会议,将于10月16日-17日在上海举办,预计到场人数 350 人左右。大会面向企业技术负责人、一线工程师及社区个人开发者,特别是已在公司或个人项目中实践过 Rust 的开发者。 + +本次大会将同步开启线上直播,以方便无法亲临现场的广大程序员第一时间观看。并且大会结束后将上传相关录像,用于国内外的开发者长期回顾学习。 + +本次大会也获得了国内知名开发者社区、媒体与出版社的支持,如Infoq, CSDN、思否、掘金、电子工业出版社、人民邮电出版社等。 + + + + +## 大会目标 + +本次大会,致力于成为中国 Rustaceans 面对面交流的盛宴,为国内的 Rust 开发者和企业提供一次充分的成果展示、技术分享、能力提升、行业资讯交流、企业人才储备建设的机会。 + +## 大会议题征集 + +### 范围 + +Rust 语言独有的特性,使得其适应面非常广泛,通过广泛搜集整个社区和企业中的议题,分类参考如下: + +- 库或框架 +- Rust 语言贡献 +- Rust 学术/ 教育/ 教程 / 书籍 +- Rust 社区 +- Rust 生产环境使用经验 +- 最佳实践 +- 系统编程 +- WebAssembly +- 数据库 +- 游戏开发 +- 区块链 +- 嵌入式开发 +- 安全(Security)领域 +- 其他领域 +- workshop + +### 议题提交截止时间 + +9月20日23:59 + +### 议题提交链接 + +[https://shimo.im/forms/xqpwpdXw6YxrJTj9/fill](https://shimo.im/forms/xqpwpdXw6YxrJTj9/fill) + + +## 赞助通道 + +本次大会赞助权益文档:https://shimo.im/docs/Yg9H6kTVYtqCWRGH/ 《Rust 大会 2021赞助方案》,可复制链接后用石墨文档 App 或小程序打开。 + +欢迎各企业参与赞助。如有赞助需求,请联系: + +- Mike, vx: daogangtang,email: daogangtang@qq.com +- 张汉东, vx: blackanger,email:blackang3r@163.com +- 胡晓维, vx:h0923xw,email:vivian.xiage@gmail.com + + +## 志愿者招募通道 + +本次大会需要大量的志愿者,包括且不仅限于:线上文案、设计、网络宣传、现场指引、签到、资料装袋、周边发放、展台服务等。优先报幕上海地区的志愿者。志愿者拥有相关福利。 + +志愿者报名地址:[https://www.wjx.top/vj/exW24IP.aspx](https://www.wjx.top/vj/exW24IP.aspx) + +详情请联系志愿者事务负责人 高粱 vx:zucc2400。 + + +## 售票通道 + +即将开启。估计本次大会的门票价格会与2020年Rust中国大会持平。 + +热忱欢迎广大 Rustaceans 们到场参会,也希望大家帮忙积极宣传,让大会的信息传达到更多开发者那里,推动国内 Rust 社区的发展! + +社区有你的参与,我们前进更有动力!10月,上海,咱们不见不散! + + +## 去年盛况 + +最后,附上[去年 2020 Rust China Conf](https://rustcc.cn/2020rustchinaconf/) 的赞助及相关支持情况。 + +![conf](./image/2020conf/1.png) \ No newline at end of file diff --git a/src/chapter_8/singleton_and_sealed.md b/src/chapter_8/singleton_and_sealed.md index 30edb119..df7051da 100644 --- a/src/chapter_8/singleton_and_sealed.md +++ b/src/chapter_8/singleton_and_sealed.md @@ -1 +1,37 @@ # 真实世界的设计模式 | 单例模式 与 Sealed + +作者:张汉东 + +> 编者按: +> +> 本文摘录自[开源电子书《Real World Rust Design Pattern》](https://github.com/ZhangHanDong/real-world-rust-design-pattern),这本书也是我创建的免费开源电子书,目前正在逐步完善中,欢迎贡献。 +> +> 这本书旨在挖掘和记录 Rust 开源生态中设计模式的真实实践。欢迎参与贡献! + +--- + +## 单例模式 + + + +## Sealed + + + + +## 一句话介绍 + + + + +## 解决了什么问题 + + + + +## 如何解决 + + + + +## 真实案例 \ No newline at end of file diff --git a/src/chapter_8/tool_libs.md b/src/chapter_8/tool_libs.md index 8692313e..9ebd24c4 100644 --- a/src/chapter_8/tool_libs.md +++ b/src/chapter_8/tool_libs.md @@ -1 +1,244 @@ # 推荐项目 | 基础工具库 + +编辑: 张汉东 + +--- + +## Hora 0.1.0 发布 + +Hora,Rust 实现的近似最邻近搜索(Approximate Nearest Neighbor Search, ANNS)算法库。先发布了 v0.1.0,专注于近似最邻近搜索领域,已经实现了 HNSW(Hierarchical Navigable Small World Graph Index)索引,SSG(Satellite System Graph)索引,PQIVF(Product Quantization Inverted File)索引,BruteForceIndex,其他索引也即将推出。 + +Hora 可以部署在任何操作系统平台上,已经支持的 PC 操作系统 Linux,Mac OS,Windows,将支持移动设备操作系统 IOS 和Android,以及将来支持嵌入式系统(no_std),并将支持多语言绑定,包括 Python,Javascript,Java,Ruby,Swift 和 R。 + +相关链接信息: + +- Github:[https://github.com/hora-search/hora](https://github.com/hora-search/hora) +- 官网:[https://horasearch.com/](https://horasearch.com/) +- 在线Demo:[https://horasearch.com/#Demos](https://horasearch.com/#Demos) + +## voila:另类处理文件的方式 + +Voila 是一种通过 CLI 工具启动的特定领域语言,用于以快速可靠的方式处理大量文件和目录。 + +安装需要切换到 nightly 版本: + +```rust +$ rustup default nightly +$ cargo install voila +``` + +一些使用实例: + +```rust + +# 删除创建日期在 2020年1月1日 之后的所有文件 +$ voila ./backup "@creation=date >= 2020-01-01 { print(@name has been deleted) delete(@path) }" +# 删除文件名以 2020 结束的文件 +$ voila ./backup "@name ~= #(.*)-2020# { print(@name has been deleted) delete(@path) }" +``` + +语法如下: + +``` +$ voila DIRECTORY "<@VARIABLE | STRING | /REGEXP/> OPERATOR <@VARIABLE | STRING | #REGEXP#> [|| | && ANOTHER_CONDITIONAL ...] {OPERATION1-CYCLE-1(ARG1 ARG1, ARG2) OPERATION2-CYCLE-1(ARG1 ARG2) ...; OPERATION1-CYCLE-2(ARG1, ARG2 ARG2, ARG3)...}" +``` + +[https://github.com/Alonely0/Voila](https://github.com/Alonely0/Voila) + +## bustd:用于内存不足场景的进程杀手守护进程 + +相比 earlyoom 有更少的内存占用(注意是 Linux 系统的,不是跨平台的噢): + +``` +$ ps -F -C bustd +UID PID PPID C SZ RSS PSR STIME TTY TIME CMD +vrmiguel 353609 187407 5 151 8 2 01:20 pts/2 00:00:00 target/x86_64-unknown-linux-musl/release/bustd -V -n + +$ ps -F -C earlyoom +UID PID PPID C SZ RSS PSR STIME TTY TIME CMD +vrmiguel 350497 9498 0 597 688 6 01:12 pts/1 00:00:00 ./earlyoom/ +``` + +[https://github.com/vrmiguel/bustd](https://github.com/vrmiguel/bustd) + +## kas : 一个新的 GUI 库 + +这个库不知不觉已经来到 v0.9 了。 + +是否愿意尝试,读者自己决定。 + +[https://github.com/kas-gui/kas](https://github.com/kas-gui/kas) + +## slitter : 可信且经过验证的 slab 分配器 + +slitter 是由 Backtrace Labs 团队设计实现并用于 C 后端服务器的 slab 分配器,采用 Rust 编写。 + +在实际生产的两个月中,该团队使用 slitter 来: + +- 检测错误的分配类别 +- 避免使用任何带内元数据(in-band metadata) +- 保证类型稳定分配 +- 允许每个分配类指定如何映射它的备份内存 + +- 文章: [https://engineering.backtrace.io/2021-08-04-slitter-a-slab-allocator-that-trusts-but-verifies/](https://engineering.backtrace.io/2021-08-04-slitter-a-slab-allocator-that-trusts-but-verifies/) +- GitHub: [https://github.com/backtrace-labs/slitter](https://github.com/backtrace-labs/slitter) + +## Connector-x Rust 和 Python 中将数据从 DB 加载到 DataFrame 的最快库 + +ConnectorX 团队观察到现有解决方案在下载数据时或多或少会多次冗余数据。此外,在 Python 中实现数据密集型应用程序会带来额外的成本。ConnectorX 是用 Rust 编写的,并遵循“零拷贝”原则。这允许它通过变得对缓存和分支预测器友好来充分利用 CPU。此外,ConnectorX 的架构确保数据将直接从源复制到目标一次。 + +[https://github.com/sfu-db/connector-x](https://github.com/sfu-db/connector-x) + +## RillRaate: 带有实时Web界面的系统监控工具 + +RillRate 是完全使用 Rust 和 Yew 框架制作的机器人、微服务和物联网的快速 UI。 全栈 Rust 是真实存在的! + +最新版本增加的新功能: + +- 新控件:按钮、开关、选择器和滑块。 +- 新数据类型:表格、仪表、直方图(尚未图形化)。 + +[[Media] System Tools with real-time Web UI 🖥️ 🚀](https://www.reddit.com/r/rust/comments/p1b65e/media_system_tools_with_realtime_web_ui/) + +项目使用[RillRate](https://github.com/rillrate/rillrate)(一个为机器人、微服务和IoT设备设计的实时UI工具),实现了对CPU、内存和系统信息的监控,将数据可视化并实时呈现在web界面上。 + +[https://github.com/rillrate/rillrate](https://github.com/rillrate/rillrate) + +## gzp: v0.3.0 现在支持多线程压缩snappy + + +关于gzp: + +gzp是一个用Rust实现的多线程压缩编码库,目前支持Gzip格式(依赖flate2)和snappy格式(依赖rust-snappy) + +[https://github.com/sstadick/gzp](https://github.com/sstadick/gzp) + +## httpmock - 一个 http 服务端 + +- 简单、富有表现力、流畅的 API。 +- 许多内置帮助程序可轻松进行请求匹配。 +- 并行测试执行。 +- 可扩展的请求匹配。 +- 具有同步和异步 API 的完全异步核心。 +- 高级验证和调试支持。 +- 网络延迟模拟。 +- 支持正则表达式匹配、JSON、serde、cookies 等。 +- 带有Docker 镜像的独立模式。 +- 支持基于 YAML 文件的模拟规范。 + +[https://github.com/alexliesenfeld/httpmock](https://github.com/alexliesenfeld/httpmock) + +## helix-editor - 一个受 neovim 启发的编辑器 + +helix-editor 是一个深受 neovim 启发使用 Rust 开发的编辑器,感兴趣的朋友可以看看。 + +Github: https://github.com/helix-editor/helix + +## cargo-smart-release + +cargo-smart-release,无所畏惧地发布工作空间 crate,无需处理依赖关系或版本。 + +与 cargo release 的比较 + +cargo-release 是这个工具存在的原因,因为它让我迷上了一个了解git的全自动化发布工作流程。截至2021-08-12,这对简单的工作区或单速率工作区来说是完美的,所以请使用它:cargo install cargo-release。 + +以下是 cargo smart-release 的不同之处。 + +- 安全地执行,所以默认情况下,它被解除了武装 +- 指定一个或多个 crate,并自动检测哪些板块需要发布 +- 处理依赖性循环,以增加整体成功的机会 +- 当出现问题时,努力避免让工作区处于不一致的状态 +- 成为 gitoxide 的 playground,为应用程序作者提供更多的便利和更多的可行性。 + +[https://crates.io/crates/cargo-smart-release](https://crates.io/crates/cargo-smart-release) + +## jsonschema-rs:Rust Json 校验工具 + +如果你没有听(用)过 Json Schema,请允许我首先简单介绍一下。JSON Schema 是用于验证 JSON 数据结构的工具,如果你厌恶对 Json 数据各种 if else 的判断和校验,那该工具非常适合。它的官网:JSON Schema | The home of JSON Schema,先看一个简单的例子,假设我们有下面的 Schema: + +```rust + +{ + "type": "object", + "properties": { + "first_name": { "type": "string" }, + "last_name": { "type": "string" }, + "birthday": { "type": "string", "format": "date" }, + "address": { + "type": "object", + "properties": { + "street_address": { "type": "string" }, + "city": { "type": "string" }, + "state": { "type": "string" }, + "country": { "type" : "string" } + } + } + } +} +``` + +这个 Schema 一共定义了四个字段,每个字段的类型都做了规定,address 本身也是一个 Json Object。此时,有效的数据是: + +```json + +{ + "first_name": "George", + "last_name": "Washington", + "birthday": "1732-02-22", + "address": { + "street_address": "3200 Mount Vernon Memorial Highway", + "city": "Mount Vernon", + "state": "Virginia", + "country": "United States" + } +} +``` +而下面这样的无效数据则会被 Json Schema 验证并报错: + +```json +{ + "name": "George Washington", + "birthday": "February 22, 1732", + "address": "Mount Vernon, Virginia, United States" +} +``` + +Json Schema 本身是语言无关的,这里已经有很多实现了:Implementations | JSON Schema,Rust 版本的使用与其他语言类似: +```rust +use jsonschema::{Draft, JSONSchema}; +use serde_json::json; + +fn main() { + let schema = json!({"maxLength": 5}); + let instance = json!("foo"); + # 编译 Schema + let compiled = JSONSchema::compile(&schema) + .expect("A valid schema"); + # 验证实例 + let result = compiled.validate(&instance); + if let Err(errors) = result { + for error in errors { + println!("Validation error: {}", error); + println!( + "Instance path: {}", error.instance_path + ); + } + } +} +``` + +这个工具唯一有个麻烦的地方就是编写 Schema 比较费劲,可以理解为设计类。不过好在写好之后就省事了。 + +[https://github.com/Stranger6667/jsonschema-rs](https://github.com/Stranger6667/jsonschema-rs) + +## cargo-auto:自动任务工具 +包括:构建、发布、文档等功能。Cargo 功能已经很强大了,为啥还要做这个东西呢?因为有时我们需要做更多的事情,比如复制一些文件、发布到 ftp 或输入长命令。这些重复性任务必须自动化(也称为 “工作流自动化”)。 +```rust +$ cargo install cargo-auto +$ cargo auto new +$ cargo auto build +$ cargo auto release +$ cargo auto docs +``` + +[https://github.com/LucianoBestia/cargo-auto](https://github.com/LucianoBestia/cargo-auto) \ No newline at end of file diff --git a/src/chapter_8/trait-upcasting-part2.md b/src/chapter_8/trait-upcasting-part2.md index e15f794c..aeae032f 100644 --- a/src/chapter_8/trait-upcasting-part2.md +++ b/src/chapter_8/trait-upcasting-part2.md @@ -1 +1,551 @@ # Trait Upcasting 系列 | Part II + +作者:张汉东 / 审校:CrLF0710 + +记录 Trait Upcasting系列 系列 PR 过程。 + +PR 系列: + +1. [Refactor vtable codegen #86291](https://github.com/rust-lang/rust/pull/86291) +2. [Change vtable memory representation to use tcx allocated allocations.#86475 ](https://github.com/rust-lang/rust/pull/86475) +3. [Refactor vtable format for upcoming trait_upcasting feature. #86461](https://github.com/rust-lang/rust/pull/86461) +4. [Trait upcasting (part1) #86264](https://github.com/rust-lang/rust/pull/86264) +5. [Trait upcasting (part2) ]() + +本文为 第二个 PR 的描述。 + +--- + +在第一个 PR 发出之后,收到了官方成员(Member)的一些 review 意见。其中之一就是促进第二个 PR 的原因,被记录于 [issues #86324](https://github.com/rust-lang/rust/issues/86324) 。 + +第二个 PR 的目标是在[#86291 (comment)](https://github.com/rust-lang/rust/pull/86291#issuecomment-861306586) 中描述: + +> 第一步是重构 miri 中的 vtable 生成,以在`Machine`上下文之外创建一个`Allocation`。 +> +> 在 `cg_{clif,ssa} `中的 vtable 代码生成器的地方可以调用此函数,然后再调用任何用于降级到后端常量分配的方法。 +> +> 将 `trait + type -> allocation` 的映射添加到 `tcx.alloc_map` 或类似的东西来替换后端内部实现也不错。 + +一句话描述:修改`miri`和两套`codegen` 以便让它使用`tcx`中构建的用`allocation`表示的 vtable。 + +## 编译器内部概念说明 + +`tcx` 是指类型上下文,是由编译器内部 `rustc_middle::ty`模块定义的,它是编译器内部核心数据结构。 + +Rust 的类型在编译器内部,由 `Ty `表示。当我们说`Ty`的时候,是指`rustc_middle::ty::Ty`,而不是指`rustc_hir::Ty`,了解它们之间的区别是比较重要的。 + +### `rustc_hir::Ty` vs `ty::Ty` + +`rustc_hir::Ty`表示脱糖以后的类型,而`ty::Ty` 代表了类型的语义。 + +例如,` fn foo(x: u32) → u32 { x } ` 这个函数中,`u32` 出现两次。从 HIR 的角度看,这是两个不同的类型实例,因为它们出现在程序中不同的地方,也就是说,它们有两个不同的 Span (位置)。但是对于 `ty::Ty`来说,`u32` 在整个程序中都是同一个类型,它代表的不是具体的类型实例。 + +除此之外,HIR 还会有更多的信息丢失。例如, `fn foo(x: &u32) -> &u32`,在 HIR 看来,它不需要 lifetime 信息,所以 `&u32` 是不完整的。但是对于 `ty::Ty` 来说,它是完整的包含了 lifetime 信息。 + +一个简单总结: + +| `rustc_hir::Ty` | `ty::Ty` | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 描述类型的「语法」 | 描述类型的「语义」 | +| 每一个 `rustc_hir::Ty`都有自己的 `Span` | 整个程序而言都是同一个类型,并不特指某个类型实例 | +| `rustc_hir::Ty `有泛型和生命周期; 但是,其中一些生命周期是特殊标记,例如 `LifetimeName::Implicit`。 | `ty::Ty` 具有完整的类型,包括泛型和生命周期,即使用户将它们排除在外 | + + + +HIR 是从 AST 中构建的,它产生在 `ty::Ty` 之前。在 HIR 构建之后,一些基本的类型推导和类型检查就完成了。`ty::Ty`就是被用于类型检查,并且确保所有的东西都有预期的类型。 `rustc_typeck::astconv` 模块负责将 `rustc_hir::Ty`转换为`ty::TY`。 + +### `ty::Ty` 实现 + +`rustc_middle::ty::Ty`实际上是`&TyS`的一个类型别名。`&TyS ` 是 `Type Structure`的简称。一般情况下,总是会通过 `ty::Ty` 这个类型别名来使用 `&TyS ` 。 + +要分配一个新的类型,你可以使用`tcx`上定义的各种`mk_`方法。这些方法的名称主要与各种类型相对应。例如: + +```rust +let array_ty = tcx.mk_array(elem_ty, len * 2); // 返回 Ty<'tcx> +``` + +你也可以通过访问`tcx`的字段来找到`tcx`本身的各种常见类型:`tcx.types.bool`,`tcx.types.char`,等等。 + + + +## 修改文件概述 + +本次修改涉及 21 个文件。 + +1. compiler/rustc_codegen_cranelift/src/common.rs +2. compiler/rustc_codegen_cranelift/src/constant.rs +3. compiler/rustc_codegen_cranelift/src/lib.rs +4. compiler/rustc_codegen_cranelift/src/unsize.rs +5. compiler/rustc_codegen_cranelift/src/vtable.rs +6. compiler/rustc_codegen_llvm/src/common.rs +7. compiler/rustc_codegen_ssa/src/meth.rs +8. compiler/rustc_codegen_ssa/src/traits/consts.rs +9. compiler/rustc_middle/src/ty/context.rs +10. compiler/rustc_middle/src/ty/mod.rs +11. compiler/rustc_middle/src/ty/vtable.rs +12. compiler/rustc_mir/src/interpret/eval_context.rs +13. compiler/rustc_mir/src/interpret/intern.rs +14. compiler/rustc_mir/src/interpret/memory.rs +15. compiler/rustc_mir/src/interpret/terminator.rs +16. compiler/rustc_mir/src/interpret/traits.rs +17. src/test/ui/consts/const-eval/ub-upvars.32bit.stderr +18. src/test/ui/consts/const-eval/ub-upvars.64bit.stderr +19. src/test/ui/consts/issue-79690.64bit.stderr +20. src/test/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr +21. src/test/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr + +修改主要涉及 五个组件: + +1. `rustc_middle`,属于 rust 编译器的 main crate ,包含rustc“家族”中的其他crate使用的通用类型定义,包括 HIR/MIR/Types。 +2. `rustc_codegen_ssa`,截至2021年1月,RustC_Codegen_SSA 为所有后端提供了一个抽象的接口,以允许其他Codegen后端(例如Cranelift)。 +3. `rustc_mir`,用于操作 MIR 的库。 +4. `rustc_codegen_cranelift`,是 基于 cranelift 的编译器后端,专门用于 debug 模式 +5. `rustc_codegen_llvm`,是 基于 llvm 的编译器后端,专门用于 release 模式 + + + +### rustc_middle 库中的修改 + + + +1. 首先新增 `src/ty/vtable.rs` 模块,将 `vtable ` 的内存分配移动到 `rustc_middle`,以达到通用的目的。 +2. 在 `src/ty/mod.rs` 中将 `vtable` 模块导入 +3. 在 `src/ty/context.rs` 中增加 `vtables_cache`。 + + + + **`src/ty/vtable.rs` 模块** + +```rust +use std::convert::TryFrom; + +use crate::mir::interpret::{alloc_range, AllocId, Allocation, Pointer, Scalar}; +use crate::ty::fold::TypeFoldable; +use crate::ty::{self, DefId, SubstsRef, Ty, TyCtxt}; // 导入 `ty`模块中相关类型 +use rustc_ast::Mutability; + +#[derive(Clone, Copy, Debug, PartialEq, HashStable)] +pub enum VtblEntry<'tcx> { + MetadataDropInPlace, + MetadataSize, + MetadataAlign, + Vacant, + Method(DefId, SubstsRef<'tcx>), +} + +pub const COMMON_VTABLE_ENTRIES: &[VtblEntry<'_>] = + &[VtblEntry::MetadataDropInPlace, VtblEntry::MetadataSize, VtblEntry::MetadataAlign]; + +pub const COMMON_VTABLE_ENTRIES_DROPINPLACE: usize = 0; +pub const COMMON_VTABLE_ENTRIES_SIZE: usize = 1; +pub const COMMON_VTABLE_ENTRIES_ALIGN: usize = 2; + +impl<'tcx> TyCtxt<'tcx> { + // 给 vtable 分配内存,`TyCtxt` 中包含一个缓存,所以必须删除其重复数据 + /// Retrieves an allocation that represents the contents of a vtable. + /// There's a cache within `TyCtxt` so it will be deduplicated. + pub fn vtable_allocation( + self, + ty: Ty<'tcx>, + poly_trait_ref: Option>, + ) -> AllocId { + let tcx = self; + let vtables_cache = tcx.vtables_cache.lock(); + if let Some(alloc_id) = vtables_cache.get(&(ty, poly_trait_ref)).cloned() { + return alloc_id; + } + drop(vtables_cache); + + // See https://github.com/rust-lang/rust/pull/86475#discussion_r655162674 + assert!( + !ty.needs_subst() && !poly_trait_ref.map_or(false, |trait_ref| trait_ref.needs_subst()) + ); + let param_env = ty::ParamEnv::reveal_all(); + let vtable_entries = if let Some(poly_trait_ref) = poly_trait_ref { + let trait_ref = poly_trait_ref.with_self_ty(tcx, ty); + let trait_ref = tcx.erase_regions(trait_ref); + + tcx.vtable_entries(trait_ref) + } else { + COMMON_VTABLE_ENTRIES + }; + + let layout = + tcx.layout_of(param_env.and(ty)).expect("failed to build vtable representation"); + assert!(!layout.is_unsized(), "can't create a vtable for an unsized type"); + let size = layout.size.bytes(); + let align = layout.align.abi.bytes(); + + let ptr_size = tcx.data_layout.pointer_size; + let ptr_align = tcx.data_layout.pointer_align.abi; + + let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap(); + let mut vtable = Allocation::uninit(vtable_size, ptr_align); + + + // 无需对下面的内存访问进行任何对齐检查,因为我们知道 + // 分配正确对齐,因为我们在上面创建了它。 我们也只是抵消了 + // `ptr_align` 的倍数,这意味着它将与 `ptr_align` 保持对齐 + // No need to do any alignment checks on the memory accesses below, because we know the + // allocation is correctly aligned as we created it above. Also we're only offsetting by + // multiples of `ptr_align`, which means that it will stay aligned to `ptr_align`. + + for (idx, entry) in vtable_entries.iter().enumerate() { + let idx: u64 = u64::try_from(idx).unwrap(); + let scalar = match entry { + VtblEntry::MetadataDropInPlace => { + let instance = ty::Instance::resolve_drop_in_place(tcx, ty); + let fn_alloc_id = tcx.create_fn_alloc(instance); + let fn_ptr = Pointer::from(fn_alloc_id); + fn_ptr.into() + } + VtblEntry::MetadataSize => Scalar::from_uint(size, ptr_size).into(), + VtblEntry::MetadataAlign => Scalar::from_uint(align, ptr_size).into(), + VtblEntry::Vacant => continue, + VtblEntry::Method(def_id, substs) => { + // See https://github.com/rust-lang/rust/pull/86475#discussion_r655162674 + assert!(!substs.needs_subst()); + + // Prepare the fn ptr we write into the vtable. + let instance = + ty::Instance::resolve_for_vtable(tcx, param_env, *def_id, substs) + .expect("resolution failed during building vtable representation") + .polymorphize(tcx); + let fn_alloc_id = tcx.create_fn_alloc(instance); + let fn_ptr = Pointer::from(fn_alloc_id); + fn_ptr.into() + } + }; + vtable + .write_scalar(&tcx, alloc_range(ptr_size * idx, ptr_size), scalar) + .expect("failed to build vtable representation"); + } + + vtable.mutability = Mutability::Not; + let alloc_id = tcx.create_memory_alloc(tcx.intern_const_alloc(vtable)); + let mut vtables_cache = self.vtables_cache.lock(); + vtables_cache.insert((ty, poly_trait_ref), alloc_id); + alloc_id + } +} +``` + + + +**`src/ty/context.rs`** + +```rust +pub struct GlobalCtxt<'tcx> { + // ... + + // 不过在合并以后,eddyb 对此代码提出了异议: https://github.com/rust-lang/rust/pull/86475/files#r680788892 + // FxHashMap 是 rustc 内部使用的一个 hashmap 结构,使用了比 fnv 还快的 hasher,因为这里没有必要防止 DoS 攻击 + pub(super) vtables_cache: + Lock, Option>), AllocId>>, +} + +impl<'tcx> TyCtxt<'tcx> { + pub fn create_global_ctxt( /* ... */ ) { + // ... + GlobalCtxt { + // ... + vtables_cache: Default::default(), + } + } +} +``` + + + + + +### rustc_codegen_ssa 中的修改 + + + +修改 `src/traits/consts.rs` 中的 `ConstMethods ` trait,该 trait 定义了一些方法用于调用不同 后端的相关实现。比如在 `rustc_codegen_llvm`中: + +```rust +impl ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { + // ... +} +``` + +在 `src/traits/consts.rs` 中 : + +```rust + + +pub trait ConstMethods<'tcx>: BackendTypes { + + // ... + + fn const_data_from_alloc(&self, alloc: &Allocation) -> Self::Value; + // ... + +} + +``` + +然后在` src/meth.rs` 中引入 `ty::Ty`,并移除 vtable 内存分配相关代码 + +```rust + + +use rustc_middle::ty::{self, Ty}; + + +pub fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>( + cx: &Cx, + ty: Ty<'tcx>, + trait_ref: Option>, +) -> Cx::Value { + let tcx = cx.tcx(); + + debug!("get_vtable(ty={:?}, trait_ref={:?})", ty, trait_ref); + + // Check the cache. + if let Some(&val) = cx.vtables().borrow().get(&(ty, trait_ref)) { + return val; + } + + // 新增 + let vtable_alloc_id = tcx.vtable_allocation(ty, trait_ref); + let vtable_allocation = tcx.global_alloc(vtable_alloc_id).unwrap_memory(); + let vtable_const = cx.const_data_from_alloc(vtable_allocation); + + let align = cx.data_layout().pointer_align.abi; + let vtable = cx.static_addr_of(vtable_const, align, Some("vtable")); + cx.create_vtable_metadata(ty, vtable); + cx.vtables().borrow_mut().insert((ty, trait_ref), vtable); + vtable +} +``` + + + +### rustc_mir 中的修改 + +viable 内存分配已经被定义在了 `rustc_middle::ty::Ty` 中,所以要移除 `rustc_mir` 中 vtable 内存分配相关代码。 + +`rustc_mir` 中修改的是 miri 相关代码,miri 用于编译器常量计算。 + +在 `compiler/rustc_mir/src/interpret/intern.rs` 内删除 Vtable 相关内存类型。 该模块用于 常量计算的全局内存分配。 + +```rust +// compiler/rustc_mir/src/interpret/intern.rs +fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>( + ecx: &'rt mut InterpCx<'mir, 'tcx, M>, + leftover_allocations: &'rt mut FxHashSet, + alloc_id: AllocId, + mode: InternMode, + ty: Option>, +) -> Option { + // ... + match kind { + MemoryKind::Stack + | MemoryKind::Machine(const_eval::MemoryKind::Heap) +// | MemoryKind::Vtable // 移除 + | MemoryKind::CallerLocation => {} + } + // ... + +} + +``` + + + +在 `compiler/rustc_mir/src/interpret/eval_context.rs` 中删除 vtable cache相关: + +```rust +// compiler/rustc_mir/src/interpret/eval_context.rs + +pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> { + // ... + + // 移除下面三行 + // /// A cache for deduplicating vtables + // pub(super) vtables: + // FxHashMap<(Ty<'tcx>, Option>), Pointer>, + + // ... + +} + +impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { + pub fn new( + tcx: TyCtxt<'tcx>, + root_span: Span, + param_env: ty::ParamEnv<'tcx>, + machine: M, + memory_extra: M::MemoryExtra, + ) -> Self { + InterpCx { + machine, + tcx: tcx.at(root_span), + param_env, + memory: Memory::new(tcx, memory_extra), +// vtables: FxHashMap::default(), // 移除此行 + } + } + // ... +} +``` + + + +在 `compiler/rustc_mir/src/interpret/memory.rs` 中: + +```rust +impl MayLeak for MemoryKind { + #[inline] + fn may_leak(self) -> bool { + match self { + MemoryKind::Stack => false, +// MemoryKind::Vtable => true, // 移除此行 + MemoryKind::CallerLocation => true, + MemoryKind::Machine(k) => k.may_leak(), + } + } +} + +impl fmt::Display for MemoryKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MemoryKind::Stack => write!(f, "stack variable"), +// MemoryKind::Vtable => write!(f, "vtable"), // 移除此行 + MemoryKind::CallerLocation => write!(f, "caller location"), + MemoryKind::Machine(m) => write!(f, "{}", m), + } + } +} +``` + +在 `compiler/rustc_mir/src/interpret/terminator.rs ` 中: + +```rust +impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { + // ... + + /// Call this function -- pushing the stack frame and initializing the arguments. + fn eval_fn_call( + &mut self, + fn_val: FnVal<'tcx, M::ExtraFnVal>, + caller_abi: Abi, + args: &[OpTy<'tcx, M::PointerTag>], + ret: Option<(&PlaceTy<'tcx, M::PointerTag>, mir::BasicBlock)>, + mut unwind: StackPopUnwind, + ) -> InterpResult<'tcx> { + // ... + + // 这里处理trait对象 + ty::InstanceDef::Virtual(_, idx) => { + // ... + // Find and consult vtable + let vtable = receiver_place.vtable(); + let fn_val = self.get_vtable_slot(vtable, u64::try_from(idx).unwrap())?; // 修改 `drop_val` 为 `fn_val` + + // ... + // recurse with concrete function + self.eval_fn_call(fn_val, caller_abi, &args, ret, unwind) + } + } + // ... + +} +``` + +在 `compiler/rustc_mir/src/interpret/traits.rs` 中: + +```rust +impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { + /// Creates a dynamic vtable for the given type and vtable origin. This is used only for + /// objects. + /// + /// The `trait_ref` encodes the erased self type. Hence, if we are + /// making an object `Foo` from a value of type `Foo`, then + /// `trait_ref` would map `T: Trait`. + pub fn get_vtable( + &mut self, + ty: Ty<'tcx>, + poly_trait_ref: Option>, + ) -> InterpResult<'tcx, Pointer> { + trace!("get_vtable(trait_ref={:?})", poly_trait_ref); + + let (ty, poly_trait_ref) = self.tcx.erase_regions((ty, poly_trait_ref)); + + // All vtables must be monomorphic, bail out otherwise. + ensure_monomorphic_enough(*self.tcx, ty)?; + ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?; + + // 移除了之前的大部分代码,浓缩为这两行 + // 为 vtable 分配内存,并拿到相关指针 + let vtable_allocation = self.tcx.vtable_allocation(ty, poly_trait_ref); + let vtable_ptr = self.memory.global_base_pointer(Pointer::from(vtable_allocation))?; + + Ok(vtable_ptr) + } +} +``` + + + +### rustc_codegen_cranelift 中的修改 + +在 `rustc_codegen_cranelift` 中也是移除 vtable 内存分配相关代码。 + +上一个 PR 分析文章中说到, `rustc_codgen_cranelift` 因为没有依赖 `rust_codgen_ssa`的一些关键trait,所以vtable 内存分配这里还存在冗余代码。在重构 vtable 内存分配之后,就可以将这些冗余代码消除了。 + + + +在 `compiler/rustc_codegen_cranelift/src/vtable.rs` 中: + +```rust +pub(crate) fn get_vtable<'tcx>( + fx: &mut FunctionCx<'_, '_, 'tcx>, + ty: Ty<'tcx>, // 这里使用了 `ty::Ty` + trait_ref: Option>, +) -> Value { + // 删除了之前的内存分配相关代码(主要是 build_vtable 函数),精简很多 + let vtable_ptr = if let Some(vtable_ptr) = fx.vtables.get(&(ty, trait_ref)) { + *vtable_ptr + } else { + let vtable_alloc_id = fx.tcx.vtable_allocation(ty, trait_ref); + let vtable_allocation = fx.tcx.global_alloc(vtable_alloc_id).unwrap_memory(); + let vtable_ptr = pointer_for_allocation(fx, vtable_allocation); + + fx.vtables.insert((ty, trait_ref), vtable_ptr); + vtable_ptr + }; + + vtable_ptr.get_addr(fx) +} + + +``` + +主要是这个方法的修改,其他修改都是围绕该方法的琐碎修改。 + +### rustc_codegen_llvm 中的修改 + +在 `compiler/rustc_codegen_llvm/src/common.rs ` 中: + +```rust +impl ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { + // ... + + fn const_data_from_alloc(&self, alloc: &Allocation) -> Self::Value { + const_alloc_to_llvm(self, alloc) + } + + // ... + +} +``` + +## 小结 + +这次 PR 主要是将 vtable 的内存分配重构到 `rustc_middle::ty::Ty` ,以便其他组件可以公用。这里只是一个大概梳理,还有很多细节可以深究。 + + diff --git a/src/chapter_8/what-is-trait-upcasting.md b/src/chapter_8/what-is-trait-upcasting.md new file mode 100644 index 00000000..8aa5e3f4 --- /dev/null +++ b/src/chapter_8/what-is-trait-upcasting.md @@ -0,0 +1,48 @@ +Trait Upcasting 系列 | 如何把子 trait 转成父 trait ? + +作者: CrLF0710 + +> 本文由 Trait Upcasting 贡献者 CrLF0710(猫老师)来介绍一下特质向上类型转换(Trait upcasting coercion) 这个功能。 + +--- + +此功能对应的MCP是[Trait Upcasting · Issue #98 · rust-lang/lang-team (github.com)](https://link.zhihu.com/?target=https%3A//github.com/rust-lang/lang-team/issues/98)。 + +现在是2021年8月,主体功能已经完成了,在nightly上可以试用。 + +目前还剩两个边角情况没支持: + +1. 在新的vtable设计方案下,trait upcasting 在多重继承存在的情况下有时需要实际访问vtable 内容。因为原生指针(raw pointer)的metadata是否必须有效是未解决问题,因而对原生指针的upcasting 应该是unsafe操作。这又涉及到了CoerceUnsized 相关的一些问题。(本条为这个功能目前为incomplete_features的主要原因) + +2. 在多重继承下,trait存在列表中可能具有相同父trait使用不同泛型参数的情况。这里的类型推导有一些实现上的问题,目前尚未实现。(这种情况下会提示无法转换) + +接下来我会继续推动这个功能完成,然后根据实现经验撰写一篇RFC。如果有必要的话,我也许会再看看在chalk下怎么实现这个功能。 + +接下来简单介绍下这个功能怎么用,其实还蛮自然的: + +1需要转换的地方,标注转换的目标类型(函数参数、返回值之类的地方已经有标注了),然后如果编译器认为可以(认定的方法可以参见下面的详细设计),转换就会成功了。 + +以下是详细设计: + +1. 对于实现了CoerceUnsized特质的类型可以在两个类型(暂时称为T, U)间进行尺寸擦除类型转换(unsizing coercion),最常用的有从 &T -> &U, Box 到 Box等等。类型转换的场景(coercion site) 简单说就是转换目标有类型标注的情况,包括let上的类型标注,函数参数的标注,返回值的标注,as表达式等等。 +2. 特质对象的语法是 dyn PrincipalTrait [+ AutoTrait]* [+ 'lifetime]* 或 dyn AutoTrait [+ AutoTrait]* [+ 'lifetime]* 其中方括号表示可选,星号表示0个或任意个。 +这里的PrincipalTrait是一个Rustc内部术语,指的是占据特质对象主导地位的那个特质。(Rust目前不支持多个)。特质对象上面的方法/关联函数由这个特质和它的所有的祖先特质决定。 +3. 特质对象之间的尺寸擦除类型转换原本就存在,主要包括三种: + a. 减少AutoTrait部分 + b. 在subtyping规则允许的范围下调整lifetime + c. 本次新增:将PrincipalTrait置换为它的任意一个祖先特质。 +4. 置换的方法是调整trait object的metadata,也就是vtable的指针。我们本次重新设计了vtable的结构: + 对于 + ```text + A + / \ + B C + \ / + D + ``` + +这样的菱形继承结构,我们保障最左边一列 A- B 特质所需的vtable恰好是D的vtable的前缀(从而对单继承优化),并为这一列之外的特质(C)在vtable中存储一个vtable指针。 +实际转换时,对于最左边一列的向上转换,是no-op,不会做任何事。对于这一列之外的转换,从vtable中取出对应的vtable指针替换即可。 + +目前官方 T-lang下推动这件事的活动(initiative),有反馈和讨论都会放这个仓库里:[https://github.com/rust-lang/dyn-upcasting-coercion-initiative/](https://github.com/rust-lang/dyn-upcasting-coercion-initiative/),感兴趣的可以关注。 +