Rust

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
Wikipedia
ウィキペディアRustの記事があります。
メインページ > 工学 > 情報技術 > プログラミング > Rust

Rust(ラスト)は、Rustは、性能と安全性、特に安全な並行実行を目指して設計されたマルチパラダイム汎用プログラミング言語です[1]

Rustは構文はC++に似ているが、ボローチェッカーを使って参照を検証することでメモリ安全性を保証することができます。 Rustはガベージコレクター()を使わずにメモリ安全性を実現しており、オプションでリファレンスカウントによる管理も可能です。 Rustはシステムプログラミング言語と呼ばれており、関数型プログラミングのような高レベルの機能に加えて、低レベルのメモリ管理のメカニズムも提供しています。

Hello World[編集]

Rustで Hello World を書くと、

hello.rs
fn main() {
    println!("Hello, world!");
}
実行結果
Hello, world!

hello.rsは、Playgroundに作った、このプログラムへのリンクになっています。

コメント[編集]

Rustのコメントには、C言語/C++と同じく一行コメントと範囲コメントがあります。

一行コメント
//から行末までがコメントと見なされます。
範囲コメント
/*から*/までがコメントと見なされます。
ネストは許されません。
コメントの例
/*
 * プログラムのエントリーポイントは、main 関数です。
 * 関数定義は fn から始まります。
 */
fn main() {
    println!("Hello, world!"); // println! は関数ではなくマクロで、マクロは識別子の末尾に ! が付きます。
}

変数と型[編集]

Rustは、静的な型システムを採用しています。

variables-types.rs
fn main() {
    let x = 999;
    print!("{}({}), ", x, type_of(x));
    let x = -123;
    print!("{}({}), ", x, type_of(x));
    let x = 999u32;
    print!("{}({}), ", x, type_of(x));
    let x = -123i32;
    print!("{}({}), ", x, type_of(x));
    let x = 999u64;
    print!("{}({}), ", x, type_of(x));
    let x = -123i64;
    print!("{}({}), ", x, type_of(x));
    let x = 1.0;
    print!("{}({}), ", x, type_of(x));
    let x = 3.14f32;
    print!("{}({}), ", x, type_of(x));
    println!();
    let x = "abc";
    print!("{}({}), ", x, type_of(x));
    let x = true;
    print!("{:?}({}), ", x, type_of(x));
    let x = [1, 2, 3];
    print!("{:?}({}), ", x, type_of(x));
    let x = (1, 2, 3);
    print!("{:?}({}), ", x, type_of(x));
    let x = &vec![10, 20, 30];
    print!("{:?}({}), ", x, type_of(x));
}

fn type_of<T>(_: T) -> &'static str {
    std::any::type_name::<T>()
}
実行結果
999(i32), -123(i32), 999(u32), -123(i32), 999(u64), -123(i64), 1(f64), 3.14(f32), 
abc(&str), true(bool), [1, 2, 3]([i32; 3]), (1, 2, 3)((i32, i32, i32)), [10, 20, 30](&alloc::vec::Vec<i32>),

なお、{ }プレースホルダーといい、文字列中のその位置に書式化して展開します。

変数とミュータブル・イミュータブル[編集]

Rustでは、変数を宣言するにはキーワード letを使います。 ディフォルトではイミュータブルImmutable;宣言後には代入不能)な変数が宣言されます。 ミュータブルMutable;宣言後に代入可能)な変数を宣言するには、追加のキーワード mut を使います。

hello-variables.rs
fn main() {
    let hello : &str = "Hello, world!"; 
    println!("{}", hello);
}
実行結果
Hello, world!
2行目のlet hello : &str = "Hello, world!";が変数宣言です[2]
&str(文字列のスライスのリファレンス)を型とする変数 hello を宣言し、"Hello, world!"で初期化しています。
Rustには強力な型推論があり多くの場合不要ですが、let 変数名 : 型名の書式で型を伴い変数宣言することも出来ます。

mut をつけない場合には変数に「代入不能」と聞くと、C言語などを知っている人は「定数」を思い浮かべるかもしれませが、 Rustにおいて「定数」は, const 宣言された定数や, static 宣言されかつ mut で修飾されていない変数が相当します。

型推論[編集]

Rust では、変数宣言が初期値を伴っていた場合、変数の型を省略することができ、初期値の型が変数の型になります。

hello-type-inference.rs
fn main() {
    let hello = "Hello, world!"; 
    println!("{}", hello);
}
実行結果
上に同じ

