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

В этом коде я беру вектор, создаю экземпляр структуры и добавляю его в вектор в штучной упаковке:

trait T {}

struct X {}
impl T for X {}

fn add_inst(vec: &mut Vec<Box<T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    // Ugly, unsafe hack I made
    unsafe { std::mem::transmute(&**vec.last().unwrap()) }
}

Очевидно, он использует mem::transmute, что заставляет меня чувствовать, что это неправильный способ сделать это. Это уродливый хак единственный способ сделать это?

Кроме того, хотя он компилируется в Rust 1.32, в Rust 1.34 происходит сбой:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/lib.rs:10:14
   |
10 |     unsafe { std::mem::transmute(&**vec.last().unwrap()) }
   |              ^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `&dyn T` (128 bits)
   = note: target type: `&X` (64 bits)

Ответы [ 2 ]

3 голосов
/ 22 апреля 2019

Ваш "уродливый взлом" на самом деле совершенно неверен и небезопасен.Вам не повезло, что Rust 1.32 не сообщает об ошибке, но, к счастью, Rust 1.34 делает.

Когда вы сохраняете в штучной упаковке значение, вы создаете тонкий указатель .Это занимает целочисленный для платформы размер целого числа (например, 32-разрядный в 32-разрядном x86, 64-разрядный в 64-разрядном x86 и т.объект, вы создаете толстый указатель .Он содержит тот же указатель на данные и ссылку на vtable .Этот указатель имеет размер два нативных целых числа:

+----------+----------+
| pointer  | vtable   |
| (0x1000) | (0xBEEF) |
+----------+----------+

При попытке выполнить преобразование объекта признака в ссылку вы теряете один из этих указателей, но это не определено, какой .Там нет никакой гарантии, что на первом месте: указатель данных или vtable.

Одно решение будет использовать std::raw::TraitObject, но это нестабильно, потому что расположение жирных указателей все еще находится в воздухе.

Я бы порекомендовал решение, которое не требует кода unsafe, это использование Any:

use std::any::Any;

trait T: Any {}

struct X {}
impl T for X {}

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    let l = vec.last().unwrap();
    Any::downcast_ref(l).unwrap()
}

Если вы не можете / не хотите использовать Any, Мне сказали , что приведение указателя объекта черты к указателю на конкретный тип сохранит только указатель данных.К сожалению, я не могу найти официальную ссылку для этого, что означает, что я не могу полностью поручиться за этот код, хотя он работает эмпирически:

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    let last: &dyn T = &**vec.last().unwrap();

    // I copied this code from Stack Overflow without reading
    // it and it may not actually be safe.
    unsafe {
        let trait_obj_ptr = last as *const dyn T;
        let value_ptr = trait_obj_ptr as *const X;
        &*value_ptr
    }
}

См. Также:

3 голосов
/ 22 апреля 2019

Я думаю, что этот код безопасен:

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    let b = Box::new(x);
    let ptr = &*b as *const X;
    vec.push(b);
    unsafe { &*ptr }
}

Хитрость заключается в том, чтобы сохранить необработанный указатель в *const X перед преобразованием его в Box<dyn T>.Затем вы можете преобразовать его обратно в ссылку перед возвратом из функции.Это безопасно, потому что коробочное значение никогда не перемещается (если, конечно, оно не вышло из Box, поэтому) ptr переживает приведение b в Box<dyn T>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...