Границы признаков замыкания в переменных типа по сравнению с границами признаков замыкания в аргументах функций высшего порядка

Почему эти 2 работы:

fn apply_once1<F: FnOnce(T1) -> T2, T1, T2> (f: F, x: T1) -> T2 {
    f(x)
}

fn apply_once2<F, T1, T2> (f: F, x: T1) -> T2 
    where F: FnOnce(T1) -> T2 
{
    f(x)
}

Но этот не компилируется:

fn apply_once3<T1, T2> (f: FnOnce(T1) -> T2, x: T1) -> T2 {
    f(x)
}

Жалуется на:

error: the trait `core::marker::Sized` is not implemented for the type `core::ops::FnOnce(T1) -> T2 + 'static` [E0277]
    once3<T1, T2> (f: FnOnce(T1) -> T2, x: T1) -> T2 {
                   ^
help: see the detailed explanation for E0277
note: `core::ops::FnOnce(T1) -> T2 + 'static` does not have a constant size known at compile-time
note: all local variables must have a statically known size

Я это понимаю FnOnce может не иметь статически известного размера, поэтому обычно я исправляю это & поменяйте местами переменную для ссылки, так что размер теперь известен. Но я не понимаю почему apply_once1 а также apply_once2 может сойти с рук?

Осматривая вокруг, я не могу найти ничего, что говорило бы о разнице между наложением черты на аргумент и на переменные типа.

2 ответа

Решение

Погугливая, я не могу найти ничего, что говорило бы о разнице между наложением черты на аргумент и на тип переменных.

На самом деле это не то, что вы делаете в третьем. Давайте работать с чем-то более простым:

fn do_something<C: Clone>(x: C); fn do_something<C>(x: C) where C: Clone;

fn do_something(x: Clone)

Первые два на самом деле одно и то же, where это просто синтаксический сахар, чтобы сделать функции с нетривиальными границами признаков более удобными для чтения, но оба говорят: "Я пишу функцию, которая позже будет специализирована для типа, реализующего свойство Clone".

Последний говорит

"Я хочу х, что буквально является чертой клона".

Может быть, это сбивает с толку, позвольте мне уточнить, что-либо с угловыми скобками может рассматриваться как схема для функции, это говорит о том, что для данного типа, удовлетворяющего определенным требованиям, компилятор может сгенерировать функцию со следующим кодом. Это мешает нам писать:

fn do_something(x: f64); fn do_something(x: Vec<usize>);

И так далее. Обратите внимание на отсутствие угловых скобок. Если угловых скобок нет, вы пишете не схему, а тот тип компилятора, который вам нужен. Сейчас, Clone это тип... но это черта. Черты не похожи на структуры. Фактически, вы запрашивали объект- черту, который может быть передан только по ссылке, поскольку он не имеет размера.

Вы на самом деле не спрашивали об объекте, который реализует FnOnce, вы на самом деле спрашивали о том, что фактически является "платонической формой" FnOnce, которую вы не можете обойти, поскольку это скорее абстрактное понятие, чем реальная вещь. Вы можете пройти в &FnOnce, который говорит "указатель на любую случайную, произвольную вещь, которая оказывается FnOnce", но у этого есть некоторые компромиссы (я рекомендую прочитать ссылку об объектах признаков или найти другие ответы SO на них, которые покрывают их более подробно чем здесь уместно).

Итак, короткая версия: что-либо в сигнатуре функции является конкретным типом, а что-либо в угловых скобках (и / или предложение where) является ограничением для одного из этих конкретных типов.

Когда вы используете apply_once1переменная типа F создается с конкретным типом функции известного размера.

В apply_once3, FnOnce(T1) -> T2 тип объекта черты, который не имеет известного размера, потому что один экземпляр apply_once3 можно вызывать с разными функциями.

Смотрите этот раздел книги Rust.

Другие вопросы по тегам