イミュータブル[編集]

Rust では、値が一度変数に let で束縛されると変更できません。これをイミュータブルと言います。

hello-immutable.rs
fn main() {
    let hello : &str = "Hello, world!"; 
    println!("{}", hello);
    hello = "Hello, rust!";
    println!("{}", hello);
}
コンパイル結果
error[E0384]: cannot assign twice to immutable variable `hello`
 --> src/main.rs:4:5
  |
2 |     let hello : &str = "Hello, world!"; 
  |         -----
  |         |
  |         first assignment to `hello`
  |         help: consider making this binding mutable: `mut hello`
3 |     println!("{}", hello);
4 |     hello = "Hello, rust!";
  |     ^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`. 
error: could not compile `playground` due to previous error
イミュータブルな変数には、代入できないというコンパイルエラーです。

ミュータブル[編集]

代入可能、すなわちミュータブルにするためには、変数宣言にあたり let に続けて mut をつけます。

hello-mutable.rs
fn main() {
    let mut hello : &str = "Hello, world!"; 
    println!("{}", hello);
    hello = "Hello, rust!";
    println!("{}", hello);
}
実行結果
Hello, world! 
Hello, rust!

同じ変数名での宣言[編集]

同一スコープで同じ変数名での宣言は可能です。 同じ型である必要はありません。ミュータブルであるかイミュータブルであるかも問いません。 同じ変数名での宣言によって、それまで変数に束縛されていた値への参照がなくなります。

同じ変数名での宣言
fn main() {
    let hello : &str = "Hello, world!"; 
    println!("{}", hello);
    let hello = 154649;
    println!("{}", hello);
}
実行結果
Hello, world! 
154649

定数[編集]

Rustには2種類の定数があり、どちらもグローバルスコープを含む任意のスコープで宣言することができます。また、どちらも明示的な型を持っている必要があります。

  • const: 不変の値
  • static: 静的寿命を持つミュータブルな値 静的寿命は推論されるので、指定する必要はありません。
2種類の定数
const HELLO : &str = "Hello, world!"; 
static LANGUAGE: &str = "Rust";
fn main() {
    println!("{}", HELLO);
    println!("{}", LANGUAGE);
}
実行結果
Hello, world! 
Rust

コードを書き換えてconst宣言や(ミュータブルな)static宣言された変数に代入をしようとすると、エラーになります。

パターン[編集]

pattern.rs
fn main() {
    let (mut x, mut y) = (5, 29);
    println!("x={} Y={}", x, y);
    let (p, q) = (x, y);
    x = q;
    y = p;
    println!("x={} Y={}", x, y);
}
実行結果
x=5 Y=29
x=29 Y=5

データー型[編集]

Restには豊富なデーター型(Data Types)があり、それらを組み合わせて新しい型を作ることができます[3]

スカラー型(Scalar Types)[編集]

スカラー型は単一の値を表します。Rustには、整数、浮動小数点数、論理値、文字という4つの主要なスカラ型があります。

整数型(Integer Types)[編集]

Rustの整数型は、符号の有無とビット幅から12種類のバリエーションがあります。

i8
符号付き8ビット整数
u8
符号なし8ビット整数
i16
符号付き16ビット整数
u16
符号なし16ビット整数
i32
符号付き32ビット整数
u32
符号なし32ビット整数
i64
符号付き64ビット整数
u64
符号なし64ビット整数
i128
符号付き128ビット整数
u128
符号なし128ビット整数
isize
符号付きでアーキテクチャーに自然な整数
usize
符号なしでアーキテクチャーに自然な整数
isizeとusizeのビット幅はプロセッサーのアーキテクチャーによって定義され、32ビットプロセッサーならば32、64ビットプロセッサーならば64です。
整数リテラル(Integer literals)[編集]

リテラル(Literals)とは、プログラミングのソースコードで使用される、数値や文字列などのデーターを直接表現したものです。

様々な整数リテラル
基数 表現
10 19_800
16 0xbadbeef
8 0o777
2 0b101_111_011
バイト(u8のみ) b'Q'
fn main() {
    println!("{:?}", 19_800);
    println!("{:x}", 0xbadbeef);
    println!("{:o}", 0o777);
    println!("{:b}", 0b101_111_011);
    println!("{}", b'Q');
}
実行結果
19800
badbeef
777
101111011
81

数値リテラルには、123u8 の様に型名をタイプサーフィックス(type suffix)として補うことで、ビット幅を明記できます(オプショナル)。
指定されない場合は(バイト以外は)i32が仮定されます(isizeではありません)。
数値リテラルには、読みやすさのため 9_281_636 のように、アンダースコア _ を補うことができます(オプショナル)。

なお、{:x}{x}部分はプレースホルダーファーマッティング・トレイツです。「x」なら16進数、oなら8進数、bなら2進数で出力します。

浮動小数点数型(Floating-Point Types)[編集]

Rustの浮動小数点数型には、f64 と f32 があり、サイズを明示しないと f64 になります。 浮動小数点数は、IEEE-754規格に従います。

論理値型(The Boolean Type)[編集]

Rustの論理値型の型名は bool、真の値は true 偽の値は false です。

文字型(The Character Type)[編集]

Rustの文字型の型名は char です。 char は Unicode のサロゲートペアであっても保持できます。

文字列型(The String Type)[編集]
string.rs
fn main() {
    println!("{:?}", "hello");     // エスケープされた文字列
    println!("{:?}", r#"hello"#);  // エスケープされないraw文字列
    println!("{:?}", b"hello");    // エスケープされたバイト文字列
    println!("{:?}", br#"hello"#); // エスケープされないrawバイト文字列
}
実行結果
"hello"
"hello"
[104, 101, 108, 108, 111]
[104, 101, 108, 108, 111]

複合型(Compound Types)[編集]

複合型(Compound Types)は、複数の値を1つの型にまとめることができます。 Rustにはタプルとアレイという2つのプリミティブな複合型があります。

タプル型(The Tuple Type)[編集]

タプル(The Tuple)は、さまざまな型の値を1つの複合型にまとめる一般的な方法です。 タプルの長さは固定されており、一度宣言すると大きくしたり小さくしたりすることはできません。

配列型(The Array Type)[編集]

複数の値の集まりを持つもう一つの方法として、配列(The Array)があります。 タプルとは異なり、配列の各要素は同じ型でなければなりません。 Rustの配列は、タプルと同じく長さが固定されています。

プレースホルダー[編集]

println! マクロなどの文字表示マクロでは、文字列中の { } の位置に指定された書式で展開します[4]

C言語の標準関数 printf() と機能は似ていますが書式は大きく異なり、Pythonのf文字列との共通点が多いです。


フォーマッティング・トレイツ(Formatting traits)[編集]

からのプレースホルダー{}を指定すればその型の自然なフォーマット(fmt::Display; 100 ならば "100")で文字列化されます。 しかし、基数を変えた16進数や8進数や2進数などでフォーマットをしたいときはフォーマッティング・トレイツ(Formatting traits)を使います[5]

この機能は、 trait を使って実装されています。

Formatting traits
fn main() {
    println!("{:?}", 100);
    println!("{:x}", 100);
    println!("{:o}", 100);
    println!("{:b}", 100);
    println!("{}", 100);
}
実行結果
100
64
144
1100100
100

ムーブセマンティクスとコピーセマンティクス[編集]

識別子[編集]

スコープ[編集]

制御構造[編集]

Rust では、ifforなどの制御構造も式です。

分岐[編集]

Rust は、ifmatch の2つの分岐構文を持ちます。

if[編集]

ifは、条件式に基づき分岐し、分岐先の式を評価します。 ifの値は、分岐先の式の値です。 elseを省略し条件式が偽であった場合のifの値は () です。

構文
if-expr := if 条件式 '{' 式1 '}' [ else '{' 式2 ] '}'
条件式に整数を使うと
fn main() {
    let i = 0;
    
    if i {
        println!("zero");
    }
}
コンパイルエラー
error[E0308]: mismatched types
 --> src/main.rs:4:8
  |
4 |     if i {
  |        ^ expected `bool`, found integer

For more information about this error, try `rustc --explain E0308`.
Rustでは、ifに限らず、条件式は、bool 型でなければいけません。
if.rs
fn main() {
    let i = 0;

    if i == 0 {
        println!("零");
    } else {
        println!("非零");
    }
    
    let s = if i == 0 {
        "ゼロ"
    } else {
        "非ゼロ"
    };
    println!("{}", s);
}
実行結果
零
ゼロ
気をつけたいのは、式の値が参照されるifでは、それぞれの式(ここでは "ゼロ""非ゼロ")にセミコロン ; を付けてはいけない点です。
もし セミコロン ; をつけると、ブロックの値は、Unit 型 () になります。
ifの、分岐先の2つの式の型は同じでなければいけません。
else節が省略された場合は、Unit 型を返さなければいけません。
式の最後に、セミコロン}; が必要ということです[6]
また、let s = if i == 0 {の文末の}; のセミコロンも忘れがちです。
Rust では if に限らず、コードブロックは式の一種で値を返します[7]。その場合、コードブロックの最後の式がコードブロックの値となりセミコロン ; は不要で、もし、 ; をつけると ; の次の式(=空文)の値 () がコードブロックの値になります。

この特徴は、関数型プログラミングを意識したものですが、同時に後に説明する所有権の移譲で重要な役割を果たします。

Some()[編集]

Rustでは、C言語のNULLに相当する値は None です。 通常の変数は None の代入は受付けませんが、Some() を使うと None を取り得る変数が宣言できます[8]

some.rs
fn main() {
    let mut x = Some(0);
    println!("{:?}({})", x, type_of(x));
    x = None;
    println!("{:?}({})", x, type_of(x));
}

fn type_of<T>(_: T) -> &'static str {
    std::any::type_name::<T>()
}
実行結果
Some(0)(core::option::Option<i32>)
None(core::option::Option<i32>)

Some() 及び None は、標準ライブラリ std::option で定義されていますが、初期化済みなので Use することなく、接頭辞Option::なしで使えます。

if let[編集]

if-let.rs
fn main() {
    let mut v = vec![2, 3, 5];

    if let Some(x) = v.pop() {
        println!("x = {}", x)
    } else {
        println!("done!")
    }
    if let Some(x) = v.pop() {
        println!("x = {}", x)
    } else {
        println!("done!")
    }
    if let Some(x) = v.pop() {
        println!("x = {}", x)
    } else {
        println!("done!")
    }
    if let Some(x) = v.pop() {
        println!("x = {}", x)
    } else {
        println!("done!")
    }
    if let Some(x) = v.pop() {
        println!("x = {}", x)
    } else {
        println!("done!")
    }
}
実行結果
x = 5
x = 3
x = 2
done!
done!

match[編集]

matchは、パターンに基づき分岐し、分岐先の式を評価します。 matchの値は、分岐先の式の値です。

matchは、C言語でいうswitch case 文と似ていますが、switch文は式とリテラルの値の一致を上から順に検査しますが、matchでは式がパターンに適合するかを上から順に検査します。 また、whenはbreakが必要ありません(フォールスルーしません)。

構文
match-expr := match 式 '{'
    パターン1 '=>' 式1 ,
    パターン2 '=>' 式2 ,
              
    パターンn '=>' 式n ,
    変数 '=>' どのパターンにもマッチしなかった場合の式 ,
'}'
パターンと式を '=>' で繋いだものをアームといいます。
パターンが変数だとあらゆる値にマッチし、式の値が束縛されます。典型的には、_ アンダースコアで、これは値を束縛しないワイルドカードパターンです。
パターンは網羅する必要があるので、最後のアームのパターンをワイルドカードを置く事が多いですが、例えば bool 型の値ならば flase と true の2つのパターンで網羅できるのでワイルドカードをパターンとするアームは不要です(コンパイル時に warning になります)。

下記コードは、Rust 日本語ドキュメント のサンプルコードをもとに、英語や課題などの情報を除いたものです。

コード例
fn main() {
    let number = 13;
    println!("Tell me about {}", number);

    match number {
        // 単一の値とのマッチをチェック
        1 => println!("One!"),

        // いくつかの値とのマッチをチェック
        2 | 3 | 5 | 7 | 11 => println!("This is a prime"),

        // 特定の範囲の値とのマッチをチェック
        13..=19 => println!("A teen"),

        // その他の場合
        _ => println!("Ain't special"),
    }

    let boolean = true;
    // マッチは式でもある。
    let binary = match boolean {
        false => 0,
        true => 1,
        // _ => 2, //  !!! note: `#[warn(unreachable_patterns)]` on by default !!!
    };

    println!("{} -> {}", boolean, binary);
}
実行結果
Tell me about 13
A teen 
true -> 1

