Rustの所有権と借用:完全ガイド
Rustの所有権と借用システムをマスターします。所有権ルール、参照、ライフタイム、高度なメモリ管理パターンを解説します。

所有権システムは、Rustを他のあらゆるプログラミング言語と区別する特徴です。この独自のアプローチは、ガベージコレクタなしでメモリ安全性を保証し、実行時ではなくコンパイル時にバグを検出します。この詳細なガイドでは、所有権と借用のメカニズムを基礎から本番環境向けの高度なパターンまで解説します。
Rustコンパイラは厳格なプログラミングアシスタントとして機能します。コンパイル時にブロックされる所有権エラーは、本番環境で防がれた潜在的なバグを意味します。
所有権の3つの基本ルール
所有権システムは、シンプルでありながら厳格な3つのルールに基づいています。これらを身につければ、Rustのメンタルモデルは自然で予測可能なものになります。
// Demonstration of the three fundamental rules
fn main() {
// Rule 1: Each value has exactly ONE owner
let s1 = String::from("hello"); // s1 is the sole owner
// Rule 2: There can only be one owner at a time
let s2 = s1; // Ownership transferred (moved) from s1 to s2
// println!("{}", s1); // Compile ERROR: s1 no longer exists
println!("s2 = {}", s2); // Only s2 is valid now
// Rule 3: When the owner goes out of scope, the value is dropped
{
let s3 = String::from("temporary");
println!("s3 inside block = {}", s3);
} // s3 is automatically freed here (drop is called)
// println!("{}", s3); // ERROR: s3 no longer exists
}これら3つのルールは、use-after-free、double-free、メモリリークといったバグのカテゴリ全体を排除します。コンパイラはこれらのルールが守られていることを静的に検証します。
MoveとCopy:転送セマンティクスの理解
代入の振る舞いはデータ型に依存します。Copyトレイトを実装する型は複製され、それ以外の型はmoveによって転送されます。
// Distinction between Copy types and Move types
fn main() {
// Copy types: values stored on the stack, known size
let x: i32 = 42;
let y = x; // x is COPIED, not moved
println!("x = {}, y = {}", x, y); // Both are valid
// Other Copy types: f64, bool, char, tuples of Copy types
let point = (3.0, 4.0);
let point_copy = point; // Tuple copy
println!("Original: {:?}, Copy: {:?}", point, point_copy);
// Move types: values on the heap, dynamic size
let s1 = String::from("owned");
let s2 = s1; // s1 is MOVED to s2
// println!("{}", s1); // ERROR: value moved
println!("s2 = {}", s2);
// Vec, HashMap, Box are also Move types
let vec1 = vec![1, 2, 3];
let vec2 = vec1; // Move, not copy
// println!("{:?}", vec1); // ERROR
println!("vec2 = {:?}", vec2);
}
// Explicit clone to duplicate Move types
fn explicit_clone() {
let original = String::from("important data");
let clone = original.clone(); // Explicit duplication (memory cost)
println!("Original: {}", original); // Still valid
println!("Clone: {}", clone); // Independent copy
}Move/Copyの区別は基本的なものです。代入が所有権を転送するのか、独立したコピーを作成するのかを決定します。
.clone()の呼び出しは意図的であるべきです。cloneだらけのコードは設計上の問題を示しているかもしれません。借用がより良い解決策となることが多いです。
借用:不変参照と可変参照
借用により、所有権を取得することなく値にアクセスできます。このメカニズムにより、Rustコードは安全かつ高性能になります。
// Immutable and mutable references
fn main() {
let s = String::from("hello");
// Immutable reference: read-only, multiple allowed
let len = calculate_length(&s); // Immutable borrow
println!("'{}' has {} characters", s, len); // s still valid
// Multiple simultaneous immutable references: OK
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("r1={}, r2={}, r3={}", r1, r2, r3);
}
fn calculate_length(s: &String) -> usize {
// s is a reference, not the owner
s.len()
} // s goes out of scope but doesn't drop anything (not owner)
// Mutable references: modification allowed
fn mutable_borrowing() {
let mut s = String::from("hello");
change(&mut s); // Mutable borrow
println!("After modification: {}", s);
}
fn change(s: &mut String) {
s.push_str(", world!"); // Modification via mutable reference
}借用の黄金ルール:複数の不変参照、または1つの可変参照のいずれかであり、両者が同時に存在することは決してありません。
ボローチェッカーのルール
ボローチェッカーは借用ルールを検証するコンパイラのコンポーネントです。そのエラーを理解することで、問題を素早く解決できます。
// Strict borrow checker rules
fn main() {
// RULE 1: No mutable reference with immutable references
let mut s = String::from("hello");
let r1 = &s; // Immutable reference: OK
let r2 = &s; // Another immutable reference: OK
// let r3 = &mut s; // ERROR: cannot borrow as mutable
println!("{} and {}", r1, r2);
// AFTER using r1 and r2, they are "dead"
let r3 = &mut s; // Now OK: r1 and r2 no longer used
r3.push_str(" world");
println!("{}", r3);
// RULE 2: Only one mutable reference at a time
let mut data = String::from("exclusive");
let ref1 = &mut data;
// let ref2 = &mut data; // ERROR: already borrowed mutably
ref1.push_str("!");
println!("{}", ref1);
}
// RULE 3: References cannot outlive the data
fn dangling_reference_prevented() {
let reference;
{
let s = String::from("short-lived");
// reference = &s; // ERROR: s doesn't live long enough
}
// s is dropped here, reference would be invalid
// Solution: move the value out of the scope
let owned_outside;
{
let s = String::from("moved out");
owned_outside = s; // Move, not reference
}
println!("{}", owned_outside); // OK: owned_outside is the owner
}ボローチェッカーはNon-Lexical Lifetimes (NLL)を使用します。参照は最後に使用された時点までアクティブとみなされ、スコープの終わりまでではありません。
Rustの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
ライフタイム:参照の有効期間を注釈する
ライフタイムは、参照が有効であり続けることをコンパイラが検証するのを助ける注釈です。ほとんどの場合、自動的に推論されます。
// Explicit lifetime annotations
// Without annotation: compiler infers lifetimes
fn first_word(s: &str) -> &str {
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
// With explicit annotation: same function
fn first_word_explicit<'a>(s: &'a str) -> &'a str {
// 'a means: returned reference lives as long as the input
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
// When annotations are necessary: multiple references
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// Compiler cannot guess which reference is returned
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 if uncommented: string2 dropped
}ライフタイムはデータが生きる期間を変更するものではなく、異なる参照のライフタイム間の関係を記述します。
構造体におけるライフタイム
構造体が参照を含む場合、構造体が参照先のデータより長く生存しないことを保証するため、ライフタイムを注釈する必要があります。
// Structs containing references
// Struct with reference: lifetime required
struct ImportantExcerpt<'a> {
part: &'a str, // This reference must live at least as long as the struct
}
impl<'a> ImportantExcerpt<'a> {
// Method returning a reference with the same lifetime
fn level(&self) -> i32 {
3
}
// Elision rule: &self implies the output lifetime
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part // Returns with 'a lifetime from self
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence, // OK: novel outlives excerpt
};
println!("Excerpt: {}", excerpt.part);
println!("Level: {}", excerpt.level());
}
// Static lifetime: reference valid for the entire program duration
fn static_lifetime_example() {
let s: &'static str = "This string is in the binary";
// String literals always have 'static lifetime
println!("{}", s);
}ライフタイム省略のルールにより、一般的なケースでは注釈を省略でき、コードが読みやすくなります。
高度なパターン:内部可変性
可変性をコンパイル時ではなく実行時に検証する必要がある場合があります。Rustはこのパターンに対応する型を提供しています:RefCellとCellです。
// Interior mutability with RefCell and Cell
use std::cell::{Cell, RefCell};
// Cell: for Copy types, replaces the entire value
struct Counter {
count: Cell<u32>, // Mutable despite &self
}
impl Counter {
fn new() -> Counter {
Counter { count: Cell::new(0) }
}
fn increment(&self) {
// Modification via immutable reference!
self.count.set(self.count.get() + 1);
}
fn get(&self) -> u32 {
self.count.get()
}
}
// RefCell: for non-Copy types, checks at runtime
struct CachedValue {
value: RefCell<Option<String>>,
}
impl CachedValue {
fn new() -> CachedValue {
CachedValue { value: RefCell::new(None) }
}
fn get_or_compute(&self, compute: impl FnOnce() -> String) -> String {
// borrow() for reading, borrow_mut() for writing
if self.value.borrow().is_none() {
*self.value.borrow_mut() = Some(compute());
}
self.value.borrow().as_ref().unwrap().clone()
}
}
fn main() {
let counter = Counter::new();
counter.increment();
counter.increment();
println!("Counter: {}", counter.get()); // 2
let cache = CachedValue::new();
let result = cache.get_or_compute(|| {
println!("Expensive computation...");
String::from("result")
});
println!("Value: {}", result);
// Second call: no recomputation
let result2 = cache.get_or_compute(|| String::from("never executed"));
println!("Cache hit: {}", result2);
}RefCellとCellは借用検証を実行時に移します。ルール違反はコンパイルエラーではなくpanicを引き起こします。
RefCell::borrow_mut()は、値がすでに借用されている場合panicします。明示的なエラー処理にはtry_borrow_mut()を使うとよいでしょう。
スマートポインタと所有権
Box、Rc、Arcなどのスマートポインタは、特定のユースケースに対して異なる所有権戦略を提供します。
// Box, Rc, and Arc for different ownership patterns
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
// Box: single owner, data on the heap
fn box_example() {
let boxed = Box::new(vec![1, 2, 3, 4, 5]);
println!("Boxed vec: {:?}", boxed);
// Useful for: recursive types, large objects, trait objects
}
// Rc: reference counting, multiple owners (single-thread)
fn rc_example() {
let data = Rc::new(String::from("shared data"));
let clone1 = Rc::clone(&data); // Increments the counter
let clone2 = Rc::clone(&data);
println!("Count: {}", Rc::strong_count(&data)); // 3
println!("All share: {}, {}, {}", data, clone1, clone2);
} // Freed when counter reaches 0
// Arc: thread-safe Rc (Atomic Reference Counting)
fn arc_example() {
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..3).map(|i| {
let data_clone = Arc::clone(&data);
thread::spawn(move || {
println!("Thread {}: {:?}", i, data_clone);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
}
fn main() {
box_example();
rc_example();
arc_example();
}スマートポインタの選択は所有権パターンに依存します:単一(Box)、シングルスレッドでの共有(Rc)、マルチスレッドでの共有(Arc)です。
実用的な所有権パターン
所有権システムを中心にコードを構造化するための一般的なパターンを示します。
// Practical patterns for ownership management
// Pattern 1: Builder pattern with chained ownership
struct RequestBuilder {
url: String,
headers: Vec<(String, String)>,
timeout: Option<u64>,
}
impl RequestBuilder {
fn new(url: &str) -> Self {
RequestBuilder {
url: url.to_string(),
headers: Vec::new(),
timeout: None,
}
}
// Consumes self and returns the new self
fn header(mut self, key: &str, value: &str) -> Self {
self.headers.push((key.to_string(), value.to_string()));
self // Returns ownership
}
fn timeout(mut self, seconds: u64) -> Self {
self.timeout = Some(seconds);
self
}
fn build(self) -> Request {
Request {
url: self.url,
headers: self.headers,
timeout: self.timeout.unwrap_or(30),
}
}
}
struct Request {
url: String,
headers: Vec<(String, String)>,
timeout: u64,
}
// Pattern 2: Cow (Copy-on-Write) to avoid allocations
use std::borrow::Cow;
fn process_text(input: &str) -> Cow<str> {
if input.contains("REPLACE") {
// Allocation only if modification needed
Cow::Owned(input.replace("REPLACE", "NEW"))
} else {
// No allocation, returns a reference
Cow::Borrowed(input)
}
}
// Pattern 3: Take to extract from an Option
fn extract_value(data: &mut Option<String>) -> String {
data.take().unwrap_or_else(|| String::from("default"))
// take() replaces with None and returns ownership of the value
}
fn main() {
// Builder pattern
let request = RequestBuilder::new("https://api.example.com")
.header("Authorization", "Bearer token")
.header("Content-Type", "application/json")
.timeout(60)
.build();
println!("URL: {}, Timeout: {}s", request.url, request.timeout);
// Cow pattern
let text1 = process_text("hello world"); // No allocation
let text2 = process_text("hello REPLACE"); // Allocation
println!("{} | {}", text1, text2);
// Take pattern
let mut optional = Some(String::from("extracted"));
let value = extract_value(&mut optional);
println!("Value: {}, Option: {:?}", value, optional); // None
}これらのパターンは所有権システムを活用して、人間工学的で高性能なAPIを作成します。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
まとめ
Rustの所有権と借用システムは、メモリ管理におけるパラダイムシフトを表しています。一度マスターすれば、高性能で安全なコードを書くための強力な味方となります。
重要なポイント:
✅ 所有権の3つのルール:単一の所有者、所有権の転送、自動drop
✅ 借用:複数の不変参照、または1つの排他的可変参照
✅ ライフタイム:参照のライフタイム間の関係を注釈する
✅ 内部可変性:実行時に検証される可変性のためのRefCellとCell
✅ スマートポインタ:Box(単一)、Rc(共有)、Arc(thread-safe)
✅ 実用的なパターン:イディオマティックなAPIのためのBuilder、Cow、Take
ボローチェッカーは最初は厳しく感じるかもしれませんが、報告される各エラーは回避された潜在的なバグを表します。実践を通じて、所有権の観点で考えることが自然になり、すべての言語におけるコード品質が向上します。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
Rust のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年4月29日 更新
タグ
共有
関連記事

Rust所有権と借用を徹底解説 -- メモリ安全性の仕組みと実践パターン
Rustの所有権、借用、ライフタイムの仕組みをコード例とともに詳しく解説します。ボローチェッカーのエラー対処法や実務で使えるパターンも紹介します。

Rust面接質問:2026年版完全ガイド
Rustの技術面接で頻出の25問を完全網羅。所有権、借用、ライフタイム、トレイト、async/await、並行処理について詳細な回答とコード例付きで解説します。

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