Подтвердить что ты не робот

Ссылки на черты в структурах

У меня есть черта 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>,
}

Скомпилирует - yay!

Однако, когда я хочу, чтобы функция возвращала Foo на bar - что-то вроде:

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
    }
}

Но теперь я получаю error: cannot move out of dereference of `&`-pointer при попытке разыменования self.

Переход на

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

Все хорошо.

Так....

  • Почему в работе bar не работает &? Я предполагаю, что мне нужно вставить поскольку структуры имеют заданный формат памяти, поэтому мы должны сказать, что это указатель к черту (поскольку мы не можем знать, насколько это будет важно), но почему компилятор предлагает что-то, что не компилируется?
  • Почему я не могу разыменовать self в get_foo() - все примеры, которые я видел, используют заимствованный синтаксис self?
  • Что подразумевается при удалении & и просто использовании self?

Обучение ржавчине увлекательно, но безопасность памяти одновременно увлекательна и запугивает!

Код полностью компилируется:

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() {}
4b9b3361

Ответ 1

Это сложная точка объектов-признаков, вам нужно четко указать, кто владеет базовым объектом.

Действительно, когда вы используете черту как тип, базовый объект должен быть где-то сохранен, поскольку объекты-объекты на самом деле ссылаются на объект, реализующий данный признак. Вот почему у вас не может быть голый MyTrait как тип, он должен быть либо ссылкой &MyTrait, либо поле Box<MyTrait>.

Со ссылками

Первый метод, который вы пробовали, был с ссылкой, и компилятор жаловался на отсутствующий спецификатор времени жизни:

struct Bar {
   foo : &Foo,
}

Проблема заключается в том, что ссылка не владеет базовым объектом, а другой объект или область должны владеть им где-то: вы заимствуете ее только. И, таким образом, компилятору нужна информация о том, как долго эта ссылка будет действительной: если основной объект был уничтожен, ваш экземпляр Bar имел бы ссылку на свободную память, что запрещено!

Идея здесь состоит в том, чтобы добавить времена жизни:

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

Что вы здесь говорите компилятору: "Мой объект Bar не может пережить ссылку Foo внутри него". Вы должны указать время жизни два раза: один раз для времени жизни ссылки и один раз для объекта-объекта, потому что черты могут быть реализованы для ссылок, а если базовый объект является ссылкой, вы также должны указать его время жизни.

В специальном случае записывалось бы:

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);
}

С коробками

Коробка, напротив, владеет своим контентом, таким образом, он позволяет вам передать право собственности на базовый объект на вашу структуру 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();
}

С открытым значением

Чтобы ответить на ваш последний вопрос:

Что подразумевает удаление и и просто использование self?

Если метод имеет такое же определение:

fn unwrap(self) {}

Это означает, что он будет потреблять ваш объект в процессе, и после вызова bar.unwrap() вы больше не сможете использовать bar.

Это процесс, который обычно используется для возврата права собственности на данные, принадлежащие вашей структуре. Вы встретите много функций unwrap() в стандартной библиотеке.

Ответ 2

Отметьте для справки в будущем: синтаксис изменился с

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

к

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

Per RFC 438