反復[編集]

loop と break の値[編集]

Rest には、永久ループのためのキーワード loop が用意されています。

loop-and-break.rs
fn main() {
    let mut i = 0;
    let result = loop {
        if i > 3 {
            break 100;
        }
        println!("i = {}", i);
        i += 1;
    };
    println!("result = {}", result);
}
実行結果
i = 0
i = 1
i = 2
i = 3
result = 100
loopも値を返せる式です。
と言ってもloop自体は無限ループなので、break文のオペランドが値になります。

continue[編集]

ループのフローコントロール文にもう一つ continue があります。

continue.rs
fn main() {
    let mut i = 0;
    let result = loop {
        if i > 10 {
            break 100;
        }
        if i % 2 == 0 {
            i += 1;
            continue;
        }
        println!("i = {}", i);
        i += 1;
    };
    println!("result = {}", result);
}
実行結果
i = 1
i = 3
i = 5
i = 7
i = 9
result = 100
continue は、ループの先頭に戻ります。

while[編集]

Rust には、条件が成立するあいだ反復するためのキーワード while が用意されています。

while.rs
fn main() {
    let mut i = 0;
    while i < 5 {
        println!("i = {}", i);
        i += 1
    };
}
実行結果
i = 0
i = 1
i = 2
i = 3
i = 4
while let[編集]
while-let.rs
fn main() {
    let mut n = Some(0);
    while let Some(i) = n {
        n = if i > 5 {
            None
        } else {
            println!("i = {}", i);
            Some(i + 1)
        }
    }
}
実行結果
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
while let again[編集]
while-let-again.rs
fn main() {
    let mut v = vec![1, 3, 5, 7, 11];
    while let Some(x) = v.pop() {
        println!("x = {}", x)
    }
}
実行結果
x = 11
x = 7
x = 5
x = 3
x = 1
Rustにdo-whileはありません
Rustにdo-whileはありません。while の条件式に do のブロックを詰め込むことで同じことが実現できます。
pseudo-do-while.rs
fn main() {
    let mut i = 100;
    while {
        println!("{}", i);
        i += 1;
        i < 10
    } {}
}
実行結果
100
whileの条件式が省略されたかのように見えますが、4,5,6行目を含むブロックが、while の条件式となり値はブロックの最後の6行目の式の値です。
7行目の {} がループ本体です。


