はじめに
こんにちは!ITスクールRareTECHにてCS(Customer Support)を担当している池村です。今回の記事はRustの参照と借用についてとなります。借用はRust独特の考え方ですかね。前回の所有権の話と繋がっているので、まずはそちらを学習してからをオススメします。
今回の記事はRustの『The Book』にある内容を自分なりに噛み砕いて記事にしたものです。
参照と借用
早速本題ですが、所有権が違うデータを所有権をムーブさせることなく渡すことができる機能のことを参照と借用と言います。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
// '{}'の長さは、{}です
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
上記のコードは、String型の変数の文字列の長さを測るための関数を準備して実際に動かしています。
calculate_length
関数にある引数のs
は、s1
を参照するための変数となります。あくまで参照しているだけなので、s1
が持っている所有権がムーブしているわけではありません。要は&
(アンバサンド)をつけることで、参照させることができるようになるんですね。
ここが大事だと思う部分ですが。
参照がスコープを抜けても値はドロップされない!
所有権をもらわないということは、所有権を返すようなこともなくなるんですね。
この関数の引数に参照を取ることを借用と言います。
借用したものの取り扱い
借用したデータに追加で文字列を加えてみます。
fn main() {
let s = String::from("Hello");
let len = test_length(&s);
print!("{}の文字数は {}文字です", s, len);
}
fn test_length(s: &String) -> usize {
s.push_str(", World");
s.len()
}
これはエラーになるようですね。
error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
--> src/main.rs:9:5
|
9 | s.push_str(", World");
| ^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
8 | fn test_length(s: &mut String) -> usize {
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `Rust_Training` (bin "Rust_Training") due to 1 previous error
日本語にしてみましょう。
--> src/main.rs:9:5
|
9 | s.push_str(", World");
| ^ `s`は`&`参照なので、参照しているデータを可変として借用することはできません
だそうです。
変数がそもそも不変なので、参照先でも不変ということですね。
じゃあmut
をつければ可変にできるのか?->結論:できます。
fn main() {
let mut s = String::from("Hello");
let len = test_length(&mut s);
print!("{}の文字数は {}文字です", s, len);
}
fn test_length(s: &mut String) -> usize {
s.push_str(", World");
s.len()
}
書き方として気をつけたいのは、引数に渡す時の書き方と、引数定義の部分の書き方ですね。
test_length(&mut s)
fn test_length(s: &mut String)
&mut
という修飾子で差別化しているということがわかりました。
注意点として、可変な参照は1回しか持てないという部分。(特定のスコープ内)
可変な参照は1度のみ
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
上記はエラーになります。
error[E0499]: cannot borrow `s` as mutable more than once at a time
(エラー: 一度に`s`を可変として2回以上借用することはできません)
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
| (最初の可変な参照はここ)
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
| (二つ目の可変な参照はここ)
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
ここは他の言語とかなり違うところですね。データ競合を防ぐための仕組みになっているようです。
これならOK👇
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1はここでスコープを抜けるので、問題なく新しい参照を作ることができる
let r2 = &mut s;
不変で借用したもの
let mut s = String::from("hello");
let r1 = &s; // 問題なし
let r2 = &s; // 問題なし
let r3 = &mut s; // 大問題!
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
(エラー: `s`は不変で借用されているので、可変で借用できません)
--> borrow_thrice.rs:6:19
複数の不変参照を作ることは許可されているようです。
ダングリング参照について
ダングリングとは、無効なメモリアドレスを指しているポインタや参照のことを言います。データ自体がなくなっているのに、その参照やポインタが残っていたりすることですね。エラー要因としてかなり大きな問題ですね。
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
これは参照だけを返していますが、関数のスコープ内から出た時点で変数s
のデータはドロップされるわけですから、何もないところを指すデータを返しています。
これは&
を使わず、直接s
を返すことで所有権がムーブされることで解決します。
法則として
- 任意のタイミングで、一つの可変参照か不変な参照いくつでものどちらかを行える
- 参照は常に有効でなければならない
この辺りは実際に色々作ってみた時に出てくる問題な気がしているので、ゴリゴリ書いてエラーとぶつかって慣れるしかなさそうです。
おわりに
今回は所有権の続きで参照と借用についてまとめました。
2025年年明け一つ目の記事でしたが、割とすんなり理解できた気がします。
元々Rustを触り始めたのはこれらの概念が面白そうと思ったからですが、実際に概念理解し始めると楽しいですね。基礎的なところは程々に、あとは実際にアプリでも作ってみたいと思います。