2020年4月21日火曜日

インメモリデータベースの開発言語にRustを選んだ理由。Why did you choose Rust as your in-memory database development language?

https://www.forcia.com/blog/001167.html
シェアしました。


QiitaのRust Advent Calendar 2018 4日目の記事です。
技術本部の松本です。フォルシアではインメモリデータベースをRustで開発して
います。本記事では、なぜRustを選んだかをご説明します。

速度

Rustは2015年に1.0がリリースされた比較的新しいプログラミング言語であり、
「速度、安全性、並行性」をゴールとしています。
言語の選定にあたっては、動作速度が重要視されました。
Computer Language Benchmarks Game(ベンチマーク結果を公開している
サイト)によれば、RustはJavaやGoより高速で、C++並の速度が出ると
言われています。実際に我々が検証した際も、RustはGoよりは高速で、
Javaと異なりGCが無いため、Rustの方が好ましいという結果になりました。

ガベージコレクション(GC)が無い

Rustでは所有権の概念を取り入れることで、いつメモリ上からオブジェクトが
解放されるか、管理しています。そのためGCが必要なく、
GCによるプログラムの停止を避けることが出来ます。
技術検証時にJava実装を検討しましたが、GCが走るタイミングで
レスポンスに時間がかかることがあり、安定して高速なレスポンスを
返すインメモリデータベースの用途上、Rustに軍配が上がりました。

静的ディスパッチによるゼロコスト抽象化

Rustがなぜ速いのかという文脈で「ゼロコスト抽象化」というキーワードに
言及される事が多いですが、少し噛み砕いて説明したいと思います。
Rustではジェネリクス(generics)とトレイト(trait)によって
多相性(Polymorphism)をサポートしています。
ジェネリック関数は同じ名前の関数に異なる型を渡すことができます。
下記に例を示します。
  1. use std::fmt::Debug;
  2. fn main() {
  3. fn print_debug(x: T) {
  4. println! {"{:?}", x};
  5. }
  6. print_debug(1u8);
  7. print_debug("&str");
  8. #[derive(Debug)]
  9. struct Point {
  10. x: i32,
  11. y: i32,
  12. }
  13. let origin = Point { x: 0, y: 0 };
  14. print_debug(origin);
  15. }
ジェネリック関数(上記例ではprint_debug)を用いた場合、
コンパイル時点で引数の型が全て明らかになっている(u8, &str, Point)ため、
それぞれの型専用の関数が作成されます。
実行時に型から関数への割当関係を解決する動的ディスパッチを行う場合は
実行時にその分のコストが掛かりますが、Rustはコンパイル時に
解決してしまう静的ディスパッチを使用するため、
実行時にはゼロオーバーヘッドで関数を対応する呼び出す事ができます。

開発効率

また、開発効率の面も考慮事項でした。Rustは新しい言語ではあるものの、
他の言語以上に効率良く開発できる環境が整っています。
標準的なツールセットがあることや、テストが標準ライブラリに含まれている
ことはC++よりも好ましい部分です。

rustupによる簡単な環境構築

Rustは環境構築が驚くほどに簡単です。rustup.rsにアクセスし、表示された
コマンドを実行するだけでRustを使い始めることが出来ます。
RustのパッケージマネージャであるCargoですが、パッケージ管理だけでなく、
プロジェクトの作成からテスト、ビルド時の設定まで一貫して管理してして
くれるため、いろいろなツールを使い分ける必要がありません。
もちろんrustupでインストールされます。

コーディング時のサポート

  • Rust Language Server (RLS)が開発されており、コーディング面で
    補完などのサポートが受けられます
  • rustfmtで統一的なコード整形が可能です
上記のツール群も日々アップデートされており、不便なくツール群の恩恵に
預かっています。

エラーメッセージが親切

Rustのコンパイラの特徴として、非常に親切なメッセージが挙げられるます。
所有権やライフタイムといった馴染みの薄い概念があるため、完璧なコードを
一撃で書くことはまだ難しいのですが、基本的に「どこを」「どう」
修正すれば適切なアノテーションができるかを、
コンパイラがエラーメッセージとして表示してくれます。
コンパイラのサポートを受けながらコードを書いていくことが出来ます。

