Rustの基本構文をわかりやすく解説|初心者でも安心の入門ガイド

Rust

プログラミング聞くことのある「Rust(ラスト)」。

高い安全性や高速な実行速度が注目されていて、次世代の主要言語になるとも言われています。

そんなRustをいざ学ぼうと思っても:

  • 「他の言語とどこがちがうの?」
  • 「基本構文がむずかしそう…」
  • 「所有権ってなに?」

と感じる人も多いでしょう。

この記事では、Rustの基本構文を初心者向けにやさしく解説します。

スポンサーリンク

Rustってどんな言語?

Rustの生まれた背景

Rustは2010年にMozilla(Firefoxで有名な会社)が開発を始めた言語です。

なぜ作られたの?

  • C/C++の安全性の問題を解決したかった
  • メモリリークやバッファオーバーフローをなくしたかった
  • 高いパフォーマンスは維持したかった

Rustの3つの大きな特徴

メモリ安全性

コンパイル時にメモリ管理を厳しくチェックし、バグを防ぎます。

// C言語だとこんなバグが起きやすい
// int* ptr = malloc(sizeof(int));
// free(ptr);
// *ptr = 10; // 解放済みメモリへのアクセス(危険!)

// Rustではコンパイル時にエラーになる
let data = vec![1, 2, 3];
drop(data); // メモリ解放
// println!("{:?}", data); // コンパイルエラー!

スピードが速い

C言語やC++並みのパフォーマンスを誇ります。

並行処理に強い

スレッド安全性をコンパイラが保証してくれるので、並列処理を安心して書けます。

他の言語とのちがい

特徴PythonJavaScriptJavaC++Rust
型安全性
メモリ安全性
実行速度遅い中程度高速高速高速
学習コスト低い低い中程度高い高い

たとえばPythonやJavaScriptは実行時エラーが起きやすいですが、Rustはコンパイル時に厳格にチェックされるため、コードの品質が自然と高まります。

Rust開発環境のセットアップ

Rustのインストール

Windows・Mac・Linuxの場合

  1. https://rustup.rs/ にアクセス
  2. 指示に従ってインストール
  3. ターミナルで確認:
rustc --version
cargo --version

初めてのプログラム

# 新しいプロジェクトを作成
cargo new hello_rust
cd hello_rust

# プログラムを実行
cargo run

これで「Hello, world!」が表示されれば成功です。

Rustの基本構文を解説

いよいよここからが本題です。Rustのプログラムを書く上でおさえておきたい基本構文を順番に見ていきましょう。

変数の宣言

基本的な宣言方法

Rustではletを使って変数を宣言します。デフォルトは「不変(immutable)」なので、一度値を入れると変えられません。

let x = 5;
println!("xの値は: {}", x); // 5が表示される

// x = 10; // エラー!不変変数は変更できない

可変変数の宣言

もし値を変更したい場合は、mutをつけます。

let mut y = 10;
println!("最初のyの値: {}", y); // 10が表示される

y = 20;
println!("変更後のyの値: {}", y); // 20が表示される

型を明示する場合

let x: i32 = 42;        // 32ビット整数
let y: f64 = 3.14;      // 64ビット浮動小数点数
let name: String = String::from("太郎");
let is_rust_fun: bool = true;

変数のシャドウイング

同じ名前の変数を再定義できます:

let x = 5;
let x = x + 1;    // 6になる
let x = x * 2;    // 12になる

println!("xの最終的な値: {}", x); // 12が表示される

ポイント

Rustは「不変」が基本です。これにより:

  • バグが減る:意図しない変更を防げる
  • 並行処理が安全:複数のスレッドから安全にアクセスできる
  • 最適化しやすい:コンパイラが効率的なコードを生成できる

データ型

プリミティブ型

// 整数型
let a: i8 = 127;        // 8ビット符号付き整数
let b: u8 = 255;        // 8ビット符号なし整数  
let c: i32 = 1000;      // 32ビット符号付き整数(デフォルト)
let d: u64 = 1000000;   // 64ビット符号なし整数