for[編集]

Rust の for は、他のプログラミング言語の foreach に近いものです。

Range[編集]
for-in-range.rs
fn main() {
    for i in 1..5 {
        println!("i = {}", i);
    }
}
実行結果
i = 1
i = 2
i = 3
i = 4
std::ops::Range<{integer}>を対象にしたイテレーション。
Range[編集]
for-in-range2.rs
fn main() {
    for i in 1..=5 {
        println!("i = {}", i);
    }
}
実行結果
i = 1
i = 2
i = 3
i = 4
i = 5
=5だけが違います。範囲の最後の数字が異なります。
Iter[編集]
iter.rs
fn main() {
    let v = vec![1, 3, 5, 7, 11];
    for x in v {
        println!("x = {}", x)
    }
}
実行結果
x = 1
x = 3
x = 5
x = 7
x = 11
std::ops::Range<{integer}>を対象にしたイテレーション。
Iter Rev[編集]
iter-rev.rs
fn main() {
    let v = vec![1, 3, 5, 7, 11];
    for x in v.iter().rev() {
        println!("x = {}", x)
    }
}
実行結果
x = 11
x = 7
x = 5
x = 3
x = 1
std::ops::Range<{integer}>を対象にしたイテレーション。

脚註[編集]

  1. ^ Hoare, Graydon (2016年12月28日). “Rust is mostly safety”. Graydon2. Dreamwidth Studios. 2021年12月3日閲覧。
  2. ^ 文字リテラルであることを強調するならlet hello : &'static str = "Hello, world!";とすべきだったかもしれません。
  3. ^ Data Types - The Rust Programming Language”. 2021年12月8日閲覧。
  4. ^ Module std::fmt
  5. ^ Formatting traits
  6. ^ C言語系では、式を文にする為いに}; が必要です。Rustもそう説明されている場合がありますが、Rustでは式の型の一致が目的です。
  7. ^ コードブロックが値を持つプログラミング言語としては、BCPL, Ruby, ScalaKotlinがあります。
  8. ^ Option in std::option - Rust

外部リンク[編集]

参考文献[編集]

Jim Blandy, Jason Orendorff. オライリージャパン,『プログラミングRust』, 第2版, (2022年1月19日).

Jim Blandy, Jason Orendorff. オライリージャパン,『プログラミングRust』, 第1版, (2018年8月10日).