Rustのブロックの返り値の式で借用するとエラーになることがある(2024Editionで解決予定)
これは何の記事?
先日week in rustを読んでいたところ、The Rust 2024 Edition takes shapeに気になる記述がありました。
The project is also fixing the order in which temporary expressions at the end of a block get cleaned up. Rust blocks have return values, which lets if statements be used as expressions, among other cases. But when an expression is returned from a block, it might create temporary values. For example, this code creates a temporary value for cell.borrow():
fn f() -> usize {
let cell = RefCell::new("..");
// The borrow checker throws an error for the below
// line in the 2021 edition, but not the 2024 edition
cell.borrow().len()
}
どうやらこのコードが所有権のエラーを発生させるようです。
へぇ___
.
.
.
いやなんで?????
ぜんぜん意味ワカラナイので、もう少し原因を調べてみました。
(ちなみに、Rust 2024 でこの仕様は修正されてエラーが出なくなるそうです。)
まずエラーの原因調査します
件のコードを試しにplayground[1]で試してみると、確かにエラーが発生します
Compiling playground v0.0.1 (/playground)
error[E0597]: `cell` does not live long enough
--> src/lib.rs:5:3
|
4 | let cell = RefCell::new("..");
| ---- binding `cell` declared here
5 | cell.borrow().len()
| ^^^^---------
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
6 | }
| -
| |
| `cell` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
|
= note: the temporary is part of an expression at the end of a block;
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
|
5 | let x = cell.borrow().len(); x
| +++++++ +++
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` (lib) due to 1 previous error
エラーメッセージをみると
5 | cell.borrow().len()
| ^^^^---------
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
borrowによってcellへの参照による一時変数が作られるのですが、参照先のcellが先にdropされてしまうことによるエラーの様です。
実際、その先のエラーを見るとcellはblockの終わりでdropされると書いてあります。
6 | }
| -
| |
| `cell` dropped here while still borrowed
つまり、
- 借用される変数(cell)はblockの終わりでdropされるが
- block最後の式でborrowし生成した一時変数はblockが終わっても残る
ということになります。
だから、最後の行を
let x = cell.borrow().len(); x
に書き換えてくださいということですね
なるほどー、理解できました!
...
...
いやいや、よくわからん
cell.borrow().len()
のborrow()
で一時変数が作られること、これが参照元のcell
より長生きしていることはわかりました。
え、なんで?
最後の式が返す値の内、利用したいのはlen()
が返す値のみのはずです。そしてこれはusize
です。borrow()
が作る一時変数がブロックの外まで生存する必要はないはずです。
なんでこんな仕様になってるのでしょうか?
もーちょっとだけ、原因調べてみました
他のドキュメントを漁ってみる
もう少し調べてみると、Rust Referenceにこんな記述がありました
The temporary scope of an expression is the scope that is used for the temporary variable that holds the result of that expression when used in a place context, unless it is promoted.
Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression and is one of the following:
- The entire function.
- A statement.
- The body of an if, while or loop expression.
- The else block of an if expression.
- The condition expression of an if or while expression, or a match guard.
- The body expression for a match arm.
- The second operand of a lazy boolean expression.
この記述によると、どうも A statement.
が関係あるようです。
一時変数のスコープは基本的にその文(statement)になることが多いようで、例えば
let x = cell.borrow().len();
の場合、borrow()
で発生した一時変数はこの文の間生存することになります。
最初のエラーになるコードの場合、
let x = fn();
のように、fn()
の最後のブロックの式の返り値がx
にバインドされる形になります。
つまり、
fn f() -> usize {
let cell = RefCell::new("..");
// The borrow checker throws an error for the below
// line in the 2021 edition, but not the 2024 edition
cell.borrow().len()
}
の最後の式で生成した一時変数が、呼び出し元で使われる可能性があることを考慮しているため、ブロック外まで生存する様な仕様になっていたのでした。
繰り返しますが、Rust 2024 でこの仕様は修正されてエラーが出なくなるそうです。
なのでこの記事の内容は「へえ〜そんなこともあるんだー」程度にしてもらえればと思います。
最後に
もう2025年では…??
追記
Rust2024は2/20リリース予定なので、もうすぐ解決予定です。
参考
- https://t.co/RyQS8N3Z0d
- https://doc.rust-lang.org/reference/destructors.html#temporary-scopes
- https://stackoverflow.com/questions/65972165/why-is-the-temporary-is-part-of-an-expression-at-the-end-of-a-block-an-error
Discussion