2016-06-03 18 views
6

Mi piacerebbe poter passare una funzione generica ad un'altra funzione (in questo caso una chiusura), senza perdere la "genericità" della funzione passata. Dal momento che è una dichiarazione piuttosto contorto, ecco un esempio:Pass Funzione generica come argomento

use std::fmt::Debug; 

fn test<F, I: Debug>(gen: F) where F: Fn(fn(I) -> I) -> I { 
    fn input<I: Debug>(x: I) -> I { 
     x 
    } 

    println!("{:?}", gen(input)); 
} 

fn main() { 
    test(|input| { 
     input(10); 
     input(10.0) 
    }); 
} 

Questo non compilerà, perché il valore della input è di tipo inferenced e non più generico.

errore completa:

<anon>:14:15: 14:19 error: mismatched types: 
expected `_`, 
    found `_` 
(expected integral variable, 
    found floating-point variable) [E0308] 
<anon>:14   input(10.0) 
         ^~~~ 

E 'possibile una cosa del genere a Rust?

edit:

Sulla base delle soluzioni di data, ho usato quanto segue per risolvere un problema simile:

#![feature(unboxed_closures)] 
#![feature(fn_traits)] 

use std::ops::Fn; 
use std::ops::Add; 
use std::ops::FnMut; 

use std::fmt::Debug; 

struct Builder; 

impl Builder { 
    pub fn build<A: Add<B>, B: Add<A>>(&self) -> fn(A, B) -> <A as std::ops::Add<B>>::Output { 
     fn c<A: Add<B>, B: Add<A>>(a: A, b: B) -> <A as std::ops::Add<B>>::Output { 
      a + b 
     } 

     return c; 
    } 
} 

impl<A: Add<B>, B: Add<A>> Fn<(A, B)> for Builder { 
    extern "rust-call" fn call(&self, args: (A, B)) -> <A as std::ops::Add<B>>::Output { 
     let (a1, a2) = args; 
     self.build()(a1, a2) 
    } 
} 

impl<A: Add<B>, B: Add<A>> FnMut<(A, B)> for Builder { 
    extern "rust-call" fn call_mut(&mut self, args: (A, B)) -> <A as std::ops::Add<B>>::Output { 
     let (a1, a2) = args; 
     self.build()(a1, a2) 
    } 
} 

impl<A: Add<B>, B: Add<A>> FnOnce<(A, B)> for Builder { 
    type Output = <A as std::ops::Add<B>>::Output; 
    extern "rust-call" fn call_once(self, args: (A, B)) -> <A as std::ops::Add<B>>::Output { 
     let (a1, a2) = args; 
     self.build()(a1, a2) 
    } 
} 

fn test<F, I: Debug>(gen: F) where F: Fn(Builder) -> I { 
    let b = Builder; 
    println!("{:?}", gen(b)); 
} 

fn main() { 
    test(|builder| { 
     builder(10, 10); 
     builder(10.1, 10.0) 
    }); 
} 
+0

link parco giochi di ruggine: https://play.rust-lang.org/?code = uso% 20std% 3A% 3Afmt% 3A% 3ADebug% 3B% 0A% 0Afn% 20Test% 3CF% 2C% 20I% 3A% 20Debug% 3E (gen% 3A% 20F)% 20where% 20F% 3A% 20Fn (fn (I)% 20-% 3E% 20I)% 20-% 3E% 20I% 20% 7B% 0A% 20% 20% 20% 20fn% 20input% 3CI% 3A% 20Debug% 3E (x% 3A% 20I)% 20-% 3E% 20I% 20% 7B% 0A% 20% 20% 20% 20% 20% 20% 20% 20x% 0A% 20% 20% 20% 20% 7D% 0A% 20% 20% 20% 20 % 0A% 20% 20% 20% 20println! (% 22% 7B% 3A% 3F% 7D% 22% 2C% 20gen (input))% 3B% 0A% 7D% 0A% 0Afn% 20main()% 20% 7B % 0A% 20% 20% 20% 20Test (% 7Cinput% 7C% 20% 7B% 0A% 20% 20% 20% 20% 20% 20% 20% 20input (10)% 3B% 0A% 20% 20% 20 % 20% 20% 20% 20% 20input (10,0)% 0A% 20% 20% 20% 20% 7D)% 3B% 0A% 7D & versione = stable & backtrace = 0 – dpzmick

