🚀

忙しい人向けのRust

2024/11/27に公開

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

無限ループ。breakcontinueを使用しない限り永遠に繰り返す。

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は組み合わせて使われることも多い。
上記の例で説明すると、IpAddrIpv4AddrIpv6Addrによって処理をわけたりできる。

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