ライフタイム

13 分で読了
このページの目次

ライフタイムパラメータは、借用が満たさなければならない有効性の関係を記述するものであり、実行時のタイマーではありません。注釈はデータの寿命を延ばすことも、関数戻り値後にローカル変数が生存し続けることもありません。実装側と呼び出し側の両方が、シグネチャで与えられた制約を満たす必要があります。

同名パラメータは関係を表し、同じ実際の寿命を表すわけではない

外側の String内側の String結果が使用可能な共通領域2つの入力が共通のライフタイム制約を共有しても、短い方の入力の寿命は延びないchoose のシグネチャは任意の入力を返すことを許可しているため、呼び出し側は今回の選択だけで有効期間が長いと推測することはできません。
fn choose<'a>(left: &'a str, right: &'a str, use_left: bool) -> &'a str {
    if use_left { left } else { right }
}
fn main() {
    let outer = String::from("outer");
    {
        let inner = String::from("inner");
        let selected = choose(&outer, &inner, true);
        assert_eq!(selected, "outer");
    }
}

2つの入力が元々完全に同じ生存範囲を持つ必要はありません。コンパイラは、許可された関係の下で、呼び出しと結果の使用の両方を満たす共通領域を選択できます。シグネチャは任意の入力を返すことを許可しているため、呼び出し側は inner が無効になった後でも結果を持ち出すことはできず、use_left が今回の呼び出しで true だったからといって、契約を拡大することはできません。

関数が常に left を返す場合、出力を left のライフタイムのみに関連付け、right を無用な制約から解放すべきです。ライフタイム設計は API の精度設計でもあります。

左の入力のみに依存する場合、右の入力を独立させる

fn left_with_check<'a>(left: &'a str, right: &str) -> &'a str {
    assert!(!right.is_empty());
    left
}
fn main() {
    let outer = String::from("outer");
    let result;
    {
        let temporary = String::from("validation only");
        result = left_with_check(&outer, &temporary);
    }
    assert_eq!(result, "outer");
}

right は呼び出し中のチェックにのみ関与し、返される参照のデータソースではないため、より早く無効化できます。right も &'a str とした場合、呼び出し側は不要な共通制約を受けなければなりません。コンパイラは公開されたシグネチャに基づいて呼び出しを検査し、実装を呼び出しごとにのぞき込んで約束を緩和することはありません。

戻り値はどの入力に依存するか

区間を調整して戻り値の依存関係を見ます。使用位置を固定して right を縮め、次に戻り値を left だけに結び付けます。改善するのは無関係な依存を除いたためで、注釈がオブジェクトの寿命を延ばすためではありません。

図を準備しています
戻り値はどの入力に依存するか

両入力に結び付く戻り値は共通区間に制約され、left だけなら right より長く使えます。

省略規則は関数本体の推測に依存しない

関数パラメータで省略された参照のライフタイムは、それぞれ独立したパラメータを取得します。入力ライフタイムが1つのみの場合、それは省略された出力ライフタイムに使用されます。メソッドに &self または &mut self 受取手がある場合、出力はデフォルトで受取手の借用に関連付けられます。規則で決定できない場合は、明示的に表現する必要があります。省略規則

struct Excerpt<'s> { part: &'s str }
impl<'s> Excerpt<'s> {
    fn tied_to_self(&self) -> &str { self.part }
    fn tied_to_source(&self) -> &'s str { self.part }
}
fn main() {
    let source = String::from("chapter");
    let result;
    {
        let excerpt = Excerpt { part: &source };
        assert_eq!(excerpt.tied_to_self(), "chapter");
        result = excerpt.tied_to_source();
    }
    assert_eq!(result, "chapter");
}

2つのメソッドは同じフィールドを返しますが、シグネチャの約束が異なります。2番目の出力は source の有効期間に依存するため、Excerpt のラップが破棄された後も引き続き使用できます。1番目はデフォルトで今回の &self の借用にも制約されます。構造体が参照を保持する場合、ライフタイム制約は、データが許可する範囲を超えて有効に使用されないことを保証するものであり、参照に実行時の有効期限フィールドを追加するものではありません。

source:String「a,b,c」の全体を所有Fields<'input>残りの入力のスライスを保持返される &'input strフィールドの内容は source を借用next_field はパーサーを一時的に排他占有するだけで、以前に返されたフィールドを同時に保持できるパーサー状態の借用と入力テキストの借用は2つの異なる関係である成立する理由は、スライスビューを前方に進めるだけで入力を書き換えないためである。バッファリングされたリーダーをカバーするには、別の契約が必要。

複数の結果の借用を同時に保持できるパーサー

struct Fields<'input> { rest: Option<&'input str> }
impl<'input> Fields<'input> {
    fn new(input: &'input str) -> Self { Self { rest: Some(input) } }
    fn next_field(&mut self) -> Option<&'input str> {
        let remaining = self.rest.take()?;
        match remaining.split_once(',') {
            Some((first, rest)) => {
                self.rest = Some(rest);
                Some(first)
            }
            None => Some(remaining),
        }
    }
}
fn main() {
    let source = String::from("a,,c,");
    let first;
    {
        let mut fields = Fields::new(&source);
        first = fields.next_field().unwrap();
        let second = fields.next_field().unwrap();
        assert_eq!((first, second), ("a", ""));
        assert_eq!(fields.next_field(), Some("c"));
        assert_eq!(fields.next_field(), Some(""));
        assert_eq!(fields.next_field(), None);
    }
    assert_eq!(first, "a");
}

これはカンマ区切りのフィールドのみであり、引用符やエスケープを含む完全な CSV ではありません。Option は「最後の空フィールドが残っている」と「終了した」を区別するため、末尾のカンマは Some("") の後に None を生成します。next_field は &'input str を返すため、出力は入力に関連付けられ、今回の &mut self には関連付けられません。パーサー自体が破棄された後でも、first は source が有効な間に使用できます。

返却型を Option<&str> に省略した場合、メソッドの規則により出力が今回の self の借用に関連付けられ、古い結果を保持している間に next_field を再度呼び出すことができない可能性があります。これはコンパイラに恣意的なマーキングを追加するのではなく、結果がどのデータから来るのかを正確に宣言するものです。

&'static T と T: 'static

&'static T は、借用がプログラムの実行全体をカバーできることを意味します(例:文字列リテラル)。T: 'static は、T が 'static 制約を満たさない借用を含まないことを意味します。データを所有する String はこれを満たすことができます。この String が次の行で drop されたとしても同様です。

fn accepts_static<T: 'static>(value: T) { drop(value); }
fn main() {
    accepts_static(String::from("short lived owner"));
    let literal: &'static str = "literal";
    accepts_static(literal);
}

短い参照を move しても、それが 'static になるわけではありません。Box::leak は、T が対応する制約を満たす場合に長い借用を生成できますが、リークは通常のライフタイムエラーに対するデフォルトの修正手段となってはいけません。まずは値の返却、借用の短縮、またはリソースの所有権の調整を検討してください。

「借用する場合もあれば、新しい内容を生成する場合もある」を Cow で表現する

use std::borrow::Cow;
fn lowercase_ascii(text: &str) -> Cow<'_, str> {
    if text.bytes().any(|b| b.is_ascii_uppercase()) {
        Cow::Owned(text.to_ascii_lowercase())
    } else {
        Cow::Borrowed(text)
    }
}
fn main() {
    assert!(matches!(lowercase_ascii("ready"), Cow::Borrowed(_)));
    assert!(matches!(lowercase_ascii("READY"), Cow::Owned(_)));
    let saved;
    {
        let input = String::from("READY");
        saved = lowercase_ascii(&input).into_owned();
    }
    assert_eq!(saved, "ready");
}

返却型は、入力を借用する可能性のある関係を保持します。呼び出し側は「今回の文字列には大文字がある」という理由だけで、Cow 全体を 'static と見なすことはできません。into_owned は String を明確に取得します。借用ブランチではコピーが必要ですが、所有ブランチでは既存の String をそのまま引き渡します。この関数は ASCII の大文字小文字のみを処理し、Unicode の大文字小文字の変換や正規化アルゴリズムではありません。Cow

共変性はどのパラメータに対するものか明確にする

'a: 'b は 'a が 'b よりも長生きすることを意味し、すなわち 'a が少なくとも 'b をカバーする要件を満たすことを意味します。参照を短縮できるかどうかは、型パラメータの位置にも依存します。

型外側の 'a に対する共変性内部の T に対する共変性
&'a T共変共変
&'a mut T共変不変
UnsafeCell該当なし不変

ミュータブル参照は、それ自身のライフタイムに対して不変ではありません。参照先の T に対して不変です。もし T が &'static str である場合、&mut T を任意の短い借用に書き込める位置として扱うことはできません。共変性の規則

fn shorter<'long: 'short, 'short>(value: &'long mut i32) -> &'short mut i32 {
    value
}
fn main() {
    let mut number = 1;
    *shorter(&mut number) += 1;
    assert_eq!(number, 2);
}

逆方向のリスクは書き込みです。もし &mut &'static str が寿命の短い String の参照を受け入れることができ、外側の位置が引き続き 'static として使用される場合、ダングリング参照が発生します。これが内部の T が不変でなければならない理由であり、一時的な借用の短縮とは別問題です。

短い参照を書けると何が残るか

不変性は現在の読み取りだけでなく書き込み経路から考えます。元の場所は &'static str を保持しますが、内部型を緩めると短いビューから local を書けてしまい、その後も元の場所は static を約束します。外側の借用だけを短くしても書ける型は変わりません。

図を準備しています
短い参照を書けると何が残るか

&mut T は外側のライフタイムに共変、T に不変で、static を要求する場所への短い参照の書き込みを防ぎます。

HRTB:コールバックは呼び出しのたびに借用を適用できなければならない

fn apply<F>(function: F)
where F: for<'a> Fn(&'a str) -> &'a str,
{
    let local = String::from("  local  ");
    assert_eq!(function(&local), "local");
    assert_eq!(function(" literal "), "literal");
}
fn trim(text: &str) -> &str { text.trim() }
fn main() { apply(trim); }

for<'a> は、コールバックが適用可能な任意の 'a に対してインターフェースを満たすことを要求し、apply が内部の一時借用でそれを呼び出すことを許可します。これは、外側の呼び出し側が特定の 'a を選択するものとは異なります。Fn(&str) -> &str の省略形でも、ここでの高階ライフタイム関係を表現できます。

PhantomData は裸のポインタを自動的に検証しない

PhantomData<&'a T> は、実際の参照フィールドを持たない構造体において、型検査中に論理的な借用関係を表現し、ライフタイム、共変性、および自動トレイトの推論に影響を与えます。しかし、これはアドレスを検証するものではなく、コンストラクション時にポインタがその借用から来ていることを保証するものでもありません。

裸のポインタをカプセル化するイテレータは、有効なスライスなどのエントリポイントから範囲とライフタイムの制約を確立する必要があり、初期化、アライメント、およびアクセスルールを維持する必要があります。単に構造体にマーキングを追加し、適当に &'a T を返しても、まだ安全ではない可能性があります。関連する証明については unsafe 契約 を参照してください。

ライフタイムエラーに対して対応するインターフェース修正を選択する

エラーの原因調整すべき関係
ローカルで作成された値への参照を返している所有値を返すか、呼び出し側にストレージを提供させる
出力が無関係な入力によって制限されている入力ライフタイムを分割し、実際のソースのみに関連付ける
パーサーの古い結果が次の呼び出しを妨げている結果が外部入力に依存しているのか、上書き可能な内部バッファに依存しているのかを確認する
非同期タスクが 'static を要求している所有データを移動するか、短い借用を許可するスコープメカニズムを使用する
コールバックが内部の一時参照を保存したい所有コピーを保存するか、コールバック中の消費のみに変更する
共有位置とミュータブル位置のライフタイム変換に失敗している型変性を確認する。特に &mut T 内部の T

for<'a> コールバックは呼び出し側の内部一時データを受け取れるため、ちょうどそれがこの短い借用をより長寿命のコンテナに任意に保存できないことを意味しています。データを保持したい場合は、コールバック内で to_owned を使用できます。長さをカウントするだけ、または所有数値に解析するだけでよい場合は、これらの結果を返すだけです。ライフタイムシグネチャはストレージ戦略と一致しているべきであり、'a や 'static を追加して当てずっぽうに解決しようとしてはいけません。