Rustとは?なぜ今Rustを学ぶべきか
RustはMozillaが開発したシステムプログラミング言語で、メモリ安全性・速度・並行性を同時に実現します。2015年の正式リリース以来、Stack Overflowの開発者調査で9年連続「最も愛されるプログラミング言語」に選ばれ続けており、LinuxカーネルへのRustコード採用やGoogle・Microsoft・AWSなど大手テック企業での採用が急増しています。
Rustが選ばれる5つの理由
- メモリ安全性:ガベージコレクタなしでメモリ安全を保証
- 高パフォーマンス:C/C++と同等の実行速度
- 並行性の安全:コンパイル時にデータ競合を防止
- 豊富なエコシステム:Cargo(パッケージマネージャ)による依存管理
- WebAssembly対応:WebブラウザでもRustコードを実行可能
Rustのインストールと環境構築
Rustのインストールはrustupを使うのが公式推奨です。以下のコマンドを実行します。
# macOS/Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# インストール確認
rustc --version
cargo --version
Windowsの場合はrustup-init.exeをダウンロードして実行します。VS Codeを使う場合はrust-analyzer拡張機能のインストールを強くお勧めします。
Rustの基本文法
変数とミュータビリティ
Rustでは変数はデフォルトでイミュータブル(不変)です。変更可能にするにはmutキーワードが必要です。
fn main() {
// イミュータブル変数
let x = 5;
println!("x = {}", x);
// ミュータブル変数
let mut y = 10;
y += 5;
println!("y = {}", y);
// 定数(型注釈が必要)
const MAX_POINTS: u32 = 100_000;
println!("MAX_POINTS = {}", MAX_POINTS);
}
データ型
fn main() {
// 整数型
let a: i32 = -10; // 符号付き32ビット整数
let b: u64 = 100; // 符号なし64ビット整数
let c: i8 = 127; // 符号付き8ビット整数
// 浮動小数点型
let d: f64 = 3.14;
let e: f32 = 2.71;
// 真偽値
let is_active: bool = true;
// 文字型(Unicodeスカラー値)
let ch: char = '日';
// タプル
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // デストラクチャリング
// 配列(固定長)
let arr = [1, 2, 3, 4, 5];
let first = arr[0];
println!("a={}, b={}, ch={}", a, b, ch);
}
Rustの核心:所有権(Ownership)
Rustの最も重要かつユニークな概念が所有権(Ownership)です。ガベージコレクタを使わずにメモリ安全を実現するための仕組みです。
所有権の3つのルール
- Rustの各値は「所有者(owner)」と呼ばれる変数を持つ
- 一度に所有者は1つだけ存在できる
- 所有者がスコープから外れると、値は自動的に破棄(drop)される
fn main() {
// s1がString "hello"を所有
let s1 = String::from("hello");
// s1の所有権がs2に移動(ムーブ)
let s2 = s1;
// エラー:s1はもう有効でない
// println!("{}", s1); // ← コンパイルエラー!
println!("{}", s2); // OK
// クローン(深いコピー)
let s3 = s2.clone();
println!("s2={}, s3={}", s2, s3); // 両方OK
}
参照と借用(References and Borrowing)
所有権を移動させずに値を使いたい場合は参照(reference)を使います。参照を使うことを「借用(borrowing)」と呼びます。
fn main() {
let s1 = String::from("hello");
// &s1で参照を渡す(所有権は移動しない)
let len = calculate_length(&s1);
println!("{}の長さは{}です", s1, len); // s1はまだ有効
}
fn calculate_length(s: &String) -> usize {
s.len()
} // sはここで破棄されるが、所有権がないのでString自体は解放されない
fn main() {
let mut s = String::from("hello");
// ミュータブル参照
change(&mut s);
println!("{}", s); // "hello, world"
}
fn change(s: &mut String) {
s.push_str(", world");
}
// 注意:同じスコープ内でミュータブル参照は1つだけ
// let r1 = &mut s;
// let r2 = &mut s; // ← エラー!
ライフタイム(Lifetimes)
ライフタイムはRustコンパイラが参照の有効期間を追跡するための仕組みです。ダングリングポインタを防ぎます。
// ライフタイム注釈
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("long string is long");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("longest: {}", result);
}
}
構造体とトレイト
// 構造体の定義
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
// メソッドの実装
impl Rectangle {
// コンストラクタ(慣例的にnewを使う)
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
// 面積を計算するメソッド
fn area(&self) -> f64 {
self.width * self.height
}
// 周長を計算するメソッド
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
// 正方形かどうか判定
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn main() {
let rect = Rectangle::new(10.0, 5.0);
println!("{:?}", rect);
println!("面積: {}", rect.area());
println!("周長: {}", rect.perimeter());
println!("正方形: {}", rect.is_square());
}
トレイト(Trait)
トレイトは他の言語のインターフェースに相当します。共通の振る舞いを定義します。
// トレイトの定義
trait Shape {
fn area(&self) -> f64;
fn perimeter(&self) -> f64;
// デフォルト実装
fn describe(&self) -> String {
format!("面積: {:.2}, 周長: {:.2}", self.area(), self.perimeter())
}
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
}
fn print_shape_info(shape: &dyn Shape) {
println!("{}", shape.describe());
}
fn main() {
let circle = Circle { radius: 5.0 };
print_shape_info(&circle);
}
エラーハンドリング
Rustは例外機構を持たず、ResultとOption型でエラーを扱います。
use std::fs::File;
use std::io::{self, Read};
// ? 演算子でエラーを簡潔に伝播
fn read_file(path: &str) -> Result {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
match read_file("hello.txt") {
Ok(contents) => println!("ファイル内容: {}", contents),
Err(e) => println!("エラー: {}", e),
}
// unwrap_or_else でエラー時のデフォルト値
let content = read_file("missing.txt")
.unwrap_or_else(|_| String::from("ファイルが見つかりません"));
println!("{}", content);
}
Rustの実用的なプロジェクト:CLIツール作成
実践的なコマンドラインツールを作成してみましょう。ファイルの行数をカウントするツールです。
use std::env;
use std::fs;
use std::process;
fn main() {
let args: Vec = env::args().collect();
if args.len() < 2 {
eprintln!("使い方: {} <ファイルパス>", args[0]);
process::exit(1);
}
let filename = &args[1];
match count_lines(filename) {
Ok(count) => println!("{}: {} 行", filename, count),
Err(e) => {
eprintln!("エラー: {}", e);
process::exit(1);
}
}
}
fn count_lines(filename: &str) -> Result {
let content = fs::read_to_string(filename)?;
Ok(content.lines().count())
}
Rustのおすすめ学習リソース
- The Rust Programming Language(通称「The Book」):公式ドキュメント、日本語訳あり
- Rustlings:小さな演習問題集、ハンズオンで学べる
- Exercism Rust Track:実践的な課題で学ぶ
- Zero To Production In Rust:WebアプリのバックエンドをRustで構築
まとめ
Rustは学習曲線が急ですが、所有権・借用・ライフタイムという概念を理解すれば、メモリ安全で高性能なシステムプログラミングが可能になります。エンジニアとしてのスキルセットにRustを加えることで、将来的なキャリアの幅も大きく広がります。まずは公式ドキュメントと小さなCLIツール作成から始めてみましょう!