// 浮動小数点型
let e: f32 = 3.14;      // 32ビット浮動小数点数
let f: f64 = 2.718;     // 64ビット浮動小数点数(デフォルト)

// ブール型
let is_learning: bool = true;

// 文字型
let letter: char = 'あ';

文字列型

// 文字列スライス(不変)
let greeting: &str = "こんにちは";

// String型(可変)
let mut message: String = String::from("hello");
message.push_str(" world");
println!("{}", message); // "hello world"が表示される

配列とベクター

// 配列(固定サイズ)
let numbers: [i32; 3] = [1, 2, 3];
let first = numbers[0]; // 1

// ベクター(可変サイズ)
let mut vec = vec![1, 2, 3];
vec.push(4);
println!("{:?}", vec); // [1, 2, 3, 4]が表示される

関数

基本的な関数定義

Rustではfnを使って関数を定義します。

fn add(a: i32, b: i32) -> i32 {
    a + b  // セミコロンなし(戻り値)
}

fn main() {
    let result = add(3, 4);
    println!("3 + 4 = {}", result); // 7が表示される
}

関数の構成要素

  • fn:関数を定義するキーワード
  • 引数の型は必須
  • 戻り値の型は->で指定
  • 最後の式がセミコロンなしなら戻り値になる

より複雑な関数の例

// 複数の戻り値(タプル)
fn divide_and_remainder(x: i32, y: i32) -> (i32, i32) {
    (x / y, x % y)
}

// 戻り値がない関数
fn print_message(msg: &str) {
    println!("メッセージ: {}", msg);
}

// 条件分岐を含む関数
fn max(a: i32, b: i32) -> i32 {
    if a > b {
        a
    } else {
        b
    }
}

fn main() {
    let (quotient, remainder) = divide_and_remainder(10, 3);
    println!("10 ÷ 3 = {} あまり {}", quotient, remainder);
    
    print_message("Rustは楽しい!");
    
    let larger = max(5, 8);
    println!("5と8のうち大きいのは: {}", larger);
}

制御構文

if文(条件分岐)

let num = 6;

if num > 5 {
    println!("5より大きい");
} else if num == 5 {
    println!("5と等しい");
} else {
    println!("5より小さい");
}

if文を式として使う

Rustではifも式なので、値を返せます:

let condition = true;
let number = if condition { 5 } else { 6 };
println!("numberの値: {}", number); // 5が表示される

match文(パターンマッチング)

let number = 3;

match number {
    1 => println!("一"),
    2 => println!("二"),
    3 => println!("三"),
    4 | 5 => println!("四または五"),
    6..=10 => println!("六から十まで"),
    _ => println!("その他"),
}

ループ文

無限ループ(loop)

let mut count = 0;

loop {
    count += 1;
    println!("カウント: {}", count);
    
    if count == 3 {
        break; // ループを抜ける
    }
}

条件ループ(while)

let mut number = 3;

while number != 0 {
    println!("{}!", number);
    number -= 1;
}
println!("発射!");

for ループ

// 範囲を使ったループ
for i in 0..5 {
    println!("カウント: {}", i); // 0, 1, 2, 3, 4
}

// 配列を使ったループ
let animals = ["猫", "犬", "鳥"];
for animal in animals.iter() {
    println!("動物: {}", animal);
}

// ベクターを使ったループ
let numbers = vec![1, 2, 3, 4, 5];
for (index, value) in numbers.iter().enumerate() {
    println!("インデックス{}: 値{}", index, value);
}

構造体(struct)

基本的な構造体

struct Person {
    name: String,
    age: u32,
    email: String,
}

fn main() {
    let person = Person {
        name: String::from("田中太郎"),
        age: 25,
        email: String::from("tanaka@example.com"),
    };
    
    println!("名前: {}", person.name);
    println!("年齢: {}", person.age);
}

構造体のメソッド

impl Person {
    // 関連関数(コンストラクタのようなもの)
    fn new(name: String, age: u32, email: String) -> Person {
        Person { name, age, email }
    }
    
    // メソッド
    fn introduce(&self) {
        println!("私は{}です。{}歳です。", self.name, self.age);
    }
    
