web-dev-qa-db-ja.com

構造体の特性への参照

私は特性を持っていますFoo

_pub trait Foo {
   fn do_something(&self) -> f64;
}
_

そしてその特性を参照する構造体

_pub struct Bar {
   foo: Foo,
}
_

コンパイルしようとすると

_error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
_

構造体を変更する

_struct Bar {
   foo: &Foo,
}
_

_error: missing lifetime specifier_を教えて

定義を変更する

_struct Bar {
   foo: Box<Foo>,
}
_

コンパイル—イェーイ!

ただし、関数がfoobarを返すようにしたい場合は次のようにします。

_impl Bar {
    fn get_foo(&self) -> Foo {
        self.foo
    }
}
_

まあ明らかに_bar.foo_は_Box<Foo>_なので、予想通り_error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`_を取得します

署名を変更する

_impl Bar {
    fn get_foo(&self) -> Box<Foo> {
        let this = *self;
        this.foo
    }
}
_

しかし、今ではselfを逆参照しようとすると_error: cannot move out of dereference of `&`-pointer_を受け取ります。

に変更

_impl Bar {
    fn get_foo(self) -> Box<Foo> {
        self.foo
    }
}
_

すべて良いです。

そう....

  1. bar構造体の_&_が機能しないのはなぜですか?構造体にはメモリレイアウトが設定されているのでボックス化する必要があると想定しているため、それは特性へのポインタであると言わなければなりません(どのくらいの大きさになるかわかりません) ?
  2. get_foo()selfを逆参照できないのはなぜですか-私が見たすべての例は、借用したself構文を使用していますか?
  3. _&_を削除して、単にselfを使用することの意味は何ですか?

Rustは魅力的ですが、記憶の安全性は魅力的で威圧的です!

コンパイルする完全なコード:

_trait Foo {
    fn do_something(&self) -> f64;
}

struct Bar {
    foo: Box<Foo>,
}

impl Bar {
    fn get_foo(self) -> Box<Foo> {
        let foo = self.foo;
        foo.do_something();
        foo
    }
}

fn main() {}
_
54
neil danson

これは特性オブジェクトのトリッキーなポイントです。基礎となるオブジェクトの所有者について非常に明確にする必要があります。

実際、特性をタイプとして使用する場合、特性オブジェクトは実際には指定された特性を実装するオブジェクトへの参照であるため、基礎となるオブジェクトはどこかに保存する必要があります。これが、タイプとして裸のMyTraitを持つことができない理由です。それは、参照_&MyTrait_またはボックス_Box<MyTrait>_でなければなりません。

参照あり

あなたが試した最初の方法は、リファレンスを使用することであり、コンパイラはライフタイム指定子の欠落について不平を言っていました:

_struct Bar {
   foo : &Foo,
}
_

問題は、参照が基礎となるオブジェクトを所有しておらず、他のオブジェクトまたはスコープがどこかで所有している必要があることです。あなたはそれを借りているだけです。したがって、コンパイラは、この参照が有効である期間に関する情報を必要とします。基礎となるオブジェクトが破棄された場合、Barインスタンスは解放されたメモリへの参照を持ちますが、これは禁止されています!

ここでの考え方は、ライフタイムを追加することです。

_struct Bar<'a> {
   foo : &'a (Foo + 'a),
}
_

ここでコンパイラに言っていることは、「私のBarオブジェクトはその内部のFoo参照よりも長生きできません」です。ライフタイムを2回指定する必要があります。1回は参照のライフタイムに、もう1回は特性オブジェクト自体に、特性は参照に実装できるため、基礎となるオブジェクトが参照である場合は、その寿命も指定する必要があります。

特別な場合には次のように書くでしょう:

_struct Bar<'a> {
   foo : &'a (Foo + 'static),
}
_

この場合、_'static_では、基礎となるオブジェクトが実際の構造体または_&'static_参照である必要がありますが、他の参照は許可されません。

また、オブジェクトを構築するには、自分で保存した他のオブジェクトへの参照を与える必要があります。

次のような結果になります。

_trait Foo {}

struct MyFoo;

impl Foo for MyFoo {}

struct Bar<'a> {
    foo: &'a (Foo + 'a),
}

impl<'a> Bar<'a> {
    fn new(the_foo: &'a Foo) -> Bar<'a> {
        Bar { foo: the_foo }
    }

    fn get_foo(&'a self) -> &'a Foo {
        self.foo
    }
}

fn main() {
    let myfoo = MyFoo;
    let mybar = Bar::new(&myfoo as &Foo);
}
_

ボックス付き

Boxはそのコンテンツを逆に所有しているため、基礎となるオブジェクトの所有権をBar構造体に与えることができます。ただし、この基礎となるオブジェクトは参照になる可能性があるため、有効期間も指定する必要があります。

_struct Bar<'a> {
    foo: Box<Foo + 'a>
}
_

基になるオブジェクトが参照にならないことがわかっている場合は、次のように書くこともできます。

_struct Bar {
    foo: Box<Foo + 'static>
}
_

そして寿命の問題は完全に消えます。

したがって、オブジェクトの構造は似ていますが、基礎となるオブジェクトを自分で保存する必要がないため、より簡単です。ボックスによって処理されます。

_trait Foo {}

struct MyFoo;

impl Foo for MyFoo {}

struct Bar<'a> {
    foo: Box<Foo + 'a>,
}

impl<'a> Bar<'a> {
    fn new(the_foo: Box<Foo + 'a>) -> Bar<'a> {
        Bar { foo: the_foo }
    }

    fn get_foo(&'a self) -> &'a Foo {
        &*self.foo
    }
}

fn main() {
    let mybar = Bar::new(box MyFoo as Box<Foo>);
}
_

この場合、_'static_バージョンは次のようになります。

_trait Foo {}

struct MyFoo;

impl Foo for MyFoo {}

struct Bar {
    foo: Box<Foo + 'static>,
}

impl Bar {
    fn new(the_foo: Box<Foo + 'static>) -> Bar {
        Bar { foo: the_foo }
    }

    fn get_foo<'a>(&'a self) -> &'a Foo {
        &*self.foo
    }
}

fn main() {
    let mybar = Bar::new(box MyFoo as Box<Foo>);
    let x = mybar.get_foo();
}
_

裸の値で

最後の質問に答えるには:

&を削除し、自己を使用することの意味は何ですか?

メソッドに次のような定義がある場合:

_fn unwrap(self) {}
_

つまり、プロセスでオブジェクトを消費し、bar.unwrap()を呼び出した後、barを使用できなくなります。

これは一般的に、構造体が所有していたデータの所有権を返すために使用されるプロセスです。標準ライブラリのunwrap()関数にたくさん会うでしょう。

99
Levans

将来の参照のために注意してください:構文は

struct Bar<'a> {
    foo: &'a Foo + 'a,
}

struct Bar<'a> {
    foo: &'a (Foo + 'a), // with parens
}

RFC 438

13
Arien Malec