Могу ли я привязать общий тип, который требует, чтобы этот тип был признаком?

#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:

Я считаю , что ответ «нет»:

Невозможно ссылаться на все объекты признаков в общем виде

(выделение мое)