言語標準のテスト

Rustは言語の標準機能としてテストが用意されており、cargo testのコマンドで
実行することが出来ます。テスト用のライブラリ群をインストールして......
といった作業が無いのは嬉しいことです。
cargo new --libした際に作られるスケルトンには、テストケースのスケルトンが
含まれています。すぐにテストを書かないとという気分にさせてくれますし、
実装とテストが同じファイルに書けるため、テストが仕様の役割を果たし
やすくなります。docコメント中のテストも実行されるので、
実装とコメントが乖離することも防ぎやすくなっています。
また、テストはファイルごとに並列で処理されるため、
プロジェクト全体のテストを行う際も高速にテストが可能です。

学習コスト

Rustのデメリットとして、初期の学習コストの高さが挙げられます。
実際、借用(・所有権)・ライフタイムの概念は馴染みが薄く、
ドキュメントを読みコンパイラと格闘しながら学習していくことになりました。
確かに馴染みの薄い概念ではなるものの、ユーザーとして使う分には難しい
概念ではないため、職業プログラマが仕事として向き合ってしまえばコンパイラの
サポート化で問題なく利用できるレベルだと考えています。 文法に関して
「変数の型はアノテーション風に書く※」「ブロックの末尾の式が返り値になる」と
念じれば、他の手続き型言語と大きな違いはありません。
先日の勉強会で「Rustは関数型風の文法が書ける手続き型言語」と
おっしゃっていた方がいらっしゃいました。
実際手続き型言語に馴染みのある人間からすると、
パターンマッチなど関数型言語に特徴的な機能を使うことができる
手続き型言語という風に見え、文法部分の学習コストは大きくありません。
フォルシアでは概念と基本文法を理解するためのハンズオンを社内で行い、
Rustが書けるエンジニアを増やそうと試みています。
※JavaScriptのFlowやPythonでアノテーションを書いたことがある人であれば、
いつもの位置に書くだけです。

さいごに

以上の理由から、フォルシアでインメモリデータベースを作る際に、
速度と開発効率の面でRustは良い選択肢になりました。
2019年1月16日に予定しているShinjuku.rs #2ではデモをお見せできそうです。
この記事を書いた人

松本健太郎

フォルシア3年目のエンジニア。
インメモリデータベースの開発を行っている。
https://www.forcia.com/blog/001167.html.

Shared.



2018.12.04

Advent Calendar 2018Rust Technology

This is the fourth day of Qiita's Rust Advent Calendar 2018.
I'm Matsumoto from the Engineering Division. At Forcier, we developed an in-memory database in Rust and used the
It is there. In this article, we'll explain why we chose Rust.

speed
Rust is a relatively new programming language, with 1.0 being released in 2015, and
The goal is "speed, safety and parallelism.

In selecting the language, speed of operation was an important factor.
Computer Language Benchmarks Game (Benchmark results are available)
According to (the site), Rust is faster than Java or Go and can be as fast as C++.
It has been said. In fact, when we tested it, Rust was even faster than Go.
Since there is no GC, unlike Java, Rust is preferable.

Garbage Collection (GC) is missing.
Rust introduces the concept of ownership, so that when an object leaves memory, the
They are released or managed. This eliminates the need for GC.
It is possible to avoid stopping the program by GC.
We considered Java implementation at the time of technical verification, but the timing of GC run
Response may take a long time, and a stable and fast response
For the use of an in-memory database to return, Rust has the upper hand.

Zero-cost abstraction with static dispatching
To the keyword "zero cost abstraction" in the context of why Rust is so fast
It's often mentioned, but I'd like to chew it up a bit and explain it.

In Rust, we use the word "generics" and "trait" to describe
Polymorphism is supported.
A generic function can pass different types to a function of the same name.
An example is shown below.

