2017-01-11 9 views
1

Рассмотрим следующий простой-структуру реализации From для этой структуры, и функцию, которая принимает экземпляры структуры, содержащие штучной функции: «Привет, мир»Почему мой родовой от реализации не принимает значения <Fn()>?

struct Foo<T>(T); 

impl<T> From<T> for Foo<T> { 
    fn from(x: T) -> Self { 
     Foo(x) 
    } 
} 

fn call(x: Foo<Box<Fn()>>) { 
    let Foo(f) = x; 
    f() 
} 

Печатается:

call(Foo(Box::new(|| println!("Hello, world!")))) 

Это не может скомпилировать:

call(Box::new(|| println!("Hello, world!")).into()) 

следующее сообщение об ошибке дано:

error[E0277]: the trait bound `Foo<Box<std::ops::Fn()>>: std::convert::From<Box<[[email protected]/main.rs:15:19: 15:47]>>` is not satisfied 
    --> src/main.rs:15:49 
    | 
15 |  call(Box::new(|| println!("Hello, world!")).into()) 
    |             ^^^^ the trait `std::convert::From<Box<[[email protected]/main.rs:15:19: 15:47]>>` is not implemented for `Foo<Box<std::ops::Fn()>>` 
    | 
    = help: the following implementations were found: 
    = help: <Foo<T> as std::convert::From<T>> 
    = note: required because of the requirements on the impl of `std::convert::Into<Foo<Box<std::ops::Fn()>>>` for `Box<[[email protected]/main.rs:15:19: 15:47]>` 

Я не вижу, каким образом моя From реализация является любой строже Foo конструктора. Почему into не удается найти здесь Foo?

ответ

4

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

From<Box<[[email protected]/main.rs:15:19: 15:47]>> 

Обратите внимание, что в этой ошибке упоминается замыкание. В Rust каждое закрытие является уникальным, неприемлемым типом (иногда называемым Voldemort type). Закрытие не a Fn, но оно орудиеFn.

Для преобразования From начального типа должно быть Box<Fn()>. Мы можем видеть, что явное приведение к Box<Fn()> позволяет компилировать:

call((Box::new(|| println!("Hello, world!")) as Box<Fn()>).into()); 

почему не as Box<Fn()> нужен в моем первом примере с помощью функции Foo?

Опять же, я подозреваю, , что это работает, потому что есть только одна конверсия происходит. Компилятор знает, как конвертировать Box<closure> в Box<Fn()> - он просто создает boxed trait object.

Я смотрю на него как на маленьком графике. Есть два края:

  1. От Box<closure> до Box<Fn()>. Это обеспечивается компилятором/ключевым словом as.
  2. От T до Foo<T>. Это обеспечивается реализацией From.

Вы можете выполнить первый шаг (или сделать это неявно), или вы можете выполнить второй шаг через .into(). Тем не менее, нет никакого шага, который идет с самого начала и до самого конца.

Возможно, было бы плохой идеей для компилятора попытаться пересечь граф преобразования, чтобы найти путь преобразования, который имеет произвольное количество шагов. Там также есть возможность для многих путей, приводящих к двусмысленности.

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

fn call<F>(f: F) 
    where F: Fn(), 
{ 
    let x: Foo<_> = Box::new(f).into(); 
    // I'm assuming something interesting happens 
    // here before we unpack the variable again 
    let Foo(f) = x; 
    f() 
} 

fn main() { 
    call(|| println!("Hello, world!")); 
} 

Chris Emerson points out, что вы также можете принять общий тип:

fn callg<T:Fn()>(x: Foo<Box<T>>) { 
    let Foo(f) = x; 
    f() 
} 

fn main() { 
    callg(Box::new(|| println!("Hello, world!")).into()); 
} 

Это работает, потому что мы больше не преобразования в Box<Fn()>, и поэтому требуется только один шаг преобразования.

+0

OK, это имеет смысл, спасибо. Но тогда почему в моем первом примере не используется 'as Box ', используя функцию 'Foo'? –

+1

Версия 'in' также работает, если вы делаете' call' generic над любым 'T: Fn()': https://play.rust-lang.org/?gist=85ba2009dac88693b05caa2f5373e975&version=stable&backtrace=0 –

+0

@ChrisEmerson a хорошо пункт, но это * - * другой. В этом случае у вас больше нет «Box ». – Shepmaster