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