Как поделиться объектами свойств, выделенными в куче? - PullRequest
3 голосов
/ 16 марта 2019

У меня есть черта и структура, реализующая эту черту (объект черты). Я хотел бы разместить свои объекты черт в куче и сделать так, чтобы другие структуры ссылались на них.

Поле поля

trait Material {}

struct Iron {}

impl Material for Iron {}

// It works, but takes ownership of boxes.
struct Sphere {
    radius: f64,
    material: Box<dyn Material>,
}

Этот код работает, но у меня не может быть двух сфер, совместно использующих Material, потому что Box владеет материалом, а сфера владеет своим полем Box.

Справочное поле

Моя следующая попытка - использовать нормальную ссылку вместо Box:

struct Sphere<'a> {
    radius: f64,
    material: &'a dyn Material,
}

Это тоже работает, но, насколько я понимаю, мои Material будут размещаться в стеке, а не в куче. Что, если значение Material действительно большое, и я бы предпочел, чтобы оно было в куче? Это приводит меня к следующему подходу, который не компилируется:

Ссылка на ящик

struct Sphere<'a> {
    radius: f64,
    material: &'a Box<dyn Material>,
}

fn main() {
    let m1 = &Box::new(Iron {});
    let s1 = Sphere {
        radius: 1.0,
        material: m1,
    };
    assert_eq!(s1.radius, 1.0);
}

Это дает мне следующую ошибку:

error[E0308]: mismatched types
  --> src/main.rs:16:19
   |
16 |         material: m1,
   |                   ^^ expected trait Material, found struct `Iron`
   |
   = note: expected type `&std::boxed::Box<(dyn Material + 'static)>`
              found type `&std::boxed::Box<Iron>`

Я не совсем уверен, откуда взялся 'static в этом типе, и похоже, что он сбивает с толку проверку типов. В противном случае, насколько я понимаю, dyn Material и Iron могут быть объединены.

Детская площадка

1 Ответ

4 голосов
/ 16 марта 2019

Rc или Arc

Когда вам нужно долевое владение , Rc или Arc, обычно это первый инструмент, к которому обращаются.Эти типы реализуют совместное использование путем подсчета ссылок, поэтому клонирование одного обходится дешево (просто скопируйте указатель и увеличьте счетчик ссылок).В этом случае любой из них работает удобно:

struct Sphere {
    radius: f64,
    material: Rc<dyn Material>,
}

let m1 = Rc::new(Iron {});
let s1 = Sphere {
    radius: 1.0,
    material: m1,
};

m1 относится к конкретному типу Rc<Iron>, но поскольку он реализует черту CoerceUnsized, он может быть автоматически вызывается в контекстах, ожидающих Rc<dyn Material>.Вы можете сделать несколько Sphere ссылок на один и тот же материал, набрав clone ing m1.( Полный пример )

Разница между Rc и Arc заключается в том, что Arc безопасно использовать для совместного использования между несколькими потоками, а Rc - нет.(Также см. Когда использовать Rc vs Box? )

Ссылки

Что касается вашего справочного примера:

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

Это правда, что времена жизни получены из стека, но сама ссылка не делаетнужно указать на что-то в стеке.Например, вы можете взять ссылку на T в Box<T>, разыменовав Box:

struct Sphere<'a> {
    radius: f64,
    material: &'a dyn Material,
}

let m1 = Box::new(Iron {});
let s1 = Sphere {
    radius: 1.0,
    material: &*m1, // dereference the `Box` and get a reference to the inside
};
let s2 = Sphere {
    radius: 2.0,
    material: &*m1,
};

Это даже дешевле, чем использовать Rc, поскольку & ссылки Copy, но даже если сам Iron хранится в куче, ссылки, указывающие на него, все еще связаны с временем жизни стек переменной m1.Если вы не можете заставить m1 жить достаточно долго, вы, вероятно, захотите использовать Rc вместо простых ссылок.

Ссылка на Box

Этот подход также долженработать, хотя это и не нужно.Причина этого заключается в том, что, хотя вы можете привести Box<Iron> к Box<dyn Material>, вы не можете привести &Box<Iron> к &Box<dyn Material>;типы несовместимы.Вместо этого вам нужно создать переменную стека типа Box<dyn Material>, чтобы вы могли ссылаться на нее.

let m1: Box<dyn Material> = Box::new(Iron {}); // coercion happens here
let s1 = Sphere {
    radius: 1.0,
    material: &m1,  // so that this reference is the right type
};
...