Use std::fmt::Debug. fn main() {
std::fmt::Debug; fn main() {
    fn print_debug(x: T) {
        println!" {"{:?}" , x};
    }
    print_debug(1u8);
    print_debug("&str");
    #[derivative(Debug)]
    struct Point {
        x: i32,
        y: i32,
    }
    let origin = Point { x: 0, y: 0 };
    print_debug(origin);
}
Let's run it in Rust Playground.

When using a generic function (print_debug in the above example), the
Since all argument types are known at compile time (u8, &str, Point), the
Each type-specific function will be created.
If you want to perform dynamic dispatching that resolves the type-to-function allocation relationship at runtime, use the
At runtime, there is a cost, but Rust does not use the compile-time
Because of the use of static dispatching that resolves
At runtime, the function can be called with zero overhead.

development efficiency
Although Rust is a new language, it's also a consideration in terms of development efficiency.
We have an environment that allows us to develop more efficiently than any other language.
There is a standard toolset and testing is included in the standard library.
is the preferred part over C++.

Simple environment construction with rustup
Rust is surprisingly easy to build an environment, just go to rustup.rs and use the
You can start using Rust by simply executing the

Cargo is Rust's package manager, but it's not just package management.
Manage your projects from creation to testing and configuration at build time
This eliminates the need to use different tools.
Of course it will be installed with RUSTUP.

Coding support
The Rust Language Server (RLS) has been developed, and the coding side of
Complementary and other support is available
Unified code formatting is possible with rustfmt
The above tools are also updated daily, and you can benefit from them without any inconvenience.
It's in my care.

Friendly error messages.
One of the features of Rust's compiler is a very kind message.
Due to unfamiliar concepts such as ownership and lifetimes, the perfect code
It's still hard to write in a nutshell, but it's basically a "where" and "how
to make sure that proper annotations can be made with modifications.
The compiler will show it to you as an error message.
You can write your code with the support of a compiler.

Testing Language Standards
Rust provides testing as a standard feature of the language, and you can use the cargo test command to
It is possible to do this. I'm glad I don't have to install a set of test libraries....
to install the test libraries.
The skeleton created when cargo new --libbing contains the test case skeleton in
It includes. It makes me feel like I need to write a test right away, and it's
Since implementation and testing can be written in the same file, tests play a role in the specification and
Since the test in the doc comment is also executed, it is easier to use
It's also easier to prevent the implementation and comments from diverging.
Also, since tests are processed in parallel on a file-by-file basis, the
You can also test fast when testing an entire project.

learning cost
A disadvantage of Rust is the high initial learning costs.
In fact, the concepts of borrowing (...ownership) and lifetimes are unfamiliar.
I had to learn by reading the documentation and wrestling with the compiler.
It is true that it is an unfamiliar concept, but it is difficult to use it as a user.
Because it is not a concept, once a vocational programmer is confronted with it as a job, the compiler's
We think it's at a level where we can use it without any problems with support. Regarding grammar
The type of the variable is written in an annotated style* and the expression at the end of the block is the return value.
If you think about it, it's not much different from other procedural languages.
The other day, at the study group, he said "Rust is a procedural language that can write functional grammar".
One of the people said.
In fact, for those familiar with procedural language, the
You can use features that are characteristic of functional languages, such as pattern matching.
It appears to be a procedural language and the cost of learning the grammar part is not significant.
At Forcier, we provide in-house hands-on instruction in understanding concepts and basic grammar.
We're trying to get more engineers who can write Rust.

If you have written annotations in JavaScript's Flow or Python, you can use the
Just write in your usual position.

in the end
For the above reasons, when creating an in-memory database in Forcia, the
In terms of speed and development efficiency, Rust has become a good choice.
I'll be able to show you a demo at the upcoming Shinjuku.rs #2 on January 16, 2019.

The person who wrote this article.
Kentaro Matsumoto
Forcier 3rd year engineer.
We are developing an in-memory database.

Translated with www.DeepL.com/Translator (free version)

0 コメント:

コメントを投稿