Rust入門:経験者のための基礎ガイド 2026年版
C++、Java、Pythonの経験を活かしてRustを効率的に学ぶためのガイドです。所有権、借用、ライフタイムなど、Rust特有の概念を体系的に解説します。

Rustは年々採用が拡大しており、コンパイル時にメモリ安全性を保証しながらC++と同等のパフォーマンスを発揮し、モダンなエコシステムを備えた言語として注目されています。C++、Java、Pythonなどの経験がある開発者にとって、Rustの学習は最初こそ戸惑いがありますが、基本概念を理解すれば驚くほど直感的になります。
RustはStack Overflowで8年連続「最も愛されている言語」に選ばれています。Microsoft、Google、Amazon、Metaがクリティカルなコンポーネントに採用しており、ガベージコレクターなしでメモリ安全性を実現する唯一の主要言語です。
Rust開発環境のセットアップ
Rustのコーディングを始める前に、公式のバージョン管理ツールであるrustupを使ってインストールを行います。
# install.sh
# Install Rust via rustup (macOS, Linux)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify installation
rustc --version
cargo --versionCargoはRustのパッケージマネージャーであり、ビルドツールでもあります。npm、Maven、Makeの機能を一つの統合ツールにまとめた存在です。
# project-setup.sh
# Create a new project
cargo new my_project
cd my_project
# Generated structure:
# my_project/
# ├── Cargo.toml # Manifest (like package.json)
# └── src/
# └── main.rs # Entry point
# Essential commands
cargo build # Compile the project
cargo run # Compile and run
cargo test # Run tests
cargo check # Check without building (faster)変数とデフォルトの不変性
Rustは一般的な言語とは逆の発想を取っています。変数は**デフォルトで不変(イミュータブル)**です。この設計により、可変性について明示的に考えることを強制し、多くのバグを未然に防ぎます。
fn main() {
// Immutable by default
let x = 5;
// x = 6; // Compile error!
// Explicitly mutable variable
let mut y = 5;
y = 6; // OK
// Shadowing: redeclaration in the same scope
let x = x + 1; // Creates a new variable x
let x = x * 2; // x is now 12
// Shadowing also allows changing the type
let spaces = " "; // &str
let spaces = spaces.len(); // usize
}シャドーイングは新しい変数を作成するのに対し、mutは既存の値を変更します。シャドーイングを使えば、明確な名前を保ちながら値を変換できます。
基本データ型
Rustは強力な型推論を備えた静的型付け言語です。ここでは知っておくべき基本的なプリミティブ型を紹介します。
fn main() {
// Signed integers: i8, i16, i32, i64, i128, isize
let age: i32 = 30;
// Unsigned integers: u8, u16, u32, u64, u128, usize
let count: u64 = 1_000_000; // Underscores for readability
// Floats: f32, f64 (default)
let pi: f64 = 3.14159;
// Boolean
let active: bool = true;
// Unicode character (4 bytes)
let emoji: char = '🦀';
// Tuple: fixed collection of different types
let person: (String, i32) = (String::from("Alice"), 28);
let (name, age) = person; // Destructuring
// Array: fixed size, same type
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let first = numbers[0];
// Slice: view into a portion of data
let slice: &[i32] = &numbers[1..4]; // [2, 3, 4]
}所有権:Rustの革新的コンセプト
所有権(Ownership)はRust最大のイノベーションです。ガベージコレクターなしでメモリ安全性を保証する仕組みであり、初期の学習コストはかかりますが、その見返りは非常に大きいです。
所有権の3つのルール
fn main() {
// Rule 1: Each value has a single owner
let s1 = String::from("hello");
// Rule 2: When the owner goes out of scope, the value is freed
{
let s2 = String::from("world");
// s2 is valid here
}
// s2 is dropped, no longer accessible
// Rule 3: Only one ownership at a time (move)
let s3 = s1; // s1 is MOVED to s3
// println!("{}", s1); // Error: s1 is no longer valid!
println!("{}", s3); // OK: s3 is the owner
}ムーブとクローン
fn main() {
// Simple types (stack): automatic Copy
let x = 5;
let y = x; // Copy, not move
println!("x = {}, y = {}", x, y); // Both are valid
// Complex types (heap): Move by default
let s1 = String::from("hello");
let s2 = s1; // Move
// s1 is no longer usable
// Explicit clone to duplicate
let s3 = String::from("world");
let s4 = s3.clone(); // Deep copy
println!("s3 = {}, s4 = {}", s3, s4); // Both valid
}値を関数に渡すと所有権が移動します。関数が所有者となり、呼び出し後はその値にアクセスできなくなります。戻り値として返さない限り、元のスコープでは使用できません。
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // Error: s has been moved
let x = 5;
makes_copy(x);
println!("{}", x); // OK: i32 implements Copy
// To regain ownership, return the value
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
println!("{}", s3); // OK
}
fn takes_ownership(s: String) {
println!("{}", s);
} // s is dropped here
fn makes_copy(x: i32) {
println!("{}", x);
}
fn takes_and_gives_back(s: String) -> String {
s // Returns ownership
}借用:所有権を移さない参照
借用(Borrowing)を使えば、所有権を移動せずに値を利用できます。Rustで最も頻繁に使われるメカニズムです。
fn main() {
let s1 = String::from("hello");
// Immutable reference: read-only
let len = calculate_length(&s1);
println!("Length of '{}': {}", s1, len); // s1 still valid
// Mutable reference: modification allowed
let mut s2 = String::from("hello");
change(&mut s2);
println!("{}", s2); // "hello, world"
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope but doesn't drop (it's a reference)
fn change(s: &mut String) {
s.push_str(", world");
}借用のルール
fn main() {
let mut s = String::from("hello");
// Rule 1: Multiple immutable references simultaneously OK
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// Rule 2: ONLY ONE mutable reference at a time
let r3 = &mut s;
// let r4 = &mut s; // Error: already borrowed mutably
println!("{}", r3);
// Rule 3: No mutable ref if immutable ref exists
let r5 = &s;
// let r6 = &mut s; // Error: r5 is still active
println!("{}", r5);
// Once r5 is used for the last time, mutable borrow is allowed
let r7 = &mut s; // OK: r5 is no longer used after this
r7.push_str("!");
}Rustの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
ライフタイム:参照の有効性を保証する仕組み
ライフタイムは参照が有効であり続けることを保証します。コンパイラが自動的に推論する場合がほとんどですが、明示的なアノテーションが必要になるケースもあります。
// Classic error: reference to freed data
// fn dangling() -> &String {
// let s = String::from("hello");
// &s // Error: s will be dropped, invalid reference!
// }
// Solution: return the owned value
fn no_dangle() -> String {
let s = String::from("hello");
s // Ownership transferred, no problem
}ライフタイムアノテーション
// The compiler can't figure out which reference will be returned
// fn longest(x: &str, y: &str) -> &str { ... } // Error!
// Explicit annotation: return lives as long as BOTH x AND y
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
println!("Longest: {}", result); // OK here
}
// println!("{}", result); // Error: string2 dropped
}Rustにはライフタイムの省略規則(Elision Rules)があり、単純なケースではアノテーションを省略できます。初学者の場合、コンパイラのエラーメッセージに従うだけで十分対応可能です。
構造体とメソッドの実装
構造体(Struct)はRustにおけるカスタム型の基本構成要素です。
// Struct definition
#[derive(Debug)] // Enables printing with {:?}
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
// Implementation block for methods
impl User {
// Constructor (convention: fn new or descriptive name)
fn new(username: String, email: String) -> Self {
Self {
username,
email,
active: true,
sign_in_count: 1,
}
}
// Method: takes &self (reference to instance)
fn is_active(&self) -> bool {
self.active
}
// Method with mutation: takes &mut self
fn deactivate(&mut self) {
self.active = false;
}
// Method consuming self (rare)
fn into_username(self) -> String {
self.username
}
}
fn main() {
let mut user = User::new(
String::from("alice"),
String::from("alice@example.com"),
);
println!("Active: {}", user.is_active());
user.deactivate();
println!("Active: {}", user.is_active());
// Debug print
println!("{:?}", user);
}列挙型とパターンマッチング
Rustの列挙型(Enum)は、多くの言語のenumよりも遥かに強力です。各バリアントがデータを持つことができます。
// Simple enum
enum Direction {
North,
South,
East,
West,
}
// Enum with data (algebraic data type)
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
impl Message {
fn process(&self) {
match self {
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Writing: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Color: rgb({}, {}, {})", r, g, b)
}
}
}
}
fn main() {
let msg = Message::Move { x: 10, y: 20 };
msg.process();
let msg2 = Message::Write(String::from("Hello Rust!"));
msg2.process();
}OptionとResult:エラーハンドリングの基礎
use std::fs::File;
use std::io::{self, Read};
fn main() {
// Option<T>: present or absent value (replaces null)
let numbers = vec![1, 2, 3];
let first: Option<&i32> = numbers.first();
match first {
Some(n) => println!("First: {}", n),
None => println!("Empty list"),
}
// Utility methods
let value = first.unwrap_or(&0);
let doubled = first.map(|n| n * 2);
// Result<T, E>: success or error
let result = read_file("config.txt");
match result {
Ok(content) => println!("Content: {}", content),
Err(e) => println!("Error: {}", e),
}
}
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // ? propagates the error
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}?演算子はエラー伝播のためのシンタックスシュガーです。ResultがErrの場合は自動的にエラーを返し、Okの場合はその中身を取り出します。
トレイト:Rustにおけるポリモーフィズム
トレイトは共通の振る舞いを定義するもので、Javaのインターフェースやswiftのプロトコルに相当します。
// Trait definition
trait Summary {
fn summarize(&self) -> String;
// Method with default implementation
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..50.min(self.summarize().len())])
}
}
struct Article {
title: String,
author: String,
content: String,
}
struct Tweet {
username: String,
content: String,
}
// Implementation for Article
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
// Implementation for Tweet
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
// Function accepting any type implementing Summary
fn notify(item: &impl Summary) {
println!("Breaking news: {}", item.summarize());
}
// Equivalent syntax with trait bound
fn notify_generic<T: Summary>(item: &T) {
println!("Breaking news: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 2026"),
author: String::from("Community"),
content: String::from("..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
content: String::from("Rust is awesome!"),
};
notify(&article);
notify(&tweet);
}標準ライブラリの主要コレクション
Rustの標準ライブラリには強力なコレクション型が用意されています。
use std::collections::HashMap;
fn main() {
// Vec<T>: dynamic array
let mut numbers: Vec<i32> = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
// vec! macro for initialization
let nums = vec![1, 2, 3, 4, 5];
// Iteration
for n in &nums {
println!("{}", n);
}
// Functional methods
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
let sum: i32 = nums.iter().sum();
let evens: Vec<&i32> = nums.iter().filter(|x| *x % 2 == 0).collect();
// String: growable UTF-8 string
let mut s = String::from("Hello");
s.push_str(", World!");
s.push('!');
// Concatenation
let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1 moved, s2 borrowed
// or with format!
let s4 = format!("{}{}", "Hello, ", "World!");
// HashMap<K, V>
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// Access with get (returns Option)
if let Some(score) = scores.get("Blue") {
println!("Blue: {}", score);
}
// Entry API for conditional insertion
scores.entry(String::from("Yellow")).or_insert(25);
}Rustにおけるイディオマティックなエラーハンドリング
適切なエラーハンドリングはRust開発において不可欠です。ここでは推奨されるパターンを紹介します。
use std::fs::File;
use std::io::{self, Read};
use std::num::ParseIntError;
// Define a custom error type
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
Custom(String),
}
// Implement From for automatic conversion
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self {
AppError::Io(err)
}
}
impl From<ParseIntError> for AppError {
fn from(err: ParseIntError) -> Self {
AppError::Parse(err)
}
}
// Function returning Result with custom error
fn read_number_from_file(path: &str) -> Result<i32, AppError> {
let mut file = File::open(path)?; // io::Error -> AppError
let mut content = String::new();
file.read_to_string(&mut content)?;
let number: i32 = content.trim().parse()?; // ParseIntError -> AppError
Ok(number)
}
fn main() {
match read_number_from_file("number.txt") {
Ok(n) => println!("Number: {}", n),
Err(AppError::Io(e)) => println!("IO error: {}", e),
Err(AppError::Parse(e)) => println!("Parse error: {}", e),
Err(AppError::Custom(msg)) => println!("Error: {}", msg),
}
}実際のプロジェクトでは、thiserrorクレート(ライブラリ向け)とanyhowクレート(アプリケーション向け)を活用することで、エラーハンドリングの実装を大幅に簡素化できます。
Rustのテスト機能
Rustはテストフレームワークを言語自体に統合しています。外部ライブラリなしで、すぐにテストを書き始めることが可能です。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
// Test module (compiled only for `cargo test`)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10, 2), Ok(5));
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10, 0).is_err());
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_panic() {
let v = vec![1, 2, 3];
let _ = v[99]; // Panic!
}
}まとめ
Rustはメモリ安全性とパフォーマンスを両立する独自のパラダイムを提供します。所有権と借用の概念は最初こそ制約に感じますが、実践を重ねることで自然に身につきます。Rustコンパイラは強力な味方であり、そのエラーメッセージは業界で最も優れたものの一つです。
Rust学習チェックリスト
- rustupでRustをインストールし、Cargoの基本コマンドを習得する
- 不変性とmutによる明示的な可変性の違いを理解する
- 所有権の3つのルールを確実にマスターする
&と&mutを使った借用を実践で身につける- nullや例外の代わりに
OptionとResultを活用する #[test]アトリビュートでテストを記述する
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
Rustコミュニティは協力的で、学習リソースも豊富です。公式ドキュメント「The Rust Programming Language」は無料でオンライン公開されています。ここで紹介した基礎を固めた上で、async/await、マクロ、WebAssemblyなどの高度なトピックへ進むことが推奨されます。
タグ
共有