    // 可変メソッド
    fn have_birthday(&mut self) {
        self.age += 1;
        println!("誕生日おめでとう!{}歳になりました。", self.age);
    }
}

fn main() {
    let mut person = Person::new(
        String::from("佐藤花子"),
        30,
        String::from("sato@example.com")
    );
    
    person.introduce();
    person.have_birthday();
}

列挙型(enum)

基本的な列挙型

enum Color {
    Red,
    Green,
    Blue,
}

fn main() {
    let my_color = Color::Red;
    
    match my_color {
        Color::Red => println!("赤色です"),
        Color::Green => println!("緑色です"),  
        Color::Blue => println!("青色です"),
    }
}

値を持つ列挙型

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => println!("終了します"),
        Message::Move { x, y } => println!("({}, {})に移動", x, y),
        Message::Write(text) => println!("テキスト: {}", text),
        Message::ChangeColor(r, g, b) => println!("色をRGB({}, {}, {})に変更", r, g, b),
    }
}

fn main() {
    let messages = vec![
        Message::Write(String::from("こんにちは")),
        Message::Move { x: 10, y: 20 },
        Message::ChangeColor(255, 0, 0),
        Message::Quit,
    ];
    
    for message in messages {
        process_message(message);
    }
}

Option型とResult型

Option型(nullの代わり)

fn find_word(text: &str, word: &str) -> Option<usize> {
    text.find(word)
}

fn main() {
    let text = "Rustは素晴らしい言語です";
    
    match find_word(text, "素晴らしい") {
        Some(index) => println!("{}文字目で見つかりました", index),
        None => println!("見つかりませんでした"),
    }
    
    // より簡潔に書く場合
    if let Some(index) = find_word(text, "言語") {
        println!("{}文字目で見つかりました", index);
    }
}

Result型(エラーハンドリング)

fn divide(x: f64, y: f64) -> Result<f64, String> {
    if y == 0.0 {
        Err(String::from("ゼロで割ることはできません"))
    } else {
        Ok(x / y)
    }
}

fn main() {
    match divide(10.0, 2.0) {
        Ok(result) => println!("結果: {}", result),
        Err(error) => println!("エラー: {}", error),
    }
    
    match divide(10.0, 0.0) {
        Ok(result) => println!("結果: {}", result),
        Err(error) => println!("エラー: {}", error),
    }
}

所有権システム

所有権とは?

Rustの最も特徴的な機能が「所有権(ownership)」です。

これがRustをむずかしく感じさせる原因のひとつですが、安全性の根本でもあります。

所有権の3つのルール

  1. 各値には所有者がいる
  2. 所有者は同時に1つだけ
  3. 所有者がスコープを出ると値は削除される

所有権の移動(move)

fn main() {
    let s1 = String::from("Hello");
    let s2 = s1; // s1の所有権がs2に移動
    
    // println!("{}", s1); // エラー!s1はもう使えない
    println!("{}", s2); // これはOK
}

なぜこうなる?

  • Stringヒープにデータを保存する
  • 同じデータへの複数の所有者を防ぐため
  • メモリの二重解放を防ぐため

借用(borrowing)

値を「借りる」だけなら、参照を使います:

fn main() {
    let s1 = String::from("Hello");
    let len = calculate_length(&s1); // 参照を渡す
    println!("文字列 '{}' の長さは {}", s1, len); // s1はまだ使える
}

fn calculate_length(s: &String) -> usize {
    s.len() // 借用なので所有権は移動しない
}

可変借用

fn main() {
    let mut s = String::from("Hello");
    change_string(&mut s); // 可変借用
    println!("{}", s); // "Hello, world!"
}

fn change_string(s: &mut String) {
    s.push_str(", world!");
}

借用の規則

  1. 不変借用は複数OK
  2. 可変借用は1つだけ
  3. 不変借用と可変借用は同時にNG
fn main() {
    let mut s = String::from("Hello");
    
    // 不変借用は複数OK
    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    
    // 可変借用は1つだけ
    let r3 = &mut s;
    // let r4 = &mut s; // エラー!
    println!("{}", r3);
}

エラーハンドリング

panic!マクロ

