Границы признаков замыкания в переменных типа по сравнению с границами признаков замыкания в аргументах функций высшего порядка
Почему эти 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.