Почему нельзя вызвать функцию для объекта-признака, если она ограничена параметром Self: Sized?

У меня такой код:

trait Bar {
    fn baz(&self, arg: impl AsRef<str>)
    where
        Self: Sized;
}

struct Foo;

impl Bar for Foo {
    fn baz(&self, arg: impl AsRef<str>) {}
}

fn main() {
    let boxed: Box<dyn Bar> = Box::new(Foo);
    boxed.baz();
}

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

Что приводит к этой ошибке:

error: the `baz` method cannot be invoked on a trait object
  --> src/main.rs:15:11
   |
15 |     boxed.baz();
   |           ^^^

Почему это невозможно? Он работает, когда я удаляю Self: Sized привязку, но тогда я не могу использовать универсальные шаблоны, которые делают функцию более удобной для вызывающего.

Это не дубликат Почему общий метод внутри признака требует изменения размера объекта признака?, который спрашивает, почему вы не можете вызов baz из объекта-признака. Я не спрашиваю, зачем нужна привязка; это уже обсуждалось.


person Tim Diekmann    schedule 13.08.2018    source источник


Ответы (2)


Потому что система дженериков Rust работает через мономорфизацию.

В Java, например, параметры типа в универсальной функции превращаются в переменные типа Object и приводятся по мере необходимости. Обобщения в таких языках, как этот, просто служат инструментом, помогающим проверить правильность типов в коде.

Такие языки, как Rust и C ++, используют мономорфизацию для дженериков. Для каждой комбинации параметров типа вызывается универсальная функция, генерируется специализированный машинный код, который запускает эту функцию с этими комбинациями параметров типа. Функция мономорфизируется. Это позволяет хранить данные на месте, исключает затраты на приведение типов и позволяет универсальному коду вызывать «статические» функции для этого параметра типа.

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

Объекты трейтов на многих языках, включая Rust, реализованы с помощью vtable. Когда у вас есть какой-либо тип указателя на объект-признак (необработанный, ссылка, бокс, счетчик ссылок и т. Д.), Он содержит два указателя: указатель на данные и указатель на запись vtable . Запись vtable - это набор указателей на функции, хранящихся в неизменяемой области памяти, которые указывают на реализацию методов этой черты. Таким образом, когда вы вызываете метод для объекта-признака, он ищет указатель функции реализации в vtable, а затем выполняет косвенный переход к этому указателю.

К сожалению, компилятор Rust не может мономорфизировать функции, если он не знает во время компиляции код, реализующий функцию, что имеет место, когда вы вызываете метод для объекта-признака. По этой причине вы не можете вызывать универсальную функцию (ну, универсальную функцию над типами) для объекта-признака.

-Редактировать-

Похоже, вы спрашиваете, зачем : Sized ограничение.

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

Однако Rust пытается явно указать, что делает компилятор, что противоречит этим неявным подходам. В любом случае, не было бы запутанным для новичка попытаться вызвать универсальную функцию для объекта-признака, и она не скомпилировалась?

Вместо этого Rust позволяет явно сделать весь трейт небезопасным для объекта.

trait Foo: Sized {

Или явно сделать определенные функции доступными только при статической отправке

fn foo<T>() where Self: Sized {

person Phoenix    schedule 14.08.2018
comment
Похоже, вы спрашиваете, почему необходимо : Sized ограничение.: Нет: Я не спрашиваю, почему требуется ограничение. - person Tim Diekmann; 15.08.2018
comment
Однако мне не было известно, что граница : Sized делает черту небезопасной для объекта. По сути, вы снова отвечаете на связанный вопрос. - person Tim Diekmann; 15.08.2018
comment
В этом вопросе спрашивается, почему нельзя вызвать baz из трейт-объекта. Вы не можете вызвать baz из объекта-признака, потому что baz является универсальной функцией, и вы не можете вызывать универсальные функции из объектов-признаков. - person Phoenix; 15.08.2018
comment
Итак, как можно решить эту проблему, чтобы эта черта Bar стала вызываемой? - person Benoît; 10.11.2019
comment
@ Benoît, это очень нетривиальный вопрос, но в этом конкретном примере вы удалили границу where Self: Sized, а затем обязательно удалили все дженерики из метода. Здесь общим является impl AsRef<str>, который в некотором роде является параметром типа. Здесь вы можете просто заменить его на &str. - person Phoenix; 10.11.2019

Граница делает метод небезопасным для объекта. Черты, которые не являются объектно-безопасными, не могут использоваться в качестве типов.

Методы, которые принимают Self в качестве аргумента, возвращают Self или иным образом требуют Self: Sized, небезопасны для объектов. Это потому, что методы объекта-признака вызываются через динамическую отправку, и размер реализации признака не может быть известен во время компиляции. - Питер Холл

Ссылаясь на официальные документы:

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

  • эта черта не требует Self: Sized
  • все его методы объектно-безопасны

Так что же делает метод объектно-безопасным? Каждый метод должен требовать Self: Sized или все из следующего:

  • не должно иметь никаких параметров типа
  • нельзя использовать Self

Смотрите также:

person Tim Diekmann    schedule 14.08.2018