Ошибка « размер для значений типа (dyn std::ops::Fn() -> S + 'static)
не может быть известна во время компиляции » вызвана вашей чертой, привязанной к F
:
F: Sized + FnMut(Fn() -> S) -> ()
Этоэквивалентно F: Sized + FnMut(dyn Fn() -> S)
.Это означает, что замыкание F
будет принимать объект признака (dyn Fn() -> S
) по значению.Но объекты черт не имеют размера и не могут быть переданы по значению (пока).
Одним из решений было бы передать объект признака по ссылке или в Box
. Ответ Родриго объясняет и обсуждает эти решения.
Можем ли мы избежать объектов-черт и динамической диспетчеризации?
Думаю, не совсем.
Не решения
Одной из идей было бы добавить другой параметр типа к count_calls
:
fn count_calls<S, F, G>(s: S, f: F) -> u32
where
S: Clone,
F: Sized + FnMut(G),
G: Fn() -> S,
Однако это не работает:
error[E0308]: mismatched types
--> src/lib.rs:9:7
|
9 | f(|| {
| _______^
10 | | counter += 1;
11 | | s.clone()
12 | | });
| |_____^ expected type parameter, found closure
|
= note: expected type `G`
found type `[closure@src/lib.rs:9:7: 12:6 counter:_, s:_]`
Проблема здесь в том, что аргументы типа count_calls
выбираются вызывающей стороной count_calls
.Но мы на самом деле хотим, чтобы G
всегда был типом нашего собственного замыкания.Так что это не работает.
Нам нужно общее закрытие (где мы можем выбрать его параметры типа).Нечто подобное возможно, но ограничено параметрами времени жизни.Он называется HRTB и выглядит как F: for<'a> Fn(&'a u32)
.Но это здесь не помогает, потому что нам нужен параметр типа, а for<T>
не существует (пока?).
Неоптимальное, ночное решение
Одним из решений было бы неиспользуйте замыкание, но тип с известным именем, который реализует FnMut
.К сожалению, вы не можете реализовать черты Fn*
для своего собственного типа в стабильной версии. По ночам это работает .
struct CallCounter<S> {
counter: u32,
s: S,
}
impl<S: Clone> FnOnce<()> for CallCounter<S> {
type Output = S;
extern "rust-call" fn call_once(self, _: ()) -> Self::Output {
// No point in incrementing the counter here
self.s
}
}
impl<S: Clone> FnMut<()> for CallCounter<S> {
extern "rust-call" fn call_mut(&mut self, _: ()) -> Self::Output {
self.counter += 1;
self.s.clone()
}
}
fn count_calls<S, F>(s: S, mut f: F) -> u32
where
S: Clone,
F: Sized + FnMut(&mut CallCounter<S>), // <----
{
let mut counter = CallCounter {
counter: 0,
s,
};
f(&mut counter); // <-------
counter.counter
}
К сожалению, теперь у вас есть этот странный тип в вашем публичном интерфейсе (который должен быть деталью реализации).
Кроме того, я не могу придумать ни одного реального решения (только другие супер-подробные решения с множеством недостатков).События в углу системы типов (в частности, в отношении GAT и HKT) могут решить эту проблему должным образом в будущем.Тем не менее, я думаю, что по-прежнему не хватает нескольких различных функций;в частности, я не думаю, что предложенные ГАТ уже решат эту проблему.
Так что, если это реальная проблема, которую нужно решить прямо сейчас, я бы:
- отступил бы и переосмыслил проблему в большем объеме, чтобы, возможно, избежать этого ограничения Rustили
- просто используйте динамическую диспетчеризацию.