(p: P) {
let p = p.to_cartesian();
println!("({}, {})", p.0, p.1);
}
fn main() {
let p = Polar(1.0, 0);
// Debugをderiveしたので印字できる
println!("{:?}", p);
// PartialEqをderiveしたので比較できる
p == p;
// ToCartesianを実装したので `print_point` に渡せる
print_point(p)
}
```
===
# 普通のRust
------------
* 割と型検査で事前に不正なコードを弾く
* テンプレートと違ってジェネリクス定義時点で型が合ってないとコンパイルできない
+ 以下のコードはこれを書いた時点でコンパイルエラー
```
fn print_point(p: P) {
let p = p.to_cartesian();
println!("({}, {})", p.0, p.1);
}
```
===
# 普通のRust
------------
* 便利な `enum` (代数的データ型)
* [`Ordering`](https://doc.rust-lang.org/std/cmp/enum.Ordering.html) も便利
* `impl` ブロックでデータ型にメソッドを生やせる
[run](https://is.gd/Cpgdpo)
```rust
enum Color {
Red,
Black,
}
enum Tree {
Leaf,
Node {
color: Color,
l: Box>,
v: T,
r: Box>,
},
}
use std::cmp::Ordering;
impl Tree {
fn is_member(&self, t: &T) -> bool {
use Tree::*;
match self {
Leaf => false,
Node { l, v, r, .. } => match t.cmp(v) {
Ordering::Less => l.is_member(t),
Ordering::Equal => true,
Ordering::Greater => r.is_member(t),
},
}
}
}
```
===
# 普通のRust
------------
* [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) 型とパターンマッチはエラーハンドリングに便利
+ Rustに例外はない。
* 「例外が上がる」という概念ではなくてデータ型という第一級の値で表現することで扱いやすさが向上
+ メソッドを生やしたりできる
```
use std::io::{self, Write};
use std::fs::File;
fn write_string(filename: &str, content: &str) -> io::Result<()> {
let mut file = match File::create(filename) {
Ok(file) => file,
Err(e) => {
eprintln!("an error occured when opening file: {}", e);
return
}
};
match file.write_all(&content.as_bytes())? {
Ok(file) => (),
Err(e) => {
eprintln!("an error occured when writing to file: {}", e);
return
}
}
Ok(())
}
```
===
# 普通のRust
------------
* 早期リターンする記法もある
* `Result` (または`Option`) 型に `?` 演算子でエラーなら関数から返る
```
use std::io::{self, Write};
use std::fs::File;
fn write_string(filename: &str, content: &str) -> io::Result<()> {
let mut file = File::create(filename)?;
file.write_all(&content.as_bytes())?;
Ok(())
}
```
===
# 普通のRust
------------
* UNIX APIの便利ラッパー
* パターンマッチに便利な仕組み
```rust
use nix::unistd::{fork, ForkResult};
fn main() {
match fork() {
Ok(ForkResult::Parent { child, .. }) => {
println!("Continuing execution in parent process, new child has pid: {}", child);
}
Ok(ForkResult::Child) => println!("I'm a new child process"),
Err(_) => println!("Fork failed"),
}
}
```
===
# 普通のRust
------------
* Cargo
+ 便利なビルドツール/パッケージマネージャ
+ プラグインの仕組みもある
+ 今回は `cargo-edit` を使ってる (`cargo install cargo-edit` で入る)
```console
$ cargo new fork-example
$ cargo add nix
$ cd fork_example
$ edit src/main.rs
$ cargo run
```
===
# Why Rust (over C/C++)?
------------------------
* 安全
+ セキュリティ的な嬉しさ
+ 開発面での余計なデバッグの不要
* 生産性が高い
+ 便利な機能があることと低レイヤが扱えることは両立する
+ 例えば最近入った `async` / `await` はOSがなくても動く
* Cargo(ビルドツール) + crates.io(パッケージレジストリ)が便利
* 活発なコミュニティ
===
# 速度と機能の話
--------------
* [Why is Rust slightly slower than C?](https://github.com/ixy-languages/ixy-languages/blob/master/Rust-vs-C-performance.md)
+ ネットワークドライバを各言語で実装してみる実験
* RustはCより少しだけ遅い。でもIPCはRustの方が断然いい。
* → CはCPUを使ってる気になってるけど使いきれてないのでは?
* → CPUも進化してるんだから言語も進化しましょう
===
# Rustの安全性について
-----------------------------
安全 ≒ 未定義動作を踏まない
* NULL Pointerはない。
+ 令和にもなってセグフォのデバッグはしたくない
* use after freeができない
* 配列の範囲外アクセスが検査される
* データ競合(data race)が起きない
+ ≒ 多数のスレッドから1つのデータに同時にアクセスできない
* 競合状態(race condition) は防げないので注意
+ デッドロックとか
===
# 範囲外アクセス
----------------
* (他の安全性とは違って)範囲外アクセスは実行時に検査される
+ 範囲外アクセスを静的に弾くのはかなり難しいことが知られている
```rust
let v = vec![1, 2, 3];
// コンパイルは通るけど実行時にパニック
v[3]
```
```console
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3', /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/slice/mod.rs:2717:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
```
===
範囲外アクセス
- ただし固定長配列に定数でアクセスする場合はコンパイルエラーにしてくれる
let v = [1, 2, 3];
// コンパイルエラー
v[3]
```console
error: index out of bounds: the len is 3 but the index is 3
--> src/main.rs:5:1
|
5 | v[3];
| ^^^^
|
= note: `#[deny(const_err)]` on by default
```
===
所有権
{
let data = Data::new();
// `data` のライフタイムはこのスコープ
} // ← ここで `data` がfreeされる
// ここでは `data` にアクセスできない
===
所有権
let data = Data::new();
// ここで `data` の所有権が `tmp` に移る
let tmp = data;
// 以降 `data` にアクセスするとコンパイルエラー
// tmpが死ぬと `data` はfreeされる
===
所有権
fn take_data(_: Data) {}
let data = Data::new();
take_data(data);
// 以降 `data` にアクセスするとコンパイルエラー
// take_data(data);
===
所有権
===
借用
- データを一時的に「貸す」こともできる
&
で参照を取ると貸すことになる
fn borrow_data(_: &Data) {}
let data = Data::new();
// 貸す
borrow_data(&data);
// 返してもらったらまた使える
borrow_data(&data);
===
借用
&
で参照を取ると貸すことになる
- 参照はポインタの意味もあるけど普段はあんまりポインタとしては意識してない
- カジュアルに構造体の値渡しをする
- 「借用するかどうか」で使い分ける
- 所有するポインタ (
Box
) もあるけどたまにしか使わない
===
借用とライフタイム
- 借りたデータを元のデータより長く生かせない
- 長く生かそうとするとコンパイルエラー
// このコードはコンパイルエラー
fn new_data() -> &Data {
let data = Data::new();
&data
// data はここで死ぬので関数から返せない
}
===
借用と変更
int
func(int *a, int *b)
{
*a = 0;
*b = 1;
return *a;
}
===
借用と変更
- 以下の関数の返り値は?
&mut
は可変ら参照を表わす
- Cでいう普通の
&
fn func(a: &mut i32, b: &mut i32) -> i32 {
*a = 0;
*b = 1;
*a
}
===
借用と変更
===
借用と変更
run
let v = vec![1, 2, 3];
for e in &v {
v.push(*e);
}
```console
error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable
--> src/main.rs:4:9
|
2 | let v = vec![1, 2, 3];
| - help: consider changing this to be mutable: `mut v`
3 | for e in &v {
4 | v.push(*e);
| ^ cannot borrow as mutable
```
===
Nullableな値
- 全てのポインタがNullableなのは酷いけどNullがないのも不便な気がする
- ポインタとは関係なくNullableであることを表わすデータ型を用意して解決
i32
とかもnullableにできる
- メソッドを生やせる
- Option
Option<Pointer>
は最適化でただのポインタになる
pub enum Option<T> {
None,
Some(T),
}
===
所有権の例外
i32
とか小さい型をいちいち貸し借りしたくない
- そういう型は無制限に使える仕組みがある
Copy
トレイトを実装した型は勝手にコピーしてくれる
- よくRustで所有権を試そうとしてる人がはまりがち
fn take_i32(_: i32) {}
let a = 1;
take_i32(a);
// 何度でも呼べる
take_i32(a);
take_i32(a);
===
所有権とか
- 正直スライドだけでは伝えきれない
- 理解しようとすると公式ドキュメントを読むのがよい
- 雑にまとめると
- データには所有者がいる
- ポインタは実体があると保証できる範囲でしか作れない
- NULL poinerやdangling pointerは存在しない
- Writeは排他
===
所有権とか
- 結構アプリケーションの設計に関わってくる
- 雑な設計だとすぐに破綻する
- それゆえ難しいといわれがち
- 所有者を意識すると綺麗になりがち
- 長寿のデータ型に持たせる
- データ構造は所有者になりがち
- 処理のフローを考えると余計なコピーを省ける
- HTTPの場合は
Request
の生存期間で十分だったり
===
所有権小話1
HashMap
はデータを所有するので get
/ get_mut
だとデータを借りることしかできない
- 取り出したいのが不変の参照か可変の参照かでメソッドが分かれてるのが普通
- データを取り出したいときは
remove
を使う
run
use std::collections::HashMap;
let mut map = vec![
(1, "one".to_string()),
(2, "two".into()),
(3, "three".into()),
]
.into_iter()
.collect::<HashMap<_, _>>();
match map.remove(&3) {
None => println!("no data"),
Some(data) => println!("got data: {}", data),
}
===
所有権小話2
- Rustにリソースを開放するAPIはない
File
の open
はあるけど close
はない
Lock
の lock
はあるけど free
はない
- RAIIで管理されるのでデータの
free
のときに一緒に開放される
run
use std::io::{self, Write};
use std::fs::File;
fn write_string(filename: &str, content: &str) -> io::Result<()> {
// Fileをwriteモードでopen
let mut file = File::create(filename)?;
file.write_all(&content.as_bytes())?;
Ok(())
// fileがスコープを抜けるときに自動でcloseされる
}
===
Rustの進歩
- Rustは問題がある「かもしれない」コードを弾く
- 不思議な力で安全になる訳ではなくて、養成ギプス的にユーザに安全なコードを書かせる
- 安全にはなるが窮屈
- 極端な話、全てのコードを弾けば実行時エラーは出ない
- Rustの進歩でコンパイルが通る範囲もちょっとづつ広がっている
===
Rustの進歩
- 昔は以下のコードがコンパイルできなかった
- 最近は制御フローまで見て問題なければコンパイルを通す
run
use std::collections::HashMap;
fn insert_or_update(map: &mut HashMap<i32, i32>, key: i32, value: i32) {
// get_mutで可変の参照
match map.get_mut(&key) {
Some(v) => *v = value,
None => {
// その参照が生きている間に更新
map.insert(key, value);
},
}
}
===
所有権をopt out
- 所有権は便利だけどそれだと書けないデータ構造が発生する
- そういう場合に実行時に所有権/ミュータビリティ検査をするAPIがある
===
所有権をopt out
run
use std::rc::Rc;
use std::cell::RefCell;
let data = Rc::new(RefCell::new(1));
let data2 = data.clone();
// data2を変更。 data2はイミュータブルだが
// `RefCell` なので変更できる
*data2.borrow_mut() = 2;
// `Rc` なのでdata2の変更がdataにも反映される
assert_eq!(*data.borrow(), 2);
===
並列
- Rustはスレッドセーフでないプログラムをマルチスレッドで使うとエラーにする
- 例えば
Rc
はスレッドアンセーフ(裏でcountの変更があるため)
run
use std::rc::Rc;
use std::thread;
use std::cell::RefCell;
let data = Rc::new(RefCell::new(1));
let data2 = data.clone();
let handle = thread::spawn(move || {
*data2.borrow_mut() = 2;
});
handle.join().unwrap();
assert_eq!(*data.borrow(), 2);
```console
--> src/main.rs:9:14
|
9 | let handle = thread::spawn(|| {
| ^^^^^^^^^^^^^ `std::rc::Rc>` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `std::rc::Rc>`
= note: required because of the requirements on the impl of `std::marker::Send` for `&std::rc::Rc>`
= note: required because it appears within the type `[closure@src/main.rs:9:28: 14:2 data2:&std::rc::Rc>]`
```
===
並列
- スレッドセーフなAPIにしたり
Mutex
を使ったりするとコンパイルが通る
Arc
= Atomic Reference Count
Mutex
= mutual exclution、要はロック
run
use std::sync::{Arc, Mutex};
use std::thread;
let data = Arc::new(Mutex::new(1));
let data2 = data.clone();
let handle = thread::spawn(move || {
*data2.lock().unwrap() = 2;
});
handle.join().unwrap();
assert_eq!(*data.lock().unwrap(), 2);
===
並列の安全性の舞台裏
- トレイトで制御している
thread::spawn
に渡せるのは Send
トレイトを実装した型のみ。
Rc
や RefCell
は Send
トレイトを実装していない
- →
Rc
や RefCell
を渡そうとするとコンパイルエラーになる
- ドキュメントを読まなくてもスレッドセーフか分かるの素敵
===
安全性を捨てるとき
- Rustを使ってても安全性を捨てないといけないケースがある
- Cと連携するとき
- データ構造を実装するとき
- ハッシュテーブルみたいに未初期化かもしれないメモリを扱うとき
- そういうときのエスケープハッチがある
unsafe
な部分とsafeな部分を区別する仕組みがある
===
安全性を捨ててみる
run
use std::ffi::c_void;
use std::ptr::null_mut;
extern "C" {
// FFIの関数のプロトタイプ宣言
// 参照とは別のマジのポインタ型
fn free(p: *mut c_void);
}
fn main() {
// unsafeで囲むとやりたい放題
unsafe {
// ヌルポが作れる!!
let p: *mut i32 = null_mut::<i32>();
// ヌルポに書き込める!!
*p = 1;
// freeできる!!
free(p.cast());
// use after freeできる!!
println!("{}", *p);
}
}
===
unsafe
の仕組み
- Rustはいくつかの操作や関数を
unsafe
とみなす
unsafe
な操作は unsafe
の内側でしかできないようになっている
run
use std::ptr::null_mut;
fn main() {
// ヌルポインタを作るだけならsafe
let p: *mut i32 = null_mut::<i32>();
// ポインタに触るのはunsafe
*p = 1;
}
```console
error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
--> src/main.rs:7:5
|
7 | *p = 1;
| ^^^^^^ dereference of raw pointer
|
= note: raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior
```
===
unsafe
の使いどころ
- 基本は使わない。
- どう頑張っても
unsafe
を使わないと実装できないものは仕方なく使う
- データ構造の実装に多い
- 標準ライブラリの
Vec
とか
- めちゃくちゃ速度が重要で、
unsafe
を使うとすごく高速化できる場合にはトレードオフを考えて使う
- C FFIのラッパを書くときはまあ、仕方ない
===
活発なコミュニティ
- 「技術的投資」というならその資産のグロースも考えよう
- stack overflowの最も愛されている言語
- Microsoftも導入に乗り気
- パッケージのセントラルレポジトリ(crates.io)がある
- 30,000+ クレート
- 参考: rubygemsは150,000+ gems
===
コミュニティ中心
- コミュニティベースの開発
- 「Mozillaの言語」ではない。
- Mozilaが初期から支援しているだけ。今はAWSとかもサポート。
- 開発はチームによる。チームの会議もDiscordなどで公開
- コミュニティから意見を吸い上げる→開発チームがロードマップを出すのサイクル
===
まとめ
- Rustは安全なシステムプログラミング言語だよ
- 普通にプログラミング言語としても便利だよ
- コミュニティに勢いがあるよ
- お試しとしても新天地としても良い言語なんじゃないでしょうか