Подпись типа 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();
Другие вопросы по тегам