[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Rust



On 04/06/2015 12:23, Gian Uberto Lauri wrote:
[snip]
Se ci fossero (non ho ancora controllato) anche funzioni di ordine
superiore il vantaggio sarebbe ulteriormente incrementato, che queste
cose richiedono non poco lavoro in C.

Ci sono, ci sono anche se la forma con cui scrivi una funzione e quella con cui scrivi una lambda/closure sono differenti. MOLTO differenti. Tanto per fare un esempio, una funzione che costruisce un addizionatore e che possiamo usare così:

fn main()
{
        let add3 = make_adder(3);
	let four = add3(1);
        println!("Se aggiungo 3 a 1 ottengo {}!", four);
}

In un linguaggio puramente funzionale, con un runtime che alloca automaticamente la closure sullo heap ti aspetti di poter scrivere qualcosa come segue:

fn make_adder(n: i32) -> (Fn(i32) -> i32)
{
	|x| { x + n }
}


Questo codice, per quanto sintatticamente valido, non compila. Senza scendere troppo nei dettagli ci sono due problemi:

1) Il compilatore deve sapere la dimensione dei tipi restituiti da una funzione e il tipo "Fn(i32) -> i32" non ha una dimensione fissa. Fn() è un trait, un'interfaccia, la dimensione dipende dall'implementazione di quella che alla fine è una "struct" contenente le variabili su cui "chiudiamo" (in questo caso "n"). Il compilatore _potrebbe_ essere sufficientemente intelligente da determinare lui implementazione esatta e dimensioni ma... in questo caso non lo è.

2) La variabile "n" che viene passata alla funzione che crea la closure vive sullo stack del chiamante (il valore "3" in main nel mio esempio) e andrebbe a morire già alla seconda riga, in quanto non più referenziata. Quindi il compilatore si rifiuta di compilare codice che potrebbe utilizzare valori in memoria già liberata.

Per risolvere (1) ci viene in aiuto il tipo generico Box, che alloca memoria sullo heap, e la nostra funzione diventa:

fn make_adder(n: i32) -> Box<Fn(i32) -> i32>
{
	Box::new(|x| { x + n })
}

Ora make_adder restituisce quello che fondamentalmente è un puntatore, quindi (1) è OK. Per risolvere (2) diciamo al compilatore che la closure ha una semantica "move", ovvero che diventa proprietaria delle variabili su cui chiude. In questo caso non è un problema, perché "3" non verrà mai più utilizzato. La funzione, che finalmente compila, diventa:

fn make_adder(n: i32) -> Box<Fn(i32) -> i32>
{
	Box::new(move |x| { x + n })
}

Fortunatamente Box è abbastanza intelligente da comportarsi in maniera trasparente rispetto ai suoi "contenuti", quindi main non richiede alcuna modifica.

Dati linguaggi come LISP, Haskell o anche solo F# questa cosa è parecchio macchinosa (blargh blargh blargh, orribile!).

Del resto Rust si pone ad un livello più basso di questi linguaggi e cerca di fare il più possibile senza aggiungere sintassi ad-hoc, ma costruendo librerie di utilità con un compilatore che implementa poche regole.

Inoltre, siamo solo alla 1.0: non è detto che la 1.x non avrà un migliore supporto per le HOF.

Hope this helps,

federico

--
Federico Di Gregorio                         federico.digregorio@dndg.it
Di Nunzio & Di Gregorio srl                               http://dndg.it
 Io non sono romantica. La candelina sul tavolo mi vede e si spegne.
                                                      -- sisterconfusion


Reply to: