Rust 事始
Install
PATH
$ bat ~/.config/fish/config.fish
set -gx PATH $HOME/.cargo/bin $PATH
IDE
Start project
cargo new --lib x-rust
Directory structure
src
├── hello_world.rs
├── lib.rs
└── main.rs
- Rust コンパイラの開始点をクレートルート (crate root) という
- lib.rs は特殊なファイルで、存在する場合はライブラリパッケージとして扱われ、クレートルートとなる
- lib.rs に
pub mod hello_world;
と書けば、hello_world.rs ファイルがモジュールとして扱われ、main.rs から参照できる
Code
pub fn hello() {
println!("{0}, {1}! {0}! {0}!", "hello", "world");
}
pub mod hello_world;
mod hello_world;
fn main() {
crate::hello_world::hello();
}
hello, world! hello! hello!
単体テスト
実装コードと同じファイルに test 用の注釈をいれて書く
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(10, -1), 9);
}
}
Rust Playground でのテスト実行
Rust Playground でテストを動かす場合は、上記のコードでもそのまま動くが、下記のようにモジュールなしでミニマムに書いても良い
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(10, -1), 9);
}
配列と For 文
実用上 Java では List、JavaScript と Ruby では配列、Go では Slice を使うが、Rust では std::vec::Vec
を使う
語源は理解しつつも省略して Vec と定義されているから何気に「ベク」と呼んでしまいそうだが「ベクタと発音する」と公式ドキュメントに明記されている
A contiguous growable array type, written as Vec<T> and pronounced ‘vector’.
使い方
初期化して push して remove
fn main() {
let mut v = Vec::new();
v.push(10);
v.push(20);
v.push(30);
println!("{:?}", v);
v.remove(1);
println!("{:?}", v);
}
[10, 20, 30]
[10, 30]
-
mut
で宣言しないと変数を変更できない- JavaScript は
const
で宣言すると変数の上書きは不可だが中身は変更可
- JavaScript は
- std ライブラリの型は
{:?}
指定で出力可能
リテラルで初期化したい場合のためにマクロが用意されている
let nums = vec![10, 20, 30];
For 文は 1 種類しかない
Go だとスライスを最初から最後まで走査する場合は
func main() {
var nums = []int{1, 2, 3, 5, 7}
for i, v := range nums {
fmt.Printf("index: %v, value: %v\n", i, v)
}
}
任意にループさせたい場合は C 言語由来の
func main() {
var nums = []int{1, 2, 3, 5, 7}
for i := 0; i < len(nums); i++ {
fmt.Printf("index: %v, value: %v\n", i, nums[i])
}
}
という使い分けができるが Rust には前者しかないのでびっくりした
fn main() {
let nums = vec![1, 2, 3, 5, 7];
for n in nums {
println!("{}", n);
}
}
インデックスが欲しい場合は
fn main() {
let nums = vec![1, 2, 3, 5, 7];
for (i, v) in nums.iter().enumerate() {
println!("index: {}, value: {}", i, v);
}
}
どちらの書き方も Vec のインデックスを一巡するので融通が利かないが、以下のように Range を使う書き方なら開始位置をずらしたりできる
fn main() {
let nums = vec![1, 2, 3, 5, 7];
for i in 1..nums.len() {
println!("index: {}, value: {}", i, nums[i]);
}
}
Rust 自体のアップグレード
$ rustc -V
rustc 1.52.1 (9bc8c42bb 2021-05-09)
$ rustup update
info: syncing channel updates for 'stable-x86_64-apple-darwin'
info: latest update on 2021-06-17, rust version 1.53.0 (53cb7b09b 2021-06-17)
info: downloading component 'rust-src'
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
16.1 MiB / 16.1 MiB (100 %) 7.5 MiB/s in 2s ETA: 0s
info: downloading component 'rust-std'
24.4 MiB / 24.4 MiB (100 %) 7.7 MiB/s in 3s ETA: 0s
info: downloading component 'rustc'
58.6 MiB / 58.6 MiB (100 %) 5.9 MiB/s in 16s ETA: 0s
info: downloading component 'rustfmt'
info: removing previous version of component 'rust-src'
info: removing previous version of component 'cargo'
info: removing previous version of component 'clippy'
info: removing previous version of component 'rust-docs'
info: removing previous version of component 'rust-std'
info: removing previous version of component 'rustc'
info: removing previous version of component 'rustfmt'
info: installing component 'rust-src'
info: using up to 500.0 MiB of RAM to unpack components
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
16.1 MiB / 16.1 MiB (100 %) 3.8 MiB/s in 3s ETA: 0s
info: installing component 'rust-std'
24.4 MiB / 24.4 MiB (100 %) 7.7 MiB/s in 3s ETA: 0s
info: installing component 'rustc'
58.6 MiB / 58.6 MiB (100 %) 9.3 MiB/s in 6s ETA: 0s
info: installing component 'rustfmt'
stable-x86_64-apple-darwin updated - rustc 1.53.0 (53cb7b09b 2021-06-17) (from rustc 1.52.1 (9bc8c42bb 2021-05-09))
info: cleaning up downloads & tmp directories
$ rustc -V
rustc 1.53.0 (53cb7b09b 2021-06-17)
コピートレイト
再代入することで変数 a
の束縛は解除されるが、もし代入したオブジェクトがコピートレイトを実装しているならコピーが作成されるので変数 a
の束縛もそのままらしい。
let a = object;
let mut b = a;
オブジェクトが String 型の場合、String 型はコピートレイトを実装していないから変数 a
の束縛は解除される。つまりプログラマはどの型がコピートレイトを実装しているか知っているべきで、そういった経験知が必要になってくるということ?
関数使ってるのに使ってないよって警告がでるとき
他から参照されてないけど関数は定義しておきたいような場合 #[allow(dead_code)]
を付けて警告を抑制できる
#[derive(Debug)]
pub struct Rectangle {
pub width: u32,
pub height: u32,
}
impl Rectangle {
#[allow(dead_code)] // <-- これ
pub fn area(&self) -> u32 {
self.width * self.height
}
}
けれど、単体テストで使われていたり、他コードから参照されているのに associated function is never used
の警告がでる場合がある
$ cargo test
Compiling x-rust v0.1.0 (/Users/jnst/go/github.com/jnst/x-rust)
warning: associated function is never used: `area`
--> src/introduction.rs:8:12
|
8 | pub fn area(&self) -> u32 {
| ^^^^
|
= note: `#[warn(dead_code)]` on by default
公開してないとこうなってしまうようだ
今回のケースだと下記のように関数には pub
を付けていたが
pub fn area(width: u32, height: u32) -> u32 {
width * height
}
関数の宣言だけでなくモジュール宣言でも公開する必要がある(なんでだろう?)
pub mod rectangle;
#[allow(dead_code)]
を付ける必要はもちろんない
用語
インスタンス
Rust は関数型言語のパラダイムが強い
Java 等オブジェクト指向の言語がクラスの実体をインスタンスと呼ぶのとは違い、トレイトを実装した構造体のことをを インスタンス
と呼ぶ
マーカートレイト
メソッドを持たないトレイトのこと
クロージャ
- トレイトである
- 動的 Size 型である
- 参照や Box を使うことでオブジェクトとして扱える
- トレイトオブジェクト という
let add = |x: u32, y: u32| -> u32 {x + y};
トレイトの規定実装
#[derive(Debug)]
のように記述するだけで実装される規定実装があるものと、#[derive(Display)]
のようにプログラマーが実装しなければならないトレイトがある