こんにちは。
ひとまず動くようにする方法を紹介すると、data
の型を Vec<Vec<String>>
と &str
ではなく String
を保持するようにし、残りのプログラムも適当に書き換えるとコンパイルが通るようになります。
rust
1use std::fs::File;
2use std::io::{BufRead, BufReader};
3
4fn main() -> std::io::Result<()> {
5 let mut data: Vec<Vec<String>> = Vec::new();
6 for line in BufReader::new(File::open("data.csv")?).lines() {
7 // `&str` から `String` に変換する
8 data.push(line?.split(',').map(String::from).collect::<Vec<_>>());
9 }
10
11 //以下に行番号・列番号の入力を受け付けてデータを出力するコードを書く...
12 //...
13 //...
14
15 Ok(())
16}
少し解説を加えます。
Rustの文字列にはデータを所有する String
と、データ(の一部)を参照する &str
があります。
今回は &str
の参照とライフタイムの問題でした。
元のコードでは、まず for
内で1行読み、String
型の値 line
を確保しています。
text
1for {
2 line: String
3 |
4 v
5 +---+---+---+---+---+
6 | a | , | b | , | c |
7 +---+---+---+---+---+
8}
そのあとに split
でそのデータを参照した &str
を作っていました。
text
1for {
2 line: String
3 |
4 v
5 +---+---+---+---+---+
6 | a | , | b | , | c |
7 +---+---+---+---+---+
8 ^ ^ ^ ^ ^ ^
9 +---+ +---+ +---+
10 | | |
11 tmp1 tmp2 tmp3
12 | | |
13 +-------+-------+
14 |
15 splitで生成した参照
16}
これは、 line
のデータを参照しているので line
のライフタイムである for
内までしか生存しません。
text
1for {
2 line: String
3 |
4 v
5 +---+---+---+---+---+
6 | a | , | b | , | c |
7 +---+---+---+---+---+
8 ^ ^ ^ ^ ^ ^
9 +---+ +---+ +---+
10 | | |
11 tmp1 tmp2 tmp3
12 | | |
13 +-------+-------+
14 |
15 splitで生成した参照
16
17 // <-- ここで終わり
18}
しかし、この参照を for
内より長生きする data
に保管しようとするのでエラーになっています。
text
1data
2
3for {
4 line: String
5 |
6 v
7 +---+---+---+---+---+
8 | a | , | b | , | c |
9 +---+---+---+---+---+
10 ^ ^ ^ ^ ^ ^
11 +---+ +---+ +---+
12 | | |
13 tmp1 tmp2 tmp3
14 | | |
15 +-------+-------+
16 |
17 splitで生成した参照
18
19 // <-- ここで終わり
20}
21
22
23// <- dataはmainの末尾まで生きる
なので参照をやめて、新たに String
にデータを所有させるようにするのがこの提案です。
text
1for {
2 line: String
3 |
4 v
5 +---+---+---+---+---+
6 | a | , | b | , | c |
7 +---+---+---+---+---+
8 | | |
9 | | +---+
10 | +---------+ |
11 +--------------+ | |
12 | | |
13 +---+ | | |
14tmp1: String ->| a |<+ | |
15 +---+ | |
16 +---+ | | データをコピー
17tmp2: String ->| b |<---+ |
18 +---+ |
19 +---+ |
20tmp3: String ->| c |<-----+
21 +---+
22
23 // このあと `data` にムーブする
24}
一見コピーが走って無駄なように見えますが、Pythonの方の実装も同じような挙動をしているはずです。
Rustの split
が効率的すぎるだけです。
蛇足ですが、 line
を長生きする別の場所に一旦退避してから split
をとれば文字列データのコピーなく2次元ベクタを生成することもできます。
しかしこれは質問の内容に反しますね。
rust
1use std::fs::File;
2use std::io::{BufRead, BufReader};
3
4fn main() -> std::io::Result<()> {
5 // `data` は `&str` に戻った
6 let mut data: Vec<Vec<&str>> = Vec::new();
7 // データの一時退避場所を用意する
8 let mut preserve: Vec<String> = Vec::new();
9 for line in BufReader::new(File::open("data.csv")?).lines() {
10 preserve.push(line?);
11 }
12 // // forの中身がシンプルになったので上記は以下でもよい
13 // let preserve = BufReader::new(File::open("data.csv")?)
14 // .lines()
15 // .collect::<std::io::Result<Vec<_>>>()?;
16
17 for line in &preserve {
18 // `preserve` のおかげでライフタイムが伸びて、エラーが出ない
19 data.push(line.split(',').collect::<Vec<_>>());
20 }
21 //以下に行番号・列番号の入力を受け付けてデータを出力するコードを書く...
22 //...
23 //...
24
25 Ok(())
26}
蛇足も含めて参考になれば幸いです。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/25 12:17