忙しい人向けのRust
TL;DR
本当は時間があるなら書籍や他の方の記事でRustについて学ぶことを推奨します。
しかしながら、期限まで時間がない場合というものは多いと思うわけですよ、私は。
そういう場合の応急処置を目的として、他の言語でプログラミング経験がある、以下のような人を対象にしてRustに必要な知識をできるだけ最小限にまとめます。
- いきなりRustで書かれたプログラムを理解しないといけなくなった人
- Rustでプログラムを書かないといけないが詳細を学ぶ時間が残されていない人
インストールからHello,worldまで
インストール(Linux)
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
Hello,World
$ mkdir rust_projects
$ cd rust_projects
$ cargo new hello
$ cd hello
$ cargo run
ccが見つからないと言われたのでdebianを使っている私はsudo apt install build-essential
で解決しました。
変数
変数の定義方法
変数の定義はlet 変数名=値
で行うが、同じ変数に再代入できない。
つまり、下記のコードはエラーになる。
let x = 5;
x = 6; //Error
これを回避するにはlet mut 変数名=値
で定義する。
let mut x = 5;
x = 6; //OK
基本の型
整数
i8
, i16
, i32
, i64
, u8
, u16
, u32
, u64
がある。
iは符号付き、uは符号なしを表し、数字はビット数を表す。
浮動小数点数
f32
, f64
がある。fは浮動小数点数を表し、数字はビット数を表す。
論理値型
true
, false
がある。
文字型
ユニコードの文字コードU+0000
からU+D7FF
と、U+E000
からU+10FFFF
までを表す。文字列ではない。
タプル
複数の値を1つの型にまとめる。各値は同じ型である必要はないが、位置と型の対応関係は守る必要がある。
分解して値を取り出す方法は以下の2種類がある。
let t = (500, 6.4, 1);
let (x, y, z) = t; //パターンマッチングによる分解
let t = (500, 6.4, 1);
let x = t.0; //インデックスによる分解
let y = t.1;
let z = t.2;
配列
配列は固定長で、全ての要素は同じ型である必要がある。
let a = [1, 2, 3, 4, 5];
let x = a[0];
制御フロー
条件分岐
if
if 条件式1 {
}
else if 条件式2{
}
else {
}
条件式は論理値である必要がある。
他の言語で見る以下のコードはエラーとなる。
let num = 3;
if num { something; }
num != 0
とかにすればセーフ。
match
他の言語で言うswitch
に近い。
ただし、分岐パターンはすべて網羅する必要がある。
_
を使うと全パターンを網羅できる。
let value = 1;
match value {
1 => println!("one"),
3 => println!("three"),
_ => println!("other"),
}
C言語のswitchで置き換えたイメージは以下のようになる。
int x = 1;
switch (x) {
case 1:
printf("one\n");
break;
case 3:
printf("three\n");
break;
default:
printf("other\n");
break;
}
if let
if let構文はmatch式の分岐が1つで、残りは無視したい場合に使われる。
let value = 3
if let 3 = value {
println!("three");
}
matchで書いた場合は以下のようになる。
let value = 3;
match value {
3 => println!("three"),
_ => (),
}
ループ
loop
無限ループ。break
かcontinue
を使用しない限り永遠に繰り返す。
while
while 条件式 { something; }
で条件式がtrueの間は繰り返す。
for
コレクション型(配列など)の要素について順番に繰り返す。
let a = [10, 20, 30, 40, 50];
for element in a {
do something;
}
また、for i in 0..10
というような感じでiが0から9までのループを作れる。
関数
関数の定義方法
fnキーワードで始まり、関数名の後に引数を書く。
各引数は型を宣言しなければならない。
関数を定義する位置の前後関係は問題にはならない。
戻り値を返す場合は、最後の式が戻り値になる。
セミコロンをつけるとエラーになる。
関数の途中でreturn
キーワードを使って値を返すこともでき、この場合はセミコロンをつける。
fn print_and_plus_one(x: i32) -> i32 {
println!("x -> {}", x + 1);
x + 1 //OK
}
fn print_and_plus_one(x: i32) -> i32 {
println!("x -> {}", x + 1);
x + 1; //Error
}
構造体とメソッド
構造体の定義方法
struct
キーワードに続いて構造体名、各フィールドの名前と型を記述する。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
この構造体のインスタンスを作成する場合は以下のようになる。
let user = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
enumの定義方法
型のグループ化に使われる。
struct Ipv4Addr {
// 省略
}
struct Ipv6Addr {
// 省略
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
インスタンス化の際は以下のようになる。
let localhost = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
enumとmatch
enumとmatch
は組み合わせて使われることも多い。
上記の例で説明すると、IpAddr
がIpv4Addr
かIpv6Addr
によって処理をわけたりできる。
match localhost {
IpAddr::V4(ipv4) => IPv4の場合の処理,
IpAddr::V6(ipv6) => IPv6の場合の処理,
}
メソッドの定義方法
impl
キーワードに続き、メソッドが属する構造体またはenumの名前を指定する。
impl
ブロックは異なる場所で複数定義してもよい。
impl User {
fn show_username(&self) -> &str {
return self.username[..]
}
}
呼び出すときは、インスタンスの変数名.show_username()
になる。
引数はself
だったり、&self
だったり、&mut self
だったりする。
&self
はフィールドの値を変更する必要がなく、参照したいだけの場合に使うことが多い。
&mut self
はフィールドの値を変更する必要がある場合に使う。
それ以外でコンパイルエラーが出たらコンパイラの助言に従って修正する。
関連関数
構造体やenumに関連付けられた関数の第一引数がself
ではない場合は関連関数と呼ばれる。
他の言語では静的メソッドやクラスメソッドと呼ばれるものに近い。
よく使われる構造体
Option<T>
enum Option<T> {
Some(T),
None,
}
Option<T>
はSome<T>
かNone
のどちらかになる。
Some<T>
は型Tのデータを1つ持つことを表す。
None
は値が存在しないことを表す。
例えば、None
に8を加算する可能性があるコードを書いたとすると、
実行するまでエラーにならない言語もあるが、Rustではコンパイルエラーになる。
let x: i8 = 8;
let y: Option<i8> = Some(8);
let sum = x + y;
Result<T, E>
enum Result<T, E> {
Ok(T),
Err(E),
}
Result<T, E>
はOk(T)
かErr(E)
のどちらかになる。
Ok(T)
は成功を表し、T型の戻り値を格納している。
Err(E)
は失敗を表し、E型のエラーを格納している。
最後に
プログラムを書くときはコンパイラのアドバイスに従えばなんとかなることが多いです。
Discussion