rustにてmutableなvecに自身の要素を追加しようとするとコンパイラのバージョンによっては借用errorになる

投稿者: Anonymous

フィボナッチ数を計算するコードを書きました。
次のコードは rustc 1.41.1 ではコンパイルが通りますが1.35.0ではエラーになります。
vecの要素はi32なのでindexアクセスをした際にcopyが行われ、借用は行われないと思うのですがどういう理由によるのでしょうか?また、どのように解決すればよいでしょうか?

fn main() {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    let n: usize = s.trim().parse().unwrap();

    let mut fib = vec![1,1];
    for i in 0..n-1{
        fib.push(fib[i]+fib[i+1]);
    }
    let ans = fib.last().unwrap();
    println!("{}", ans);
}

error文
error内容

解決

エラーの理由は、Indexアクセスを行う時に所有権の借用が行われるからです。
質問者さんのコードを例に記述いたしますと

1.「.push(」でmutable参照の借用が開始され
2.「fib[]」でimmutable参照の借用が開始され(Indexトレイト)
3.「);」でfib借用のライフタイムが終了

という流れになります。
恐らく察しが付いたと思いますが
これはRustの借用ルール(mutableとimmutableは同時に複数持てない)に違反するのでエラーになります。

そして本題、Rust1.41.1でエラーにならない件についてですが
Rust1.36.0から「Non-Lexical Lifetimes」という機能がデフォルト有効になりました
What are non-lexical lifetimes?

fib.push(fib[i]+fib[i+1]);という式において、参照が同時に使われる事が無いのは人の目で見て明確です
その事をコンパイラが認識できるようになり(適切なタイミングで借用を行う)
コンパイルが通るようになります

【解決策について】
1.ソースコードの変更
色々な解決方法があると思いますが
以下のように式を分離してしまうのが一番簡単かと(個人的には)思います

fn main() {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    let n: usize = s.trim().parse().unwrap();

    let mut fib = vec![1,1];

    for i in 0..n-1{
        let val = fib[i] + fib[i+1];
        fib.push(val);
    }
    let ans = fib.last().unwrap();
    println!("{}", ans);
}

2.Rust2018へ変更する
上記の海外stackoverflow回答にもある通り
cargoを利用しcargo.tomlへ以下のように記載します

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <[email protected]>"]
edition = "2018"

Rust2018については以下の記事を参照下さい
What is Rust 2018?
Rust 2018のリリース前情報

ただし、各記事でも述べられています通り
C++で言うC++11みたいな物なので副作用が有るかもしれません

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *