web-dev-qa-db-ja.com

Rustのstructでライフタイムを使用する正しい方法は何ですか?

この構造を書きたい:

struct A {
    b: B,
    c: C,
}

struct B {
    c: &C,
}

struct C;

B.cA.cから借用する必要があります。

A ->
  b: B ->
    c: &C -- borrow from --+
                           |
  c: C  <------------------+

これは私が試したものです:struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: C,
}

impl<'a> A<'a> {
    fn new<'b>() -> A<'b> {
        let c = C;
        A {
            c: c,
            b: B { c: &c },
        }
    }
}

fn main() {}

しかし失敗します:

error[E0597]: `c` does not live long enough
  --> src/main.rs:17:24
   |
17 |             b: B { c: &c },
   |                        ^ borrowed value does not live long enough
18 |         }
19 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'b as defined on the method body at 13:5...
  --> src/main.rs:13:5
   |
13 |     fn new<'b>() -> A<'b> {
   |     ^^^^^^^^^^^^^^^^^^^^^

error[E0382]: use of moved value: `c`
  --> src/main.rs:17:24
   |
16 |             c: c,
   |                - value moved here
17 |             b: B { c: &c },
   |                        ^ value used here after move
   |
   = note: move occurs because `c` has type `C`, which does not implement the `Copy` trait

私はRust所有権に関するドキュメントを読みましたが、それを修正する方法がわかりません。

27
Quan Brew

上記のコードが失敗する理由は、実際には複数あります。それを少し分解して、それを修正する方法についていくつかのオプションを調べてみましょう。

最初にnewを削除し、Aのインスタンスをmainに直接作成してみます。これにより、問題の最初の部分が存続期間とは関係がないことがわかります。

struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: C,
}

fn main() {
    // I copied your new directly here
    // and renamed c1 so we know what "c"
    // the errors refer to
    let c1 = C;

    let _ = A {
        c: c1,
        b: B { c: &c1 },
    };
}

これは失敗します:

error[E0382]: use of moved value: `c1`
  --> src/main.rs:20:20
   |
19 |         c: c1,
   |            -- value moved here
20 |         b: B { c: &c1 },
   |                    ^^ value used here after move
   |
   = note: move occurs because `c1` has type `C`, which does not implement the `Copy` trait

つまり、c1cに割り当てた場合、その所有権はcに移動します(つまり、c1を介してアクセスできなくなります。 cを介して)。これは、c1へのすべての参照が無効になることを意味します。ただし、&c1がまだスコープ内(B)にあるため、コンパイラーはこのコードをコンパイルできません。

コンパイラは、タイプCがコピー不可であると言ったときに、エラーメッセージで考えられる解決策を示します。 Cのコピーを作成できた場合、c1cに割り当てると、値の所有権を移動する代わりに、値の新しいコピーが作成されるため、コードは有効になります。オリジナルのコピー。

次のように定義を変更することで、Cをコピー可能にすることができます。

#[derive(Copy, Clone)]
struct C;

これで上記のコードが機能します。 @ matthieu-mのコメント はまだtrueであることに注意してください。 値への参照と値自体の両方をBに保存することはできません (参照を保存しています)値とここでの値のコピー)。ただし、これは構造体だけでなく、所有権の仕組みです。

これで、Cをコピー可能にしたくない(またはできない)場合は、代わりにABの両方に参照を格納できます。

struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: &'a C, // now this is a reference too
}

fn main() {
    let c1 = C;
    let _ = A {
        c: &c1,
        b: B { c: &c1 },
    };
}

じゃあ大丈夫?本当に... Aの作成をnewメソッドに戻したいと思っています。そして、それは私たちが一生に苦労して実行する場所です。 Aの作成をメソッドに戻しましょう:

impl<'a> A<'a> {
    fn new() -> A<'a> {
        let c1 = C;
        A {
            c: &c1,
            b: B { c: &c1 },
        }
    }
}

予想通り、これが寿命エラーです。

error[E0597]: `c1` does not live long enough
  --> src/main.rs:17:17
   |
17 |             c: &c1,
   |                 ^^ borrowed value does not live long enough
...
20 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 13:1...
  --> src/main.rs:13:1
   |
13 | impl<'a> A<'a> {
   | ^^^^^^^^^^^^^^

error[E0597]: `c1` does not live long enough
  --> src/main.rs:18:24
   |
18 |             b: B { c: &c1 },
   |                        ^^ borrowed value does not live long enough
19 |         }
20 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 13:1...
  --> src/main.rs:13:1
   |
13 | impl<'a> A<'a> {
   | ^^^^^^^^^^^^^^

これは、c1newメソッドの最後で破棄されるため、その参照を返すことができないためです。

fn new() -> A<'a> {
    let c1 = C; // we create c1 here
    A {
        c: &c1,          // ...take a reference to it
        b: B { c: &c1 }, // ...and another
    }
} // and destroy c1 here (so we can't return A with a reference to c1)

可能な解決策の1つは、Cの外にnewを作成し、それをパラメーターとして渡すことです。

struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: &'a C
}

fn main() {
    let c1 = C;
    let _ = A::new(&c1);
}

impl<'a> A<'a> {
    fn new(c: &'a C) -> A<'a> {
        A {c: c, b: B{c: c}}
    }
}

遊び場

36
Paolo Falabella

#Rust IRCでManishearthとeddybを確認した後、構造体がそれ自体またはその一部への参照を格納することは不可能だと思います。したがって、Rustの型システムでは、実行しようとしていることは不可能です。

4
Rufflewind