Rustの複数の生存期間パラメータでハマった
問題となったソースコード
RustでPath
cargoを使って、いろいろファイル操作をしていました。
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言語と比べるとかなり安全なコードがかけるので学習コストより恩恵の方が多いように思います。