Rustの複数の生存期間パラメータでハマった
問題となったソースコード
RustでPathcargoを使って、いろいろファイル操作をしていました。
app.rs
let input_path_str = matches.value_of("input").unwrap(); let output_path_str = matches.value_of("output"); let (input_path, output_path) = utils::get_output_file_name(input_path_str, output_path_str);
- helper.rs
pub fn get_output_file_name(input_path_str: &str, output_file_str: Option<&str>) -> (&Path, &Path) { let output_path: &Path; let input_path = Path::new(input_path_str); if let Some(output_file_str) = output_file_str {; output_path = Path::new(output_file_str); return (input_path, output_path); } ... return (input_path, output_path); }
すると以下のようなエラーが出るのです。
error[E0106]: missing lifetime specifier
--> src/utils.rs:7:86
|
7 | pub fn get_output_file_name(input_path_str: &str, output_file_str: Option<&str>) -> (&Path, &Path) {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `input_path_str` or `output_file_str`
error[E0106]: missing lifetime specifier
--> src/utils.rs:7:93
|
7 | pub fn get_output_file_name(input_path_str: &str, output_file_str: Option<&str>) -> (&Path, &Path) {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `input_path_str` or `output_file_str`
本記事では、一旦、問題を簡略化して考えようと思います。
問題
問題としては
- 生存期間をコンパイラができていないコードを書いている
です。簡略化して以下のようなソースコードを考えましょう。
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); }
longest関数は以下のように定義されてあります。
fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
これも同様のエラーがでるのですが、何が悪いのでしょう。
error[E0106]: missing lifetime specifier
--> src/main.rs:1:33
|
1 | fn longest(x: &str, y: &str) -> &str {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `x` or `y`
これはxがyのどっちの生存期間をもつのがわからないというエラーが出ます。
if文によって分岐したものはxも返しうるし、yも返す可能性があります。
関数として外に出しているからなおさらわかりにくいと思います。
例えば以下のようになっていたらどうでしょう。
fn main() { let string1 = "hoge" let result { let string2 = "piyo" result = longest(string1, string2); } println!("result in {}", result} }
この場合は生存期間は明らかにstring1とstring2は違います。
もしlongest関数に渡されても、戻り値はどっちの生存期間であるかはわかりません。
解決策
問題はxかyかの生存期間がわからないことにつきます。
つまり、明示的に指定すればいいので生存期間パラメータを使います。
これによって生存期間aが生きる限りは関数の戻り値が生きることを指定します。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
生存期間パラメータがジェネリクスで指定しなければいけないのは、必然だと思います。
このように明示的に生存期間パラメータをつけることをmake signatureといいます。
longent関数が呼び出された時にRustはaにあたる生存期間が短いxかyを探し出します。
これは
fn main() { let string1 = "hoge" { let string2 = "piyo" let result = longest(string1, string2); println!("result in {}", result} } }
のときにうまくいってました。 しかし、以下のようにしたらどうでしょう。
fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); }
errorがでます。これはprintlnが呼び出されるときに生存期間パラメータよりstring2も生存しておかないといけないからです。
このときは以下の様に対処することができます。
fn longest<'a>(x: &'a str, y: str) -> &'a str { if x.len() > y.len() { x } else { y } }
このようにすることで生存期間によって制約を課すことなく、実行できました。
まとめ
Rustに入門すると生存期間についてつまづいて、そのままやめてしまう人が多いと思います。
私自身、入門した頃は非常に難しく感じていましたが、C言語と比べるとかなり安全なコードがかけるので学習コストより恩恵の方が多いように思います。