Syntax and Semantics
Lifetimes
ずっと後回しにしてきましたが、今回はlifetimeを扱います。 Rustのlifetimeは、リソースの寿命に名前をつけて明確にし、 dangling pointerと、それによるuse after freeを防ぐ仕組みです。
例えば、関数にある参照aを渡し、それにリソースを生成し、その参照bを返す場合、
aの有効範囲は、bの有効範囲を包含していなくてはなりません。
さもないと、aが不正な参照になった後(dangling pointer)にも、
aと同時に不正になったbがつかわれてしまう(use after free)からです。
さて、まずはlifetimeに名前をつけるところから始めます。
Lifetimeを気にしないといけないのは、まずは関数を使うときです。 これまでlifetimeが表舞台には登場してこなかったように、 多くの場合、lifetimeの記述は省略できます(後述)。 省略しない場合は、genericsの型パラメータと同じ所に記述します。
// implicit
fn foo(x: &i32) {
}
// explicit
fn bar<'a>(x: &'a i32) {
}Lifetimeは、'aのように、単一の'のあとになんらかの名前をつけてあらわします。
二つ以上指定したり、mutと同時に使うときなどは次のようになります。
fn foo<'a, 'b>(x: &'a i32, y: &'b i32) { ... }
fn bar<'a>(x: &'a mut i32) { ... }戻り値にもlifetimeが指定できます。
fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
fn x_or_y<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { ... }ひとつ目は、x, yが同じlifetimeをもち、さらに戻り値も同じになります。
ふたつ目は、x, yが別々のlifetimeをもて、戻り値はxと同じlifetimeをもちます。
なお、参照ではないものにはlifetimeは必要ありません。
In structs
Structにもlifetimeを指定できます。 フィールドが参照の場合、そのlifetimeを決定するためです。
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let y = &5; // this is the same as `let _y = 5; let y = &_y;`
let f = Foo { x: y };
println!("{}", f.x);
}この場合、f.xはyと同じlifetimeを持ちます。
メソッドをimplする際のsyntaxは、genericsのときと同じです。
implとstruct名両方に<>で囲ってlifetimeを記述します。
impl<'a> Foo<'a> {
fn x(&self) -> &'a i32 { self.x }
}Thinking in scopes
Lifetimeは、言ってしまえば参照変数の有効範囲に名前をつけただけです。 この有効範囲はソースコードの領域として可視化できます。
まず、関数ローカルに参照を定義します。 この参照のlifetimeは、この関数を抜けるまでです。
fn main() {
let y = &5; // -+ y goes into scope
// |
// stuff // |
// |
} // -+ y goes out of scope参照をフィールドにもつstructを定義し、先ほどの参照でコンストラクトします。
Structfのlifetimeは、渡されたyのlifetimeと同じ、つまり関数を抜けるまでです。
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let y = &5; // -+ y goes into scope
let f = Foo { x: y }; // -+ f goes into scope
// stuff // |
// |
} // -+ f and y go out of scopeこのyとfを新たにスコープでくくると、y, fのlifetimeはそのスコープに狭まります。
このスコープ外に参照変数を用意して、f.xを指すようにすると……
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scopef.xは狭いスコープでlifetimeが切れますが、それを指すxのlifetimeは続いています。
ここでdangling pointerが起こっています。
Rustコンパイラはこれを見逃さず、エラーメッセージを吐くことになります。
'static
'staticという特別なlifetimeがあります。
名前からわかるように、このlifetimeは、プログラムの初めから終わりまでです。
以前にでてきましたが、文字列リテラルは'staticlifetimeを持ちます。
他には、グローバルに定義したstatic変数を指す参照は、'staticlifetimeを指定できます。
let x: &'static str = "Hello, world.";
static FOO: i32 = 5;
let x: &'static i32 = &FOO;Lifetime Elision
上述したように、関数定義においてlifetimeの記述は多くの場合省略できます。 この、「多くの場合」というのが、次で述べる3つのルールに当てはまる場合です。
ルールについて説明する前に、関数定義におけるlifetimeには二種類あることを説明します。 ‘Input lifetime’と’output lifetime’です。 その名の通り、input lifetimeは参照型の引数がもつlifetimeで、 output lifetimeは戻り値がもつlifetimeです。
fn foo<'a>(bar: &'a str) { ... }
fn foo<'a>() -> &'a str { ... }
fn foo<'a>(bar: &'a str) -> &'a str { ... }ひとつ目の例では、input lifetimeがひとつ、 ふたつ目の例では、output lifetimeがひとつ、 みっつ目の例では、同一のinput lifetimeとoutput lifetimeがひとつずつあります。
そして、lifetimeが省略できる場合の3つのルールというのが以下になります。
- 省略されたlifetimeは、それぞれが別名のlifetimeをあらわす。
- (省略されたかどうかに関わらず)ちょうどひとつのinput lifetimeがあるとき、 省略されたoutput lifetimeは、そのinput lifetimeと同一になる。
- Input lifetimeが複数あり、そのうちひとつが
&selfまたは&mut selfのとき (つまりstructのメソッドのとき)、 省略されたoutput lifetimeはselfと同じlifetimeになる。
これらに当てはまらないときは、output lifetimeを省略できません。 明示的に指定する必要があります。
Examples
3つのルールに従って、省略されたlifetimeは実際にはどのように推論されるか見てみましょう。
fn substr(s: &str, until: u32) -> &str; // elided
fn substr<'a>(s: &'a str, until: u32) -> &'a str; // expandedInput lifetmeがひとつなので、ルール2.によって、 output lifetimeはinput lifetimeとおなじになります。
fn get_str() -> &str; // ILLEGAL, no inputsこの例はどのルールにも当てはまらないので、output lifetimeを省略できません。 コンパイルエラーになります。
fn frob(s: &str, t: &str) -> &str; // ILLEGAL, two inputs
fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // Expanded: Output lifetime is ambiguousまず、ルール1.によってsとtには別々のlifetimeが推論されます。
しかし、input lifetimeが複数あるにも関わらず、どれも&self, &mut selfではないので、
output lifetimeを省略できません。
fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command; // elided
fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // expandedルール1.によって、&mut selfとargsは別々のlifetimeを持ちます。
Input lifetimeの一つが&mut selfなので、ルール3.によって、戻り値のlifetimeは、
&mut selfと同じになります。
おもったほど難しくはありませんでしたね。 Lifetimeについては以上です。 同時に、Rustのsyntax and semaiticsについて学ぶのは今回で最後にしようと思います。 まだ公式リファレンスのsyntax and semantics部 には、
- Crates and Modules
- Macros
- Raw pointers
- ‘unsafe’
が残っていますが、最初の二つは長いので読むだけにしておきたいし、 残り二つはなるべく使わないようにするほうがいいでしょう。 FFIなどで必要になりそうですが、しばらくは触れないでおきます。