+1

Non sono ... sicuro che funzionerebbe mai? Stai chiamando 'test' che sarà monomorfizzato per essere u32 in base all'inferenza della prima chiamata alla chiusura' gen'. Se ne vuoi uno separato, dovrai chiamare 'test' di nuovo .. separatamente. Sto "compilando" questo nella mia testa e non vedo come sarebbe possibile in questa o in altre lingue, perché l'inferenza si abbassa. Forse ho sbagliato comunque. –

risposta

7

Come è stato detto, sfortunatamente la chiamata è monomorfizzata nel sito di chiamata, quindi non è possibile passare una funzione generica, è possibile solo passare una versione monomorfizzata della funzione generica.

Che cosa si può passaggio, tuttavia, è una funzione costruttore:

use std::fmt::Debug; 

struct Builder; 

impl Builder { 
    fn build<I: Debug>(&self) -> fn(I) -> I { 
     fn input<I: Debug>(x: I) -> I { x } 
     input 
    } 
} 

fn test<F, T: Debug>(gen: F) 
    where F: Fn(Builder) -> T 
{ 
    let builder = Builder; 
    println!("{:?}", gen(builder)); 
} 

fn main() { 
    test(|builder| { 
     builder.build()(10); 
     builder.build()(10.0) 
    }); 
} 

Il Builder è in grado di generare le istanze di input su richiesta.

2

Molto interessante domanda! Sono abbastanza sicuro che sia non possibile.

I generici di ruggine funzionano con funzioni di monomorfizzazione. Ciò significa che il compilatore di Rust genererà il codice macchina della funzione per ogni tipo di calcestruzzo con cui la funzione viene invocata. All'interno di una chiamata di una funzione, i parametri generici sono fissi. Quindi, dal momento che si chiama test esattamente una volta in main, i parametri generici vengono corretti per quella chiamata.

Ciò implica che il tipo di chiusura è fisso e che anche il parametro input della chiusura ha un tipo concreto. Il compilatore deduce tutti i tipi per noi, ma se vogliamo cercare di annotare questi, notiamo subito che corriamo lo stesso problema come il compilatore:

test::<_, usize> // we can't ever spell out a closure type, therefore '_' 
    (|input: fn(usize) -> usize| // we can't have a generic closure right now 
{ 
    input(10); // works 
    input(10.0) // doesn't work 
}); 

Questo assomiglia molto a un caso d'uso per tipi più elevati e chiusure generiche. Entrambe queste funzionalità non sono ancora disponibili in Rust, AFAIK.

Tuttavia, è ancora possibile ottenere quello che vuoi utilizzando dispatch dinamico:

fn test<F, I: Debug>(gen: F) where F: Fn(fn(Box<Debug>) -> Box<Debug>) -> I { 
    fn input(x: Box<Debug>) -> Box<Debug> { 
     x 
    } 

    println!("{:?}", gen(input)); 
} 

fn main() { 
    test(|input| { 
     input(Box::new(10)); 
     input(Box::new(10.0)) 
    }); 
} 

Naturalmente, questo non è bello come la versione generica, ma almeno funziona. Inoltre: se non si ha effettivamente bisogno della proprietà in input, è possibile modificare Box<Debug> in &Debug.

+0

Questa è un'ottima risposta. Ho accettato l'altra risposta dal momento che la mia soluzione è più simile a quella della tua risposta, ma la spiegazione del problema è molto, molto buona. Grazie! – dpzmick