Подпись типа Rust HashMap функции
Я создаю HashMap, который отображает строки на функции типа Vec<Expression> -> Expression
, где Expression
это тип, который я определил. Код в вопросе:
let functions: HashMap<_, _> = vec!(("+", Box::new(plus))).into_iter().collect();
Если я позволю Rust выводить для меня тип, как в приведенном выше коде, он компилируется и работает нормально, как в коде выше. Однако, если я пытаюсь указать тип, он не компилируется:
let functions: HashMap<&str, Box<Fn(Vec<Expression>) -> Expression>> =
vec!(("+", Box::new(plus))).into_iter().collect();
Сообщение об ошибке компилятора не очень полезно:
let functions: HashMap<&str, Box<Fn(Vec<Expression>) -> Expression>> = vec!(("+", Box::new(plus))).into_iter().collect();
^^^^^^^ a collection of type `std::collections::HashMap<&str, std::boxed::Box<std::ops::Fn(std::vec::Vec<Expression>) -> Expression>>` cannot be built from an iterator over elements of type `(&str, std::boxed::Box<fn(std::vec::Vec<Expression>) -> Expression {plus}>)`
Каков фактический тип этого HashMap
?
2 ответа
Если вы внимательно посмотрите на разницу, у вас будет ответ, хотя он может вызывать недоумение.
Я ожидаю что plus
был объявлен как:
fn plus(v: Vec<Expression>) -> Expression;
В этом случае тип plus
является fn(Vec<Expression>) -> Expression {plus}
, и на самом деле это тип Волдеморта: он не может быть назван.
В частности, это отличается от возможного fn(Vec<Expression>) -> Expression {multiply}
,
Эти два типа могут быть приведены в голое fn(Vec<Expression>) -> Expression
(без {plus}
/{multiply}
номинал).
И этот последний тип может быть преобразован в Fn(Vec<Expression>) -> Expression
что является признаком для любого вызываемого объекта, который не изменяет свою среду (например, закрытие |v: Vec<Expression>| v[0].clone()
).
Проблема, однако, заключается в том, что в то время как fn(a) -> b {plus}
может быть преобразован в fn(a) -> b
которые могут быть преобразованы в Fn(a) -> b
... преобразование требует изменения представления памяти. Это потому что:
fn(a) -> b {plus}
тип нулевого размера,fn(a) -> b
указатель на функцию,Box<Fn(a) -> b>
является объектом в штучной упаковке, который обычно означает как виртуальный указатель, так и указатель данных.
И поэтому типовая надпись не работает, потому что она может выполнять только бесплатные приведения.
Решение состоит в том, чтобы выполнить преобразование, пока не стало слишком поздно:
// Not strictly necessary, but it does make code shorter.
type FnExpr = Box<Fn(Vec<Expression>) -> Expression>;
let functions: HashMap<_, _> =
vec!(("+", Box::new(plus) as FnExpr)).into_iter().collect();
^~~~~~~~~~~~~~~~~~~~~~~~
Или, может быть, вы предпочли бы сохранить неупакованные функции:
// Simple functions only
type FnExpr = fn(Vec<Expression>) -> Expression;
let functions: HashMap<_, _> =
vec!(("+", plus as FnExpr)).into_iter().collect();
Соответствующие части сообщения об ошибке Box<std::ops::Fn ... >
а также Box<fn ... {plus}>
, Первый в штучной упаковке Fn
черта объекта. Вторая функция в штучной упаковке plus
, Обратите внимание, что это не прямоугольный указатель на функцию, которая Box<fn ...>
без {plus}
часть. Это уникальный и неименованный тип функции plus
сам.
То есть вы не можете написать реальный тип этого HashMap
, поскольку тип, который это содержит, является неименованным. Это не имеет большого значения, вы можете только поставить plus
функция в это.
Следующий код дает ошибку компиляции
let functions: HashMap<_, _> =
vec![("+", Box::new(plus)),
("-", Box::new(minus))].into_iter().collect();
^^^^^ expected fn item, found a different fn item
Это работает, но это бесполезно
let functions: HashMap<_, _> =
vec![("+", Box::new(plus)),
("-", Box::new(plus))].into_iter().collect();
Одним из возможных решений является преобразование первого элемента вектора в требуемый тип.
type BoxedFn = Box<Fn(Vec<Expression>) -> Expression>;
let functions: HashMap<&str, BoxedFn> =
vec![("+", Box::new(plus) as BoxedFn),
("_", Box::new(minus))].into_iter().collect();
Другим типом является определение типа промежуточной переменной.
type BoxedFn = Box<Fn(Vec<Expression>) -> Expression>;
let v: Vec<(_, BoxedFn)> = vec![("+", Box::new(plus)), ("_", Box::new(minus))];
let functions: HashMap<&str, BoxedFn> = v.into_iter().collect();