#rust
#Ржавчина
Вопрос:
Я хочу объявить универсальную функцию, которая принимает объекты признаков и только объекты признаков. Я хочу этого, потому что я хочу ввести erase и передать их как TraitObject
объекты через границу ABI.
Функция, написанная подобным образом, не сможет скомпилироваться…
fn f<T: ?Sized>(t: amp;T) -> std::raw::TraitObject {
unsafe { std::mem::transmute(t) }
}
… со следующей ошибкой:
error[E0512]: transmute called with differently sized types: amp;T (pointer to T) to std::raw::TraitObject (128 bits)
Я понимаю, почему компилятор жалуется на разные размеры: amp;T
может быть указателем на конкретный тип (like amp;i32
), который представляет собой один указатель (64 бита), или объект признака (like amp;Display
), который будет представлять собой два указателя с тем же расположением, std::raw::TraitObject
что и (128 бит).
Эта функция должна работать нормально, если amp;T
она является объектом признака, т.Е. T
Является признаком. Есть ли способ выразить это требование?
Комментарии:
1. И, чтобы было ясно, это не может быть конкретный объект признака? Причиной
fn f(t: amp;SomeTrait)
всегда будет объект признака.2. @Shepmaster Правильно. Без дженериков все отлично.
Ответ №1:
Невозможно доказать отрицательный результат… но, насколько я знаю, ответ отрицательный, извините.
Представление TraitObject
нестабильно, особенно потому, что в будущем Rust сможет привязывать несколько виртуальных указателей к одному указателю данных (представляющему amp;(Display Eq)
, например).
В то же время я обычно использую низкоуровневые приемы памяти для чтения виртуального указателя и указателя данных, а затем создаю TraitObject
сам; защищается вызовом mem::size_of
, чтобы убедиться, что amp;T
имеет правильный размер для 2 *mut ()
, потому ?Sized
что означает Sized
или нет (и нет !Sized
).
Комментарии:
1. Я только что понял, что
transmute_copy
transmute
для компиляции функции достаточно использовать вместо, и, похоже, она работает должным образом (если, конечно, T является признаком) play.rust-lang.org /. … Это те низкоуровневые трюки с памятью, о которых вы думали?2. (На заметку: ГОСПОДИ, у меня более 100 тыс. репутации, и я не могу опубликовать сокращенную ссылку на игровую площадку в комментарии по какой-то нечестивой причине)
3. @R.MartinhoFernandes: Нет, это не так; на самом деле это намного лучше. Я удивлен, что он работает с ненормированными типами, хотя, поскольку
?Sized
в документации нет привязки. Я рекомендую вам опубликовать это в качестве ответа, это намного лучше, чем возиться с указателями. Я также рекомендую вам использовать проверку размера, чтобы проверить, что во время выполнения переданные данные действительно имеют 128 бит (а не 64), посколькуtransmute_copy
не проверяется (и имеет неопределенное поведение, если ему нужно считывать за пределы исходного объекта).4. Да, я бы добавил проверку размера, если я использую этот подход. Я просто не делал этого в тесте.
5. Также обратите внимание, что он не работает с типами без размера.
Foo
является признаком, следовательно, безразмерным, ноamp;Foo
является объектом признака и, следовательно, имеет размер. То, чтоtransmute_copy
копируетamp;Foo
, неFoo
является. (Т.Е. Аргумент на самом делеamp;amp;Foo
в моем примере)
Ответ №2:
Если вы <a rel="noreferrer noopener nofollow" href="https://play.rust-lang.org/?code=#![feature(raw)]
use std::raw::TraitObject;
use std::mem;
trait Foo {
fn f(&self);
}
struct Bar;
impl Foo for Bar {
fn f(&self) { println!("ok") }
}
fn erase(r: &’a T) -> TraitObject { unsafe { mem::transmute_copy(&r) } }
fn recover(r: TraitObject) -> &’a T { unsafe { mem::transmute_copy(&r) } }
fn erase_foo(r: &’a Foo) -> TraitObject { unsafe { mem::transmute(r) } }
fn recover_foo(r: TraitObject) -> &’a Foo { unsafe { mem::transmute(r) } }
fn main() {
let r: &Foo = &Bar;
let x = erase(r);
let xf = erase_foo(r);
println!(«{:?} {:?}», x.data, x.vtable);
println!(«{:?} {:?}», xf.data, xf.vtable);
let y: &Foo = recover(x);
let yf = recover_foo(xf);
y.f();
yf.f();
}amp;version=nightlyamp;backtrace=0″ rel=»nofollow»>используете transmute_copy
вместо этого, вы можете заставить компилятор игнорировать несоответствия размера. Однако это означает, что вы должны самостоятельно решать такие проблемы, например, самостоятельно проверять размер и, возможно, паниковать, если есть несоответствие. Невыполнение этого требования может привести к неопределенному поведению.
fn f<T: ?Sized>(t: amp;T) -> std::raw::TraitObject {
assert!(std::mem::size_of::<amp;T>() == std::mem::size_of::<std::raw::TraitObject>());
unsafe { std::mem::transmute_copy(amp;r) }
}
Ответ №3:
Я считаю , что ответ «нет»:
Невозможно ссылаться на все объекты признаков в общем виде
(выделение мое)