Blog | Phodal - A Growth Engineerhttp://www.phodal.com/blog/2022-10-29T12:42:12.556624+00:00Blog高性能可视化架构:一个交互式实时数据引擎的架构设计2022-10-29T12:39:28+00:002022-10-29T12:42:12.556624+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/high-performance-frontend-component/在分析 SecDB、Athena、Quartz 几个实时金融与风险分析平台的时候,发现了 [Perspective](https://perspective.finos.org/) —— 一个 FinTech 开源基金会 FinOS 旗下开源的交互式分析和可视化组件库,由摩根大通(J.P. Morgan Chase)公司开源出去的流式数据可视化组件库。所以,从某种意义上来说也是《[金融 Python 即服务:业务自助的数据服务模式](https://www.phodal.com/blog/bank-python-as-a-service/)》 的后续展开,也可以算是[低延迟架构](https://www.phodal.com/blog/explore-low-latency-architecture/)的后续探索。
起初,我只是对其中使用的 ExprTk 感兴趣,后来发现这个库不简单:使用了 C++、Rust、Python、JavaScript、TypeScript 等语言。混合语言的项目都特别好玩,于是乎,我便开始探索它了。而我原先感兴趣的 `x <= 'abc123' and (y in 'AString') or ('1x2y3z' != z)` 的解析与实现,也就先放在一边了。
开始之前,先复制一下官方的介绍:
> [Perspective](https://github.com/finos/perspective) 是一个交互式分析和数据可视化组件,特别适合大型数据集或流数据。可以 使用它来创建用户可配置的报告、仪表板、Notebook 和应用程序,然后在浏览器中独立部署,或与 Python 和/或 Jupyterlab 协同部署。
简单来说,就是可以提供实时图形渲染,并支持 Jupyter 集成。如果是 Jupyter 的集成,那么从某种来说,它是一种金融工作台,类似于先前定义的[架构工作台](https://www.phodal.com/blog/architecture-workbench/)。
PS:写这样的工具太过复杂了,所以先写篇文章记录一下,等未来有空的时候,再写一个。
## 高性能可视化架构:Perspective 架构分析
初步绘制的 Perspective 架构图如下所示:
![高性能可视化架构][1]
在 JavaScript 侧,系统可以分为三层:
* 数据引擎。使用 C++ 与一系列的数据结构库等,进行封装,并提供数据操作 API。通过 [Emscripten](https://github.com/emscripten-core/emscripten) 构建和封装,以提供 WASM 接口。
* wrapper 层。提供对于数据引擎的再次封装,以使 API 更符合日常的编程习惯,诸如于 table、view 等,还提供 `worker`、`websocket` 等封装。
* UI 组件。viewer 分为 d3fc、datagrid、openlayers 等不同的组件,大部分使用纯 Rust 编写,提供 Web Component API 等。
在 Python 侧,除了相同的 UI 部分,还需要构建 Jupyter 插件:
* 数据引擎。结合 pybind11 来提供 FFI (Foreign Function Interface,外部函数接口)能力。
* wrapper 层。结合了 Python 数据科学生态中的 Pandas、Numpy 等工具,来进行数据转换。
* UI 层。结合 Lumino 对 UI 组件进行封装。
其中,比较有意思的是 Apache Arrow,提供了跨语言的数据支持。
## 密集计算下沉:C++ 与 WASM 应对挑战
对于将密集型计算下沉到 WASM 部分,相信大家都比较熟悉了。对于常规的 WASM 使用来说,需要平衡开发效率和运行效率,FFI 在调用的时候也存在性能损失。也因此,一种比较理想的方式是将数据操作,全部委托给 C++ 部分去实现。
如上面的架构图所示,Perspective 的计算部分,主要是 Table 对象实现的,它是 **Perspective** 中的基本数据容器。 Table 是有类型的 —— 它们有一组不可变的列名,每个都有一个已知的类型。每当有对数据的处理时,都会通过 WASM 来处理。**过滤与计算**,在这里也是一个非常有意思的问题,即上面说到的 ExptTk,便是用来做这部分计算用的。
值得注意的是,**Perspective** 之所以采用 C++ 来构建 WebAssembly 的方式,大概率是因为原有的一部分基础设施是基于 C++ 的。与此同时,原先采用的是 C++ 的 UI,以提供更好的性能。不过,Perspective 提供的 WASM 包,大概有 40M 左右,在初始化的时候相对慢了一点。
可是,又为什么是 Table 呢?这就得从 Apache Arrow 提供的能力说起。
## 无序列化与内存分析:Apache Arrow
对于序列化的性能优化,相信大家都比较熟悉了。通常来说一次数据传输操作包括:
1. 以某种格式序列化数据
2. 通过网络连接发送序列化数据
3. 在接收端反序列化数据
于是乎,在很多系统中(如 ArchGuard),序列化就是系统的瓶颈。**既然序列化会带来问题,那么就不应该有序列化**。于是乎,我们就可以在上面的架构图中,看到两个工具:
* Apache Arrow。一个直接针对数据分析需求的数据层,提供分析所需的数据类型的综合集合。除了语言无关的标准化列式存储格式之外,它还包含三个特性:零拷贝共享内存和基于 RPC 的数据移动、读取和写入文件格式(如 CSV、Apache ORC 和 Apache Parquet)、内存分析和查询处理。
* FlatBuffers。同样的,无需解析/解包即可访问序列化数据。
不过呢,FlatBuffers 只是 Arrow 用来序列化实现 Arrow 二进制 IPC 协议所需的模式和其他元数据。随后,我们就可以使用 Table 来调用 Arrow 的 API 来进行计算。
Apache Arrow 的相关介绍可以见官方文档:<https: arrow.apache.org=""></https:>
## 灵活的前端组件:无框架与渲染机制优化
简单来说,只要是以下的两个特点:
* **无框架**。对于一个以渲染为主的项目来说,Perspective 不采用任何框架。从某种意义上来说,更小的包大小,也带来了更好的性能。除此,作为一个纯粹的 web components 组件,它可以非常容易与几大主流框架结合到一起。
* **虚拟渲染的 Table**。在 Table 显示上, Perspective 采用的是 JMPC 的 [regular-table](https://github.com/jpmorganchase/regular-table),同样也是 Web Components 组件,可以直接引入项目使用。并且支持虚拟渲染,即仅显示可视区域的数据,减少 DOM 节点以带来更好的性能。
对于 Web Component 和 Custom Element 部分,相信大家都比较熟悉了。它们使用起来和正常的 HTML 区别不大,如下是一个不同 UI 组件之间的关系示例:
```javascript
<perspective-workspace>
<perspective-viewer>
<perspective-viewer-datagrid>
<regular-table></regular-table>
</perspective-viewer-datagrid>
<perspective-viewer-datagrid-toolbar></perspective-viewer-datagrid-toolbar>
</perspective-viewer>
<perspective-viewer ...=""></perspective-viewer>
</perspective-workspace>
```
每一个组件分别在不同的工程中,倒是挺 [componentless](https://componentless.com/) 的。一旦数据发生变化的时候,就会从 viewer 侧,调用 `update_and_render` 从而更新 UI 部分的 render。
## 其它
参考材料:
* 《[Apache Arrow 和 Java:大数据传输快如闪电](https://www.infoq.cn/article/bcayj0vv8f8wtcpzxm7h)》
* 《[Perspective](https://perspective.finos.org/).js》官网
[1]: /media/uploads/gallery/bank/low-latency-render-architecture.pngRust + LLVM 调用 C/C++ 模块2020-11-22T07:52:53+00:002020-11-22T07:53:42.746023+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/llvm-rust-call-c-bytecode/在上一篇文章『[LLVM + Rust JIT hello, world](https://www.phodal.com/blog/llvm-rust-hello-world-jit/)』中,我们介绍了如何使用 Rust + LLVM 编写一个 hello, world。而随着我们继续在这个领域的探索,我还想到了一个非常有意思的问题:如何使用 LLVM 调用三方模块。
最后代码见:https://github.com/phodal/rust-llvm-practises/tree/main/stdlib
从最后的结果来看,要实现这样的功能也相当的简单。只是呢,作为一个 LLVM 新手,我要学习的东西还有蛮多的。
## Clang + LLVM 字节码
我们所要做的事情其实还是相对比较简单的:通过 Clang 来编译,并输出 LLVM IR。
> Clang 是一个 C、C++、Objective-C 和 Objective-C++ 编程语言的编译器前端。它的目标是提供一个 GNU 编译器套装的替代品,支持了 GNU 编译器大多数的编译设置以及非官方语言的扩展。
在 `clang` 的编译参数中,提供了一个 `emit-llvm`:
```makefile
CC=clang
CFLAGS=--target=$(TARGET) -emit-llvm -O3 -ffreestanding -fno-builtin -Wall -Wno-unused-function
%.bc: %.c
$(CC) -c $(CFLAGS) $< -o $@
CHARJ=charj.bc
all: $(CHARJ)
$(CHARJ): TARGET=x86_64-apple-darwin-macho # macos
```
对应的是一个 C 的 hello, world :
```c
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
```
## Rust LLVM 调用 LLVM IR
接下来,就是用 Inkwell + LLVM 的 API, 加载这个字节码了:
```rust
let context = Context::create();
let memory = MemoryBuffer::create_from_memory_range(CHARJ_LIB, "charj");
let module = Module::parse_bitcode_from_buffer(&memory, &context).unwrap();
```
这里的 `module` 改为采用 `parse_bitcode_from_buffer` 的方式,而不是原来的创建方式。然后我们就可以获取到定义的 main 函数了,然后执行:
```rust
if let Some(_fun) = module.get_function("main") {
let ee = module
.create_jit_execution_engine(OptimizationLevel::None)
.unwrap();
let maybe_fn = unsafe { ee.get_function::<unsafe "c"="" -="" extern="" fn()=""> f64>("main") };
unsafe {
maybe_fn.unwrap().call();
}
}
```</unsafe></stdio.h>Rust + LLVM + JIT hello, world 示例2020-11-22T07:01:24+00:002020-11-22T07:55:11.901657+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/llvm-rust-hello-world-jit/最近在和我一同事一起,使用 Rust 来创造一门新的编程语言:[Charj](https://github.com/charj-lang/charj)。而在实践方面,我们都是这方面的新手,所以不得不经历一番尝试。而作为其中的一部分,必然就是由一个 hello, world 开始的。由于在这个过程上,遇到一些小坑,所以我决定写篇文章记录一下。
最后代码见:[https://github.com/phodal/rust-llvm-practises](https://github.com/phodal/rust-llvm-practises)
简单介绍一下,一些工具:
- Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为“安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。
- LLVM 是一套编译器基础设施项目,为自由软件,以 C++ 写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。
- [Inkwell](https://github.com/TheDan64/inkwell),一个非官方的 LLVM 的 Rust 封装。
- [lvmenv](https://github.com/termoshtt/llvmenv),一个用来管理不同版本 LLVM 的三方工具,主要也是 Rust 编写时使用的。
作为结论来说,我们需要这么一些步骤:
1. 安装 LLVM 相关的编译工具
2. 安装 llvmenv
3. 通过 llvmenv 来编译 LLVM
4. 了解 LLVM 的一些基本概念
5. 编写和运行我们的 hello, world
而实际上,前三步是同一件事情:安装和编译 LLVM,只是使用了道具不同而已。
## 编译和安装 LLVM
现在,我们开始安装 llvmenv(PS:在它的 GitHub 上看到最新的安装步骤:https://github.com/termoshtt/llvmenv )。从 README 上来看,我们需要安装 `make`, `ninja`, `clang`(使用的是 macOS 系统)这些工具。由于 Clang 是 XCode 自带的,所以不需要安装了。
```bash
brew install cmake ninja
```
紧接着,就可以安装 `llvmenv`
```bash
cargo install llvmenv
```
llvmenv 的官方文档不是那么详细,缺少了如何安装的说明。所以在 issue 上有相关的内容,2333。简单来说,步骤就是:
```bash
llvmenv init
llvmenv entries
llvmenv build-entry 10.0.0
```
`entries` 命名可以列出相关的版本,`build-entry` 则是构建对应的 LLVM 版本。而由于网络的原因,我直接 `build-entry` 并不成功,所以我需要先下载对应版本的 LLVM。接着修改配置:`$XDG_CONFIG_HOME/llvmenv/entry.toml`,将 LLVM 指定我下载的位置,如:
```toml
[local-llvm]
path = "/path/to/your/src"
target = ["X86"]
```
然后,我就可以构建了:
```
llvmenv build-entry local-llvm
```
构建完之后,我们就可以先大概熟悉一下 LLVM 的一些基本概念。
### LLVM 基本概念
详细的说明,我这里不就列举了,可以看官方文档。我只要是罗列一下后面代码会用到的一些基本概念。来源于《[使用LLVM IR 编程](http://richardustc.github.io/2013-06-19-2013-06-19-programming-with-llvm-ir.html)》
**Module**,可以将 LLVM 中的 Module 类比为 C 程序中的源文件。一个 C 源文件中包含函数和全局变量定义、外部函数和外部函数声明,一个 Module 中包含的内容也基本上如此,只不过C源文件中是源码来表示,Module 中是用 IR 来表示。
**Function**,Function 是 LLVM JIT 操作的基本单位。Function 被 Module 所包含。LLVM 的 Function 包含函数名、函数的返回值和参数类型。Function 内部则包含 BasicBlock。
**BasicBlock**,BasicBlock与编译技术中常见的基本块 (basic block) 的概念是一致的。BasicBlock 必须以跳转指令结尾。
**Instruction**,Instruction就是 LLVM IR 的最基本单位。Instruction 被包含在 **BasicBlock 中。
**ExecutionEngine**,ExecutionEngine 是用来运行 IR 的。运行IR有两种方式:解释运行和 JIT 生成机器码运行。相应的 ExecutionEngine 就有两种:Interpreter 和 JIT。ExecutionEngine 的类型可以在创建 ExecutionEngine 时指定。
而一个典型的创建过程便是:
1. 创建一个 Module
2. 在 Module 中添加 Function
3. 在 Function 中添加 BasicBlock
4. 在 BasicBlock 中添加指令
5. 创建一个 ExecutionEngine
6. 使用 ExecutionEngine 来运行 IR
嗯,剩下的东西就没有那么复杂了。
### Rust LLVM hello, world
首先,添加一下依赖:
```
[dependencies]
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm10-0"] }
```
然后,复制一下我写的 hello, world 代码:https://github.com/phodal/rust-llvm-practises/blob/main/helloworld/src/main.rs 。由于 Inkwell 文档上没有,所以我好不容易参考了一下 C++ 的版本,然后抄了过来。
对应上面的逻辑便是:
```rust
let context = Context::create();
let module = context.create_module("repl");
let builder = context.create_builder();
Compiler::new(&context, &builder, &module);
```
然后在 Compiler 里写上对应的方法:
```rust
let i32_type = self.context.i32_type();
let function_type = i32_type.fn_type(&[], false);
let function = self.module.add_function("main", function_type, None);
let basic_block = self.context.append_basic_block(function, "entrypoint");
self.builder.position_at_end(basic_block);
let i32_type = self.emit_printf_call(&"hello, world!\n", "hello");
self.builder
.build_return(Some(&i32_type.const_int(0, false)));
let _result = self.module.print_to_file("main.ll");
self.execute()
```
编写过程中,困扰我比较多的一点是:如何调用 `print` 方法来输出。最后没有想到是这么的简单:
```rust
let printf = self
.module
.add_function("puts", printf_type, Some(Linkage::External));
```
最后构建:
```bash
LLVM_SYS_100_PREFIX=$HOME/llvm/llvm-10.0.1.src/build cargo build
LLVM_SYS_100_PREFIX=$HOME/llvm/llvm-10.0.1.src/build cargo run
```Rust 性能优化日志(上)2020-10-11T07:33:18+00:002020-10-11T07:34:14.158477+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/rust-performance-optimization/最近的几个月里,一直在编写代码识别引擎 [Scie](https://github.com/phodal/scie/) ,好不容易解决了各种奇怪的问题。随后,在尝试做了一次 benchmark 之后,发现我写了的这么一些 Rust 代码,运行起来的速度非常慢。同样是对一个代码文件的分析,Scie 差不多要 12S 完成,而同样的 Node.js Addons 则只需要 200ms。于是,我开始了我的性能优化之旅。
## Clion Profiler
开始之前,先介绍一下 Clion 包含的相关功能,也就是 Profiler。不论是在 IDE 中的右键,还是在菜单栏中都可以轻松找到。对于我这种系统编程的新手来说,这种 IDE 的工具真的非常好用,笑~。
执行一下:
```
/Users/fdhuang/charj/scie/target/debug/benchmark
native-profiler-starter: waiting for profiler...
native-profiler: starting target executable itself...
TOKENIZING 121220 length using grammar source.js 5322 ms
Process finished with exit code 0
```
然后打开 Profiler,查看对应的调用,诸如于:
| | Method | Samples |
|----|----------|---------------|
| 92.8% | benchmark`scie_grammar::grammar::grammar::Grammar::tokenize_string `| 4883 |
| 64.7% | benchmark`scie_grammar::grammar::grammar::Grammar::match_rule` | 3158 |
| 8.9% | benchmark`scie_grammar::grammar::grammar::Grammar::handle_captures` | 434 |
考虑到图形化的程度,建议感兴趣的读者可以自己尝试一下。
然后,我们就可以找到对应的突破点。
## Rust 性能优化
好了,接下来就可以正式开始优化的过程了。
### 测试
重构之前,记得有点测试。
### 使用 lazy_static 延迟初始化正则
Scie 中需要使用了大量的正则表达式,其中有一些需要在结构体中使用。在我最开始的版本中,直接在结构体中初始化。在几千次的调用之下,必然会出现性能问题。于是就需要使用 `lazy_static` 来进行赋值。
```rust
lazy_static! {
static ref CAPTURING_REGEX_SOURCE: Regex = Regex::new(r"\$(?P<index>\d+)|\$\{(?P<commandindex>\d+):/(?P<command/>downcase|upcase)\}").unwrap();
}
```
事实上,我们就是寻找一种类似于单例的方式。
### 清理不需要的 clone
在编写的过程中,由于懒 + 重复修改的原因,导致了代码中遗留了一些不必备的 `clone`。在 Profiler 视图下,还是能明显地看到一些差异。也因此呢,全局搜索一下,然后删除不需要的。
### 优化 clone
同样的,在取某些值的时候,直接 `a.clone().b`,而非 `a.b.clone()` 也是一个不错的优化点。虽然,我没有真正测试过是否对性能有影响(没细研过编程的处理逻辑),但是至少舒服了。
### 使用传递引用
由于 Scie 之前参考的是其它语言的代码,所以在实现的时候,也没有以 Rust 的最佳方式来实现。难免就出现一些问题,比如没有传递引用,导致需要大量的 `clone()`。以下以一下简单的字符串作为示例:
```rust
#[derive(Debug, Clone)]
pub struct LineTokens {
pub _line_text: String
}
impl LineTokens {
pub fn new(
_line_text: String,
) -> Self {
...
}
}
```
我们要做的是 `String` -> `&'str`。而由于生命周期的传染性,我们需要改大量的相关代码,这真的是一个痛苦的过程:
```rust
impl<'a> LineTokens<'a> {
pub fn new(
line_text: &'a str,
) -> LineTokens<'a> {
LineTokens {
_line_text: line_text,
}
}
```
### 判断后再 `clone`
这个主要是我没发现 Rust 的 Option 有一个 `is_some()` 的接口。所以,一直都是 `let Some(capts) = captures.clone()` 这样的写法。发现之后,就改为了:
```rust
if captures.is_some() {
let capts = captures.unwrap();
...
}
```
而在这一些场景之下,根本就不需要使用到的值。
类似的操作还有:
```rust
let begin_captures = desc.begin_captures.clone();
if let None = begin_captures {
desc.begin_captures = desc.captures.clone()
}
```
改成对应的其它先判断的形式,主要是习惯了其它语言的写法,又或者是它们对性能的要求没有这么高。毕竟,我是复制、粘贴过来的。
### chars 转换成 vec
这又是另外一个对于编程语言不熟悉的问题。
```rust
while pos < length {
let ch = exp_source.chars().nth(pos).unwrap();
```
所以,只需要先转换为数据,再通过它去取值即可:
```rust
let chars: Vec<char> = exp_source.chars().collect();
while pos < length {
let ch = chars[pos];
```
## 其它
未完待续!</char></commandindex></index>Rust OO:多态与继承2020-09-05T02:31:19+00:002020-09-05T02:32:11.940708+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/rust-oo-notes/> 学习编程语言的最好方式最反复练习。
最近在用 Rust 重写 VSCode-Textmate 库:[scie](https://github.com/phodal/scie/)。原有的代码中,大量地使用了 OO 相关的东西,而 Rust 要实现 OO 也需要一些奇技淫巧,而我本身对 Rust 也不是非常熟练,所以我写了这一篇笔记来记录如何实现一个复杂的 OO 场景。
至于 Rust 是不是 OO 语言,这个场景我不擅长讨论,我只是需要一个使用 Rust 实现 OO 的场景。不过呢,引用 Rust 官方文档对于 Rust OO 的介绍中,的 GOF 的引用部分:
> 面向对象的程序是由对象组成的。一个对象包含数据和操作这些数据的过程。这些过程通常被称为方法或操作。
PS:我使用 Rust 的时间并不长,里面一些对于相关的描述并非非常准确,如果有不合理地地方,欢迎读者指正。
在这个场景下,主要是通过:
1. Struct 共享数据结构
2. Trait 定义行为
3. Enum 获取实例
简单来说,就是要在 Rust 的继承里,类要被拆分两部分 struct 和 trait,即数据和行为。当我们需要值的时候,从 struct 中获取,当我们需要方法调用、使用时,使用 trait。
## 场景说明
这个场景之下,我们一些语法规则,如(懒得打了,复制一下):
```rust
pub enum RuleEnum {
BeginEndRule(BeginEndRule),
BeginWhileRule(BeginWhileRule),
CaptureRule(CaptureRule),
MatchRule(MatchRule),
EmptyRule(EmptyRule),
IncludeOnlyRule(IncludeOnlyRule),
}
```
他们都需要相同的数据结构 `Rule` 和相同的行为 `AbstractRule`。然后在消费这些 Rule 的过程中,需要根据条件取到 Rule 实例,并调用特定的方法。
## Struct 共享数据结构
首先,来看一下我们的 Rule `struct`:
```rust
#[derive(Clone, Debug, Serialize)]
pub struct Rule {
pub _type: String,
pub _location: Option<ilocation>,
pub id: i32,
pub _name: Option<string>,
pub _content_name: Option<string>,
}
```
对应的每个 Rule 实现,都会使用这个 Rule struct 以实现数据共享:
```rust
#[derive(Clone, Debug, Serialize)]
pub struct BeginEndRule {
#[serde(flatten)]
pub rule: Rule,
pub _begin: RegExpSource,
pub begin_captures: Vec<box<dyn abstractrule="">>,
...
}
```
这里的 `#[serde(flatten)]` 是用于 JSON 序列化的时候,进行扁平化操作。
嗯,就这一点上来说,这里并没有什么特定之处。
## Trait 定义行为
接着,我们使用 Trait 来实现的行为:
```rust
pub trait AbstractRule: DynClone + erased_serde::Serialize {
fn id(&self) -> i32;
fn type_of(&self) -> String;
fn display(&self) -> String {
String::from("AbstractRule")
}
fn get_rule(&self) -> Rule;
fn get_rule_instance(&self) -> RuleEnum;
...
```
为了实现子类的可复制和可序列化,我使用了 `dyn-clone` 和 `erased_serde` 来实现对应的操作。然后,先让我们看个获取 id() 的接口的实现:
```rust
impl AbstractRule for BeginEndRule {
fn id(&self) -> i32 {
self.rule.id
}
fn type_of(&self) -> String {
String::from(self.rule.clone()._type)
}
fn get_rule(&self) -> Rule {
self.rule.clone()
}
```
嗯,对,我们就是直接去 Rule 里面的值。
所以,其实继承就是这么简单。
## Enum 获取实例
随后,在我们的开发过程中,还需要获取真正的 Rule 实现,所以:
1.我们可以定义一个 Rule 相关的枚举:
```rust
#[derive(Clone, Debug, Serialize)]
pub struct Rule {
pub _type: String,
pub _location: Option<ilocation>,
pub id: i32,
pub _name: Option<string>,
pub _content_name: Option<string>,
}
```
2.在 Trait 中创建接口来返回对应的枚举值:
```rust
fn get_rule_instance(&self) -> RuleEnum {
RuleEnum::BeginEndRule(self.clone())
}
```
3.使用它们:
```rust
match capture_rule.get_rule_instance() {
RuleEnum::CaptureRule(capture) => {
}
_ => {}
}
```
嗯,流程大概就是这样了。</string></string></ilocation></box<dyn></string></string></ilocation>『头破血流』学编程语言(Rust 篇)2020-08-31T12:28:40+00:002020-08-31T11:29:42.216493+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/how-to-learn-a-program-language-in-hard-way/学习 Rust 已经有一段时间了,断断续续地在业余时间造了一些轮子。通过这一系列的练习和仿造,对于如何学习新的编程语言有一些新的感悟。这篇文章讲的方式并非是捷径,也不是什么 7 天精通,而是继续使用笨办法地方式来进行学习。
关于编程语言学习,我已经写过两篇相关的文章:
- 『[学习的艺术——如何学好一门技术、语言](https://www.phodal.com/blog/how-to-learn-language-technology/)』,文章的主旨是:介绍如何通过造相似的轮子、进行相关内容输出的方式,来提升对于编程语言的理解。
- 『[如何同时学会两门编程语言?](https://www.phodal.com/blog/how-to-learn-two-languages/)』,介绍的则是用硬核的方法:造语法、词法解析的方式,来掌握新的编程语言。同时,如果我们是对新的编程语言的解析,那么我们就等于学习了两门新的语言。
从我的角度来看,前者的介绍过于简单,只是告诉了你应该这么做,但是没有说要怎么做。而后者则难度太大,对于大部分的人来说,几乎是不会想着去做这样的事情。而本文的难度呢,刚好介于两者之间,至于是不是中间嘛,也不好说。难度,因人而异,因时间也有区别。
对于编程和计算机理解越来越深刻,那么原先难度适中的事情,因为做过会变得更加简单;而原先复杂的事情,如果我们还没做过,那么我们可能还觉得它依然相当的复杂。
## 为什么学习新的编程语言?
工作多年,我们依然会和同事、朋友讨论到:业务是永恒的,技术是永远在变的。所以,成为一个业务专家更容易、更持续,成为一个技术专家更难、更需要持续提升。选择很难,因为我们不是火星人,也没有上帝视角。所以,成为一个技术上的专家,我们需要不断地接触一些新的东西,接受一些新的概念。其中的一种模式便是,人们口中经常说的:**每年学习一门新的语言**。
从个人的角度来看,这是一个非常 SMART (具体、可度量、可实现、相关性、有时限)的目标。所以,它还会存在这么一些优点:
1. 保持学习的习惯。
2. 为技术热情添到香油。
3. 学习不同的编程模式。
4. 拓展职业机会和前景。
除此,从职业鸡汤上来说,就是:机会是留给有机会的人。如果你学习了一门新的编程语言,那么未来有相关的机会,你更有可能触摸到。
若是你将学习新的编程语言,视为非常火热的什么内卷化、奋斗逼之类的说辞,那么我倒是没啥好说的。有的人是真的在 “奋斗”,有的人是想了解各种有意思的东西。从我的角度来看,学习新的编程语言和上述的说辞是不存在关系的 —— 不存在竞争,只是加一条赛道,笑。
## 寻找语言学习的高效路径
在上文中,我提及的第一篇文章《[学习的艺术——如何学好一门技术、语言](https://www.phodal.com/blog/how-to-learn-language-technology/)》在今天对于我来说,已经是一个相当浪费时间的事情 —— 重复劳动。文中提到的方法,无非是造重复的轮子、重写旧的应用,这种方式和诸如在 30 天里去练习不同的项目,都只是在特定的场景之下,出于特定的目标而练习的垃圾产物。随着我们的成长,生活和工作上的一些事情,会占据我们更多的时间。尽管,我尚未被这些问题困扰着,但是我已经有了一个又一个的方案。不过,我相信你们都会有这些问题。
简单来说,我们需要即学好一门编程语言,又不重复劳动。所以,可行的方式是学习新的语言,并在新的编程语言里寻找新的轮子。诸如于《『[如何同时学会两门编程语言?](https://www.phodal.com/blog/how-to-learn-two-languages/)』》就是一种不错的方式,但是对于多数的人来说,它有点难。不过,从个人的角度来看,如果你是选择从一个 XML 解析、JSON 解析开始的话,可能就没有那么难。但是,就是在重复的造轮子。
这么一圈废话下来,其实我们的结论就是:在语言的适合场景下,造适合的轮子 —— 这可能意味着一定的时间成本。比如,用 JavaScript 来处理非关系型数据,用 Go 来开发跨平台命令行工具,用 Rust 来开发 WASM 应用等等。
### 高效路径
在我尝试了一系列的造轮子工作之后,我有了一个初版的模型(基于 Rust 语言)。我暂时划分了四条路径:
1. 工程实施。即使用该语言时,开发应用时需要哪些实践。
2. 应用开发。理解完整的开发应用所需要的知识体系。
3. 框架设计。使用该语言如何进行各种抽象设计。
4. 语言练习。要么用它来写语法解析,要么来解析这门语言。
5. 领域特定编程/场景编程。即寻找适合这门语言的场景。
作为初版,这条路径可能不一定能 match 上你的需要,但是随着我们不断也提升,我们终将能形成一个更完整的路径。
## 工程实施
从工程实施,这个角度来看,我们所要掌握的是一些基本的编程能力:
1. 自动化测试。诸如于单元测试、集成测试等等,以帮助我们开发出高质量的应用,并节省 debug 的时间。
- TDD(测试驱动开发)。同上。从个人的角度来看,若是掌握 TDD 这一项技能,可以编写高质量的代码。
- 测试覆盖率。
2. 持续集成。真实的软件开发需要持续集成,这也是我们学习编程语言时,要掌握的工程技能。
3. 构建管理。寻找适合于这门语言的构建体系,以帮助我们构建出可信的软件。
如我们在使用 Rust 开发应用时,就可以使用 GitHub + Travis CI 的方式完成对于持续集成的了解;结合 Justfile/Makefile 等,完成自动化的构建。
## 应用开发
应用开发是基于真实项目的角度出发,来完成对于语言的练习。这些内容包含了:
1. 自动化部署。主要用于学习在真实项目下,如何提交效率。
- 容器化部署。
2. 分层架构。如何合理的划分项目的目标结构,常见的方式有两种:
- MVC 架构。传统的三层架构
- 整洁架构。基于抽象的形式设计的架构
3. DevOps 体系。根据需要,完成从需求到上线流程的支持。如:
- 应用性能监控。
- 日志。
不同的语言之间,或许存在一些差异,但是从最终的情况来看,它们都需要提供一致性的接口,或者是采用一致性的接口。如对于数据库的访问,使用的接口是一致的;提供 RESTful API,其对于消费者来说,也需要提供一致地 API。
## 框架设计
框架设计是基于造轮子的需求场景下的路径。它包含了:
1. 抽象。语言如何进行抽象
- 支持 OO。
- 不支持 OO。如何使用诸如于 Rust Trait 完成类似的工作
2. 语言无关。如何进行跨语言的设计支持。如:
- 语言无关的数据格式。
3. 模块化开发。如何完成跨团队、跨业务模块的代码、服务共享。
- 包管理/依赖管理。如如何构建,并发布到制品仓库,实现复用。
框架设计从理论上来说是稍微复杂一些。至于有没有必要,就看你想学习到什么程度了。
## 语言练习
语言练习是《[如何同时学会两门编程语言?](https://www.phodal.com/blog/how-to-learn-two-languages/)》模式之下的一种路径方式,相对会陡峭一下。
1. 编写其它语言/DSL 的解析器。
2. 使用其它语言编写该语言的解析器。
3. 使用该语言解析该语言。
嗯,是不是有点意思了。从场景上来说,当我们拿到了一个语言的 AST,然后就可以尝试去做一些高端的事情。如我在 Coca 里做的自动化重构、架构可视化等等。
## 领域特定编程
领域特定编程是在该语言擅长的场景下,做该语言擅长的事情。如 Rust 里的
1. 跨平台
2. WASM
- 一门应用跨端运行
3. 系统级编程
- 结合系统接口,如获取用户输入,并修改输出。
这依赖于我们识别场景,并知晓出什么时候才是合适的场合。
## 其它
没有银弹,如果有的话,那就不需要人类了。六年之后:回到底层编程2020-08-13T05:01:08.286348+00:002020-08-13T05:01:08.323996+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/back-native-programming/一个月以前,我加入到 ThoughtWorks 已经进入了第 7 个年头。我本来不想再花时间写一篇相关的文章。只是呢,最近发生的一些事情,或许和每个 IT 人息息相关。我便想着,再花点时间思考一下:我们的行业以及行业背后的 IT 体系。
从某种程度上来说,我更可能是在弥补一块重要的短板 —— 一来,不是从事相关的领域;二来,平时的开发中真的用得少。
## Web 开发的核心:CRUD?
在我先前的文章《[项目初期的最优技术实践](https://www.phodal.com/blog/short-time-project-best-practise/)》里,我把产品开发的生命周期分为了:
1. 技术准备期
2. 业务回补期
3. 成长优化期
从技术维度来看待的话,我们会发现随着时间的推移,整个项目对于人能力的要求会越来越低,直至大家都变成 CRUD、Copy/Paste 程序员。唯一经常还被提及的难点就是:对于数据库的各种优化,又或者是对于遗留系统的迁移,又或者是对于某个开源软件的优化。
所以啊,我们听过别人提及一个明确的大公司技术发展路线:成为一个技术 + 业务专家。回过头来看,事实也是如此 —— 在大部分的公司里,成为一个技术专家,是一个投入大收益低的事情。对于个人来说,不一定有足够的回报,不一定有用武之地,不一定能找到相匹配的工作;对于组织来说,难以创造更高的价值。
可是呢?对于多数人来说,他们真的还是技术专家吗?如果不写代码的话,他们只是一个**技术决策者**,而技术决策者和技术专家是两种不同的发展路径,只是他们做的某些事情是重叠的。
所以,在 CRUD 之外,我们的一个侧重点还在于对基础设施的优化,而这些基础设施大多数是开源的。
## 开源是否在扼杀技术创造力?
自由软件和开源软件运动,解放了我们在整个行业的生产力。过去,我们关注于如何实现某个功能,现在我们可以转而关注于某个业务的具体实现。
不可避免地,在国内的一些大公司里,受过训练的、编程能力极强的程序员,变成了一个会写代码的业务专家。他们带领着大量的**平均水平**一般的外包程序员,一起编写着业务价值巨大的低质量代码。
这一切看上去似乎非常合理,但是行业总体的创造力在下降。特别是,如果中国的 IT 公司在开源领域的影响力过小时,整个市场都将充斥在水平一般的程序员。
所以,这样一来,我们又不得不考虑一个问题,开源是否在不断降低组织的技术创造力?对于个人来说,可以通过学习造轮子来提升,对于组织呢?
## 阅读源码的本质:解析字符串?
这一年里,受项目及公司大佬的熏陶,我开始寻求以半自动化的方式来理解代码。这其中的一个过程产物就是 Coca,以及 Chapi。往往复复的练习之后,对于类似工作的自动化,有了更充足的把握。
在机器执行一段代码之前,我们要进行一系列的前端转换:词法解析、语法解析、语义分析、生成中间代码,随后再交由编译器后端处理,直至目标程序。而对于人类来说,我们理解的过程是相似的,稍有区别的是,生成的中间代码是人类语言。
而对于一个新的项目来说,我们要做的就是把理解汇制成架构图,又或者是它们之间的调用关系。于是,Coca 和 Chapi 就是用来做这部分工作的自动化 —— 解析字符串。
所以,我一直想写一个工具,用于自动化生成不同语言的代码结构。只是吧,一直缺乏一个明确地用户场景,理论有了,技术有了,就差有人买单了。
## 可执行的背后:二进制?
起先,因为在完成之前的一个 Todo 列表,我便开始折腾写一个 TLV 编码、解码器。TLV,即标识域(Tag/Type)+ 长度域(Length)+ 值域(Value),它是用在通信协议的一种编码格式。
随后,我便开始尝试解析 Java 生成的 class 文件,嗯,我发现做的事情和 TLV 解析,又或者是 Coca 做的语法解析有些类似。Java .class 文件毕竟有固定的格式,所以反而比直接字符串处理更加简单。
紧接着,我继续扩展对于 Java 虚拟机的理解,尝试模仿造一个简单的 JVM,理解整个 Java 应用是如何运行起来的。
直到了,我发现有一个新的领域:Android 对于 Java 二进制的各种骚操作:class 转换为 dex,对 dex 进行优化,dex 转 oat 二进制……。
生活就是这么充满乐趣。
## Go、Rust 成为编程语言的新希望?
作为一个浪漫的程序员,我们无不时刻想着创建一个新的语言/DSL,以丰富我们的业余生活,调节一下 copy/paste 的乏味感。
在陆陆续续地学习 Go 和 Rust,并用它们创造了一系列的工具。我对于创建领域特定语言的事情,又有了一定的感悟 —— 适合的时机,适合的场景,便容易产生适合的语言。
特定的语言解决某一类特定的问题。当然了,新的编程语言还需要有一定体量的公司才能支撑起来。不过,领域特定语言就不需要了,你想干些什么,你就可以轻松上手了。
## 回到底层编程和基础设施
所以,既然大部分 Web 开发都是重复性 CRUD 的。那么回到底层编程、系统编程,偶尔造造系统软件,也是相当的不错。特别是当前,整个市场的大环境对于基础设施的需求,一直在持续不断地提升。
创造和学习是一种乐趣。
嗯,我打算业余生活写写底层代码,进行进行系统编码。编辑器的自制2020-08-05T12:08:04+00:002020-08-05T01:09:01.265417+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/diy-ide-editor/最近的几个月里,我一直在寻找一种合适的方式来构建底层应用。可能是 Web、App 应用已经缺乏了一些新鲜感;也可能是受受国际局势的影响,我开始思考构建底层应用架构的能力。
于是,在我学习了一段时间的 Rust 之后,我便不断地往系统的底层探索。在那之前,另外一门合适的语言大抵是 Golang,不过我偏向于认为 Golang 是一个 Web 应用或者普通应用的开发语言,而非一个系统编程语言。
这其中的一个话题就是:编辑器以及 IDE。毕竟,讨论 Emacs 和 Vim 哪一个更好,已经不能满足我的需求。程序员的乐趣应该在于如此去写一个 Emcas,便实现一个 Vim —— 毕竟 Emacs 操作系统太复杂了。
## 1. 无 UI 式:命令行编辑器
作为过程的第一步,我开始寻找一些合适的编辑器(PS:主要是简单),以作为我的编辑器和 IDE 生涯的第一步。然而,这并不是一件容易的事,毕竟我先前构建 Client 端的经验,都是使用现成的 UI 组件,如 WebView 的 Textarea、Android 框架中的 EditText 组件。
于是乎,我模仿 + 复制 [Iota](https://github.com/gchp/iota) 项目的一部分代码,形成了一份最小可工作的代码,以了解命令行编辑器如何运作的整体原理:
**字符移动**
对于终端编程来说,并不存在组件可以使用,所以我们所要做的事情是:在特定的位置显示特定的字符,如:
```
rustbox.print_char(1, 1, RustBoxStyle::empty(), Color::White, Color::Black, 'A')
```
这样一来,它就在屏幕上的 1,1 的位置画了一个 A,它的前景是白色,背景是黑色。嗯,没错,这种体验就是我大学玩的嵌入式编程。
于是,第一步我们要做的就是读取文本,然后渲染。这里用的库是 RustBox,它封装了 C 语言下的 Termbox。对于一些人来说,更为熟悉的名字可能是 curses,又或者是 GNU 里的 ncurses。在另外的一个 Rust 编写的编辑器 [amp] 中,使用的是 [termion](https://github.com/jmacdonald/amp)。
**快捷键识别**随后,我们可以启动起编辑器,而后做各种事件轮询,等待用户的交互,如快捷键。同样的,这个功能也是由底层的 Termbox 提供了支持,我们只需要创建行为与快捷键的绑定即可。
**状态栏 + 命令模式**。有了上面的基础之后,这个也不会遇到什么困难。
**语法高亮**。这里我就被 iota 这个项目坑了,项目的截图上是有语法高亮的,但是代码上已经删了。回溯了一下过程,发现这部分的功能删了,因为原先的设计并不合理。合理的方式应该是使用 [syntect](https://github.com/trishume/syntect) 这种现成的方案,它使用了 Sublime Text 的语法定义格式。
理解了原理,快速画了个瓢之后,我就转向 UI 式的文件编辑器。
## 2. UI 式:进程分离
随后,我在 Awesome-rust 项目中,物色到了第二个可以项目:xi-editor。Xi Editor 是 Google 员工开源的一款用Rust 语言编写的文本编辑器。从之前的新闻来看,像是火了一段时间,但是好像已经没有那么活跃了。
它最主要的特点有:
**前后端分离**。编辑器分为两部分,后端和前端。后端(即核心部分)负责保存文件缓冲区,并负责所有潜在的昂贵编辑操作。你可以将等价为前后端分离应用的关系,又或者是 Electron 应用中:Electron 的 Node.js 和 WebView 部分的区别。
**JSON RPC**。xi-editor 提供了 JSON 形式的 RPC(远程过程调用)用于前后端之间进行通讯。采取 JSON 的主要原因是减少开发插件的成本,更好的扩大生态。如,我从 UI 上修改编辑器的主题,将通过 RPC 的方式通知后端,并将对应的配置存储到系统中。并且诸如于 IDEA 的索引模式,它应该也会在后台运行,而不占用 UI 进程,影响用户体验。如此一来,我们所面临的卡顿问题,会进一步得到缓解。
**不限 UI**。因为 xi-editor 本身只提供 core 模块,所以,我们可以看到有各种各样的 xi-editor 的前端,如原生 macOS 实现、基于 Electron 实现等等。
所以,我尝试基于这种架构模式,开发了一个基于这种架构模式的系统状态应用 [Stadal](https://github.com/phodal/stadal/)。有了这样的模式,我们就可以分离 UI 进程,提供更好的用户体验。
顺便一题,在这种模式之后,编辑器的模型都统一由后端管理(PS:这一点与 Web 应用是相似的,笑~)。
### Emacs 架构:M-V-C 架构
这样一类比的情况下,Emcas 的架构就好似一个大单体一样。毕竟这是 M-V-C 架构(源自《架构之美》:
- 模型。程序所操作数据的底层描述,如文本属性、缓冲区等等;并与系统进行交互。
- 视图。面向用户展示数据的方法,如对于窗口增量显示更新逻辑等。
- 控制器。负责实现用户与视图的交互(如按键、鼠标事件等),并对模型进行更新(采用 Lisp 作为支撑)
至于插件部分则是由 Lisp 脚本来实现,至于是插件好还是脚本好就是另外一个问题了。
## IDE:IDEA 的插件化
因为偶然地原因,我分析了一段时间的 Intellij IDEA 社区版 + Android Studio 的源码之后,我有了一些新的感受 —— 这个系统架构有点复杂,哈哈。当然,也发现了一些相似的模式。
### 隔离层:独立二进制
对于工具的制造者来说,开发者并不希望工具被捆绑在某一个开发工具上。因此,对于开发者而言,优先做的是提供一个可独立运行的程序,而后再封装一个针对于该工具的实现。
于是乎,这个运行的程序,它可能是:
- C/C++ 编写的二进制应用。通过 daemon 的方式来运行,并能通过解析输出来进行错误处理。
- Gradle 开发的插件。并借助于 Gradle Tooling API 来实现插件的调用。
- Java 编写的应用。通过直接集成的方式进行。
- ……
这样一来,我们就在 IDE 中集成了这样的能力,并引入到我们的系统中使用。
### 语言扩展
IDEA 本身的插件体系已经设计得很完善了,如我们可以快速添加一门语言,只需要:
- 注册文件类型
- 实现 Lexer(词法分析)
- 实施 Parser(语法分析) 和 PSI(程序结构接口)
- 语法高亮显示和错误高亮显示
- 代码补全
- 查找用法
- 重构:重命名、安全删除
- 代码格式化程序
- ……
这样一来,我们就能快速地具备一个语言 IDE 应该有的能力。比如 IDEA 的 Rust 插件就是这样一个不错的示例。
## 总结
一个好的编辑器/IDE 应该能:
- 滋长的特性:通过插件化支撑
- 可维护性:具备良好的可读性
- 进程分离
- 速度
讨论哪个编辑器/IDE 是一件没意义的事。
只有自己挖的坑才是好的。
我行我上。Rust.unwarp(): 编译器驱动开发2020-07-06T12:15:08+00:002020-07-06T05:15:39.986508+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/rust-compile-driven-design/用 Rust 来写个应用,这个想法颇久了。之前呢,要么找不到合适的场景,要么觉得 Rust 门槛有些高。直到最近呢,刚好对底层编程有点想法,便想着用这门语言做点东西玩玩。
考虑到,我用这门语言的时间只有一星期多,某些观点和感受并非那么准确。因此,我的观点并不适合作为一份参考材料。
## Rust 是什么?
让我来 copy 一下
> Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为“安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。 Rust 语言原本是 Mozilla 员工 Graydon Hoare 的私人项目,而 Mozilla 于 2009 年开始赞助这个项目,并且在 2010 年首次揭露了它的存在。
顺便加上 MDN 上的介绍:
Rust 是一个全新的开源系统编程语言,由 Mozilla 和社区的义务劳动者创造,它帮助开发者创造高速与安全的应用,同时能享受到现代多核处理器的强大特性。Rust 使用易懂的语法避免了段错误 (segmentation faults) 并保证了线程安全。
不仅如此,Rust 提供了零成本抽象,更多语义,内存安全保证,不会发生竞争的线程,基于特性 (trait) 的泛型,模式匹配,类型推导,高效的 C 绑定,和最小运行时大小。
所以,就这个简介来说,这个语言已经差不多有十年的历史了。
## Rust 优点
开始之前,我仍然是要强调一下的,这里的优点是对比我所学的其它语言而言的,如 Go、Java、Kotlin、TypeScript或者其它。
### 底层语言 && 系统编程语言
我正在寻找一门不是那么复杂的底层编程语言,以陪我完成一些更有意思的工作,而且还不需要那种 “指向指针的指针”。就这方面来说,Go 是一门不错的语言,但是没有 OO。哦,不对,对于 Go 来说的,它的 OO 应该是:**Yes and No**。
从某种意义来说,Go 也有类似的潜质,面向的也是类似的人群。然而,从现有的情况来说,Go 更像是面向网络编程,而非系统编程。
### 编译器驱动
我记得我听闻到的一个关于 Rust 的观点是:只要编译成功,基本呢,不会出错。比如烦人的内存泄漏之类的问题(当然还是会有一些的,只是要写出来并不是那么容易)。
编译器内建了强大的纠错功能。它把我们在运行时遇到的问题,提前到了编译时。也因此,相比于其它语言,它可能会降低你的开发速度。
从某中意义上来说,它相当于是内置了一个 Sonarqube、Findbugs 这一类的 SAST(静态应用程序安全测试)工具。并在编译时失败,以强迫你修复潜在的漏洞。
这其实是个缺点,哈哈哈。
### 交叉编译
在 Go 一样,在这一点上远远比 C/C++ 还是优秀。
### 包管理 + 构建
在几个底层语言里,C/C++、Go、Rust 里,几乎只有 Rust 的包管理是好用的。虽然说 Go 也有,但是就是渣啊。为了使用方方便,我基本选择的是拷贝,而不是用 go mod。
与此同时,我们还可以拓展 Cargo 的功能,以进行更多的操作。
==,Go 有构建工具吗?可能是 Makefile 吧
### 和 Web 的无缝结合
是的,作为一个追求跨平台的开发人员,我特别看好 Rust 的两个 Web 相关的方向。
- 高性能 Web。Rust + WASM,
- 更高性能地跨平台应用。Rust + Electron + Node.js,结合 Neon Binding,可以编译为 Node.js 的模块,并在 Electron 应用中调用,开发跨平台桌面应用。
在一切都云化之后,它的这一点特质将会越来越重要。
### 编程语言优点
从社区来说,它还有这么一些优点:
- 优秀的 Macro 宏定义机制
- 可 OO。基于 Traits 的简洁而强大的范型系统
- 错误处理。基于 `Option & Result` 的空值和错误处理
- 防 OOM。基于 Ownership、Borrowing、Lifetime 的内存管理机制
## 缺点
从上手难道上来说,我觉得 Rust 是比 Go 更加复杂的。
### 学习成本 + 处理更多的细节
大抵这是一门系统集成编程语言,对于原先我们使用的那些编程语言来说,原先的这些事都是由 bug 和编译器来体现。于是乎,我们要处理更多的细节。
Rust 的诸多语法,都有些不合直觉。除此,Rust 还有一个功能非常强大的宏(macro)系统。嗯,每多一个特性,就多一点点的复杂度。
### 复杂的所有权机制
Rust 引入了所有权的概念和工具,以在没有垃圾回收机制的前提下保障内存安全。这是一个相当复杂的概念——主要是在其它语言中都没有。
一个非常有意思的例子就是对于字符串的操作。
```rust
let mut owned_string: String = "hello ".to_owned();
let borrowed_string: &str = "world";
owned_string.push_str(borrowed_string);
```
嗯,对,处理一个字符串,你都要小心翼翼的。所以,我还是用 format 来做这样的事情:
```rust
format!("{}{}", owned_string, another_owned_string);
```
### 缺少支持完备的 IDE
Rust 并非没有可用的 IDE,而是还没有像 Intellij IDEA 那么完备的 IDE。
我使用 Clion + Rust 插件来开发应用,但是它并非非常完美 —— 主要是,我依赖于 IDE 来进行重构,以及借助于 IDE 的智能提醒。
## 其它
当前,我正在写的两个 Rust 应用:
[phodal/exemd](https://github.com/phodal/exemd): 一个简单的 Rust 应用,读取 markdown 文件,解析其中的不同编程语言,并执行。
[phodal/stadal](https://github.com/phodal/stadal): 参考(复制) Google 的 xi-editor 的 json RPC + 进程分离架构而设计的系统监视器。Stadal 使用 Rust 开发核心,使用 Electron 开发界面。
欢迎入坑讨论学习。