fn main() {
    let numbers = vec![1, 2, 3];
    
    // 範囲外アクセスでパニック
    // let fourth = numbers[3]; // パニックが発生
    
    // 安全なアクセス方法
    match numbers.get(3) {
        Some(value) => println!("4番目の要素: {}", value),
        None => println!("4番目の要素は存在しません"),
    }
}

ファイル読み込みの例

use std::fs::File;
use std::io::Read;

fn read_file(filename: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(filename)?; // ?演算子でエラーを伝播
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    match read_file("example.txt") {
        Ok(contents) => println!("ファイルの内容:\n{}", contents),
        Err(error) => println!("ファイル読み込みエラー: {}", error),
    }
}

実践的なプログラム例

簡単な計算機

use std::io;

enum Operation {
    Add,
    Subtract,
    Multiply,
    Divide,
}

fn calculate(a: f64, b: f64, op: Operation) -> Result<f64, String> {
    match op {
        Operation::Add => Ok(a + b),
        Operation::Subtract => Ok(a - b),
        Operation::Multiply => Ok(a * b),
        Operation::Divide => {
            if b == 0.0 {
                Err("ゼロで割ることはできません".to_string())
            } else {
                Ok(a / b)
            }
        }
    }
}

fn main() {
    println!("簡単な計算機");
    
    loop {
        println!("最初の数値を入力してください:");
        let mut input = String::new();
        io::stdin().read_line(&mut input).expect("入力エラー");
        let a: f64 = match input.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("無効な数値です");
                continue;
            }
        };
        
        println!("演算子を入力してください (+, -, *, /):");
        input.clear();
        io::stdin().read_line(&mut input).expect("入力エラー");
        let operation = match input.trim() {
            "+" => Operation::Add,
            "-" => Operation::Subtract,
            "*" => Operation::Multiply,
            "/" => Operation::Divide,
            _ => {
                println!("無効な演算子です");
                continue;
            }
        };
        
        println!("2番目の数値を入力してください:");
        input.clear();
        io::stdin().read_line(&mut input).expect("入力エラー");
        let b: f64 = match input.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("無効な数値です");
                continue;
            }
        };
        
        match calculate(a, b, operation) {
            Ok(result) => println!("結果: {}", result),
            Err(error) => println!("エラー: {}", error),
        }
        
        println!("続けますか? (y/n)");
        input.clear();
        io::stdin().read_line(&mut input).expect("入力エラー");
        if input.trim().to_lowercase() != "y" {
            break;
        }
    }
}

よくある初心者の疑問

Q1: 他の言語と比べて難しいですか?

A1: 最初は難しく感じますが、理由があります

難しく感じる理由:

  • 所有権システムが独特
  • コンパイラが厳格
  • 型システムが複雑

でも慣れると:

  • バグが圧倒的に少なくなる
  • パフォーマンスを意識せずに高速なコードが書ける
  • 並行処理が安全に書ける

まとめ

今回は「Rustの基本構文」について詳しく解説しました。

重要なポイント

  1. Rustは安全性とパフォーマンスを両立
    • メモリ安全性をコンパイル時に保証
    • C/C++並みの高速実行
    • 並行処理にも強い
  2. 独特な特徴を理解することが重要
    • 変数は基本的に不変
    • 所有権システムでメモリ管理
    • 強力な型システムとパターンマッチング
  3. エラーハンドリングが組み込まれている
    • Option型でnullを安全に扱う
    • Result型で例外を型安全に処理
    • コンパイラが多くのバグを事前に検出
  4. 学習コストはあるが、得られるメリットが大きい
    • 最初は困惑するかもしれません
    • 慣れると他の言語に戻れなくなる
    • 長期的に見ると開発効率が向上

基本構文のまとめ

構文記法特徴
変数宣言let x = 5;デフォルトで不変
可変変数let mut y = 10;mutで可変に
関数定義fn add(a: i32) -> i32型注釈が必須
条件分岐if condition { }式として使える
パターンマッチmatch value { }強力な分岐処理
構造体struct Person { }データをまとめる
列挙型enum Color { }状態を表現

コメント

タイトルとURLをコピーしました