Archivi Categorie: Rust

Rust – ottimizzazioni, non tutte necessarie ma intriganti

323836
Nei mesi scorsi –inizialmente grazie a un suggerimento di Robitex, poi ci ho preso gusto– mi sono occupato di Rust. Promette bene, il suo sogno è che daa grande vorrebbe diventare il sostituto del C++ (e forse del C). Chissà 😳

Intanto cresce bene, i rustaceans rockzs 😀
Oggi ho scoperto un blogger e mantainer di librerie Rust che –OK, vedrete subito, ecco Kang Seonghoon (Kang è il cognome ma lui vuole così).
Seonghoon, noto anche come lifthrasiir è attivo su Twitter, principalmente in coreano (dove è conosciuto come senokay), e da altre parti. Ha un blog, Rustlog, in inglese 😀
Oggi qualcuno twitta il suo ultimo post: Why is a Rust executable large?
Meraviglioso! 😀
Post lungo ma solo perché lifthrasiir la sa lunga 😀
Vero –lo dice anche lui– che la dimensione in bytes dell’eseguibile è un argomento meno stringente di una volta, ma non sempre e poi in ogni caso prima di distribuire la versione finale almeno una parte di quanto dice conviene farla.

E poi Yurume (sì è conosciuto anche così) sa raccontare bene; leggerlo è un piacere.
Non credo che userò Rust come linguaggio, non faccio cose abbastanza impegnative da giustificarlo, ma se capitasse mi toccherebbe cercare nel blog (il mio OKp) per questo post. Sempre che nel frattempo i rustaceans non tengano conto dei suggerimenti di Seonghoon. Ma se del caso sono convinto che lui ci avviserebbe 😀

OT: ecco un suo retweet di un cinguettio che riguarda l’이태리 (se del caso fatevi aiutare da Google Traduttore).
:mrgreen:

Rust – considreazioni conclusive

CQKZQTHXAAAff-t

La lunga serie di post su Rust finisce qui. Ma con una raccomandazione, per quel che può valere la mia opinione: tenetelo d’occhio; crescendo diventerà qualcuno, una star.
Probabilmente, forse. Si sa che il futuro è nascosto tra le Pastose Appendici del Prodigioso FSM, sempre sia condito, RAmen.

Graydon Hoare

Un po’ di storia (vorrei, ne so poco) per iniziare. Tanto tempo fa, come ricorda la Wiki, Lambda the Ultimate diceva:

Rust is systems programming languages being developed by Mozilla. It’s very preliminary work, but the list of features suggests an interesting intersection of features. I’m particularly excited by the control over memory layout that Rust gives to the programmer, but it also has massive concurrency, structural typing, and the “ability to define complex invariants that hold over data structures” in its bag of tricks.

The main developer is Graydon Hoare, with contributions from heroes-to-the-Internet Dave Herman and Brendan Eich amongst others.

OOPS! 😳 ho copiato tutto il post 😳 Ma ci sono i commenti, tanti, variegati. Tutti del 2010, un’altra era geologica.
Poi Rust è cresciuto (e continua tuttora a crescere), copio dalla Wiki:

In January 2014, the editor-in-chief of Dr Dobb’s, Andrew Binstock, commented on Rust’s chances to become a competitor to C++, as well as to the other upcoming languages D, Go and Nim (then Nimrod): according to Binstock, while Rust was “widely viewed as a remarkably elegant language”, adoption of it stayed behind because the language kept changing between versions.[32] The first “stable” version of the Rust, version 1.0.0, was released in May 2015.

Rust was the third most loved programming language in the 2015 Stack Overflow annual survey, and jumped to the first place in 2016 Stack Overflow annual survey.

C’è un intervista a Graydon Hoare, il creatore di Rust da leggere, qui.
Graydon continua a interessarsi al programma, anche se non è più il responsabile; si è perso anche il suo sito ma è attivo su Twitter, dove i follows e followers sono i soliti che sono abituato a trovare (il mondo è piccolo).E su Twitter posta anche cose così 😀

OK, basta storia, il programma adesso.
Rust mi piace. Come piace (e viene usato da) hacker che seguo ontehtoobz, già ricordati più volte, niente elenco che poi dimentico qualcuno, sapete la gestione della mia memoria non è paragonabile a quella di Rust. (Solo un link, che mi sembra utile, qui).
Poi c’è l’ambiente, Cargo, p.es., che diventa insostituibile appena il progetto assume una dimensione sensata.
C’è una documentazione on- e offline molto completa, esaustiva.

Il problema piuttosto è un altro, umano.
Rust è un programma di sistema, se la richiesta è piccola ci sono altri strumenti, per esempio Python. Ma, più grave, il tempo è prezioso e il periodo di apprendimento è sempre troppo lungo. Poi quando ci si ambienta si fanno cose che ti fanno sentire meno niubbo, p.es. ecco cose come questa:

with open('{0}{1}'.format(pdir, "/orc-ma"), "w") as f:
	f.write('{0}{1}{2}'.format( ...

Basta, finisco di installare ‘buntu; Rust, solo se dovesse servire.

:mrgreen:

Rust – il linguaggio – 53

k3v_u8NP

Proseguendo dovrei scrivere del capitolo Nightly Rust, qui: /usr/local/share/doc/rust/html/book/nightly-rust.html ma per me sono cose troppo specifiche. Lo stesso vale per tutti i post del capitolo; Tutto per über-nerd rustici (rustaceans). Che un po’ invidio ma non sono dei loro.

Restano altre tre sezioni:

  • Glossary [/usr/local/share/doc/rust/html/book/glossary.html]: Not every Rustacean has a background in systems programming, nor in computer science, so we’ve added explanations of terms that might be unfamiliar;
  • Syntax Index [/usr/local/share/doc/rust/html/book/syntax-index.html], elenco di tutte le keywords, operatori e simboli e altre sintassi, fondamentale per chi ha la memoria come la mia, dimentica i nomi e fa confusione tra i linguaggi (e le parentesi);
  • Bibliography [/usr/local/share/doc/rust/html/book/bibliography.html]: This is a reading list of material relevant to Rust. It includes prior research that has – at one time or another – influenced the design of Rust, as well as publications about Rust. Molto ricca, da perdercisi :grin.

Sono molto grato a Robitex che mi ha fatto conoscere Rust, anche se poi non ho raccolto il suo invito originale (manca il tempo, non sono così bravo come crede). Resta da fare sull’argomento un ulteriore post, le mie conclusioni. Niente di che ma tirare le somme 😀

:mrgreen:

Rust – il linguaggio – 52

sg3
Continuo, oggi qui: /usr/local/share/doc/rust/html/book/borrow-and-asref.html.
Solo qualche appunto; in generale sono cose già viste e queste specifiche servono solo se davvero si userà Rust.

Borrow e AsRef

The Borrow and AsRef traits are very similar, but different.
The Borrow trait is used when you’re writing a datastructure, and you want to use either an owned or borrowed type as synonymous for some purpose.

The AsRef trait is a conversion trait. It’s used for converting some value to a reference in generic code.

OK, passo al capitolo successivo, a proposito dei canali qui: /usr/local/share/doc/rust/html/book/release-channels.html.

Release Channels

The Rust project uses a concept called ‘release channels’ to manage releases. It’s important to understand this process to choose which version of Rust your project should use.

There are three channels for Rust releases:

  • Nightly
  • Beta
  • Stable

New nightly releases are created once a day. Every six weeks, the latest nightly release is promoted to ‘Beta’. At that point, it will only receive patches to fix serious errors. Six weeks later, the beta is promoted to ‘Stable’, and becomes the next release of 1.x.

This process happens in parallel. So every six weeks, on the same day, nightly goes to beta, beta goes to stable. When 1.x is released, at the same time, 1.(x + 1)-beta is released, and the nightly becomes the first version of 1.(x + 2)-nightly.

Generally speaking, unless you have a specific reason, you should be using the stable release channel. These releases are intended for a general audience.

Poi, ovviamente, ci sono casi che … –da leggere tutto di là 😀

Come pure il capitolo successivo, qui: /usr/local/share/doc/rust/html/book/using-rust-without-the-standard-library.html.

Usare Rust senza la Standard Library

Rust’s standard library provides a lot of useful functionality, but assumes support for various features of its host system: threads, networking, heap allocation, and others. There are systems that do not have these features, however, and Rust can work with those too! To do so, we tell Rust that we don’t want to use the standard library via an attribute: #![no_std].

Ahemmm… ovviamente (etc.) 😉

:mrgreen:

Rust – il linguaggio – 51

Sergio Gridelli

Sergio Gridelli

Oggi qui: /usr/local/share/doc/rust/html/book/choosing-your-guarantees.html, continuando la mia rassegna da qui.

Scegliere le condizioni (garanzie)

One important feature of Rust is that it lets us control the costs and guarantees of a program.

There are various “wrapper type” abstractions in the Rust standard library which embody a multitude of tradeoffs between cost, ergonomics, and guarantees. Many let one choose between run time and compile time enforcement. This section will explain a few selected abstractions in detail.

Before proceeding, it is highly recommended that one reads about ownership and borrowing in Rust.

Vengono di seguito esaminati i vati tipi di puntatori, già incontrati nella lunga serie di post precedenti. Riporto solo l’elenco: Box<T>, &T, &mut T, *const T, *mut T e Rc<T>.

Si passa poi a esaminare tipi più specifici come Cell<T>, RefCell<T> e i tipi sincroni Arc<T>, Mutex<T>, RwLock<T> e le loro composizioni.

Se si usa Rust questa pagina del manuale è fondamentale; per me basta sapere che c’è, se dovesse servirmi in futuro 😉

Si passa quindi a FFI, qui: /usr/local/share/doc/rust/html/book/ffi.html.

Foreign Function Interface

Uhmmm… oltre che molto specifico l’argomento è già stato trattato in modo comprensibile anche agli umani come me, copiando TheKeng3r, qui e qui.

:mrgreen:

Rust – il linguaggio – 50

FoLug

Proseguo, oggi qui: /usr/local/share/doc/rust/html/book/error-handling.html.

Gestione degli errori

Panico (mio, non panic!) perché: This chapter is very long, mostly because we start at the very beginning with sum types and combinators, and try to motivate the way Rust does error handling incrementally. As such, programmers with experience in other expressive type systems may want to jump around.

Segue un lungo indice molto dettagliato (la documentazione di Rust è davvero ottima) che non copio; c’è tutto là 😀
C’è anche, per finire, un esempio pratico (case study), molto interessante, da fare se si decide che Rust è da usarsi.

E alla fine il riassunto del capitolo 😀

Riassunto

Since this chapter is long, it is useful to have a quick summary for error handling in Rust. These are some good “rules of thumb.” They are emphatically not commandments. There are probably good reasons to break every one of these heuristics!

  • If you’re writing short example code that would be overburdened by error handling, it’s probably just fine to use unwrap (whether that’s Result::unwrap [/usr/local/share/doc/rust/html/std/result/enum.Result.html#method.unwrap], Option::unwrap [/usr/local/share/doc/rust/html/std/option/enum.Option.html#method.unwrap] or preferably Option::expect [/usr/local/share/doc/rust/html/std/option/enum.Option.html#method.expect]). Consumers of your code should know to use proper error handling. (If they don’t, send them here!)
  • If you’re writing a quick ‘n’ dirty program, don’t feel ashamed if you use unwrap. Be warned: if it winds up in someone else’s hands, don’t be surprised if they are agitated by poor error messages!
  • If you’re writing a quick ‘n’ dirty program and feel ashamed about panicking anyway, then use either a String or a Box<Error + Send + Sync> for your error type (the Box<Error + Send + Sync> type is because of the available From impls [/usr/local/share/doc/rust/html/std/convert/trait.From.html]).
  • Otherwise, in a program, define your own error types with appropriate From and Error [/usr/local/share/doc/rust/html/std/error/trait.Error.html] impls to make the try! macro more ergonomic.
  • If you’re writing a library and your code can produce errors, define your own error type and implement the std::error::Error trait. Where appropriate, implement From to make both your library code and the caller’s code easier to write. (Because of Rust’s coherence rules, callers will not be able to impl From on your error type, so your library should do it.)
  • Learn the combinators defined on Option [/usr/local/share/doc/rust/html/std/option/enum.Option.html] and Result [/usr/local/share/doc/rust/html/std/result/enum.Result.html]. Using them exclusively can be a bit tiring at times, but I’ve personally found a healthy mix of try! and combinators to be quite appealing. and_then, map and unwrap_or are my favorites.

Rust è OK ma chissà se lo userò 😳
:mrgreen:

Rust – il linguaggio – 49

CUrS1_gWcAAlgRM

Da qui oggi sono qui: /usr/local/share/doc/rust/html/book/concurrency.html.

Concurrency

Concurrency and parallelism are incredibly important topics in computer science, and are also a hot topic in industry today. Computers are gaining more and more cores, yet many programmers aren’t prepared to fully utilize them.

Rust’s memory safety features also apply to its concurrency story too.

Before we talk about the concurrency features that come with Rust, it’s important to understand something: Rust is low-level enough that the vast majority of this is provided by the standard library, not by the language. This means that if you don’t like some aspect of the way Rust handles concurrency, you can implement an alternative way of doing things. mio is a real-world example of this principle in action.

Background: Send e Sync

Concurrency is difficult to reason about. In Rust, we have a strong, static type system to help us reason about our code. As such, Rust gives us two traits to help us make sense of code that can possibly be concurrent.

Send

The first trait we’re going to talk about is Send [/usr/local/share/doc/rust/html/std/marker/trait.Send.html]. When a type T implements Send, it indicates that something of this type is able to have ownership transferred safely between threads.

This is important to enforce certain restrictions. For example, if we have a channel connecting two threads, we would want to be able to send some data down the channel and to the other thread. Therefore, we’d ensure that Send was implemented for that type.

In the opposite way, if we were wrapping a library with FFI [/usr/local/share/doc/rust/html/book/ffi.html] that isn’t threadsafe, we wouldn’t want to implement Send, and so the compiler will help us enforce that it can’t leave the current thread.

Sync

The second of these traits is called Sync. When a type T implements Sync [/usr/local/share/doc/rust/html/std/marker/trait.Sync.html], it indicates that something of this type has no possibility of introducing memory unsafety when used from multiple threads concurrently through shared references. This implies that types which don’t have interior mutability [/usr/local/share/doc/rust/html/book/mutability.html] are inherently Sync, which includes simple primitive types (like u8) and aggregate types containing them.

For sharing references across threads, Rust provides a wrapper type called Arc<T>. Arc<T> implements Send and Sync if and only if T implements both Send and Sync. For example, an object of type Arc<RefCell<U>> cannot be transferred across threads because RefCell [/usr/local/share/doc/rust/html/book/choosing-your-guarantees.html#refcellt] does not implement Sync, consequently Arc<RefCell<U>> would not implement Send.

These two traits allow you to use the type system to make strong guarantees about the properties of your code under concurrency. Before we demonstrate why, we need to learn how to create a concurrent Rust program in the first place!

Threads

Rust’s standard library provides a library for threads, which allow you to run Rust code in parallel. Here’s a basic example of using std::thread:

fn main() {
    thread::spawn(|| {
        println!("Hello from a thread!");
    });
}

The thread::spawn() method accepts a closure, which is executed in a new thread. It returns a handle to the thread, that can be used to wait for the child thread to finish and extract its result:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        "Hello from a thread!"
    });

    println!("{}", handle.join().unwrap());
}

rs49-0

Many languages have the ability to execute threads, but it’s wildly unsafe. There are entire books about how to prevent errors that occur from shared mutable state. Rust helps out with its type system here as well, by preventing data races at compile time. Let’s talk about how you actually share things between threads.

Safe Shared Mutable State

Dai, mica si può tradurre 😉
Due to Rust’s type system, we have a concept that sounds like a lie: “safe shared mutable state.” Many programmers agree that shared mutable state is very, very bad.

Com’è che si dice?

Shared mutable state is the root of all evil. Most languages attempt to deal with this problem through the ‘mutable’ part, but Rust deals with it by solving the ‘shared’ part.

(Rust’s auto-cit.).

The same ownership system [/usr/local/share/doc/rust/html/book/ownership.html] that helps prevent using pointers incorrectly also helps rule out data races, one of the worst kinds of concurrency bugs.

As an example, here is a Rust program that would have a data race in many languages. It will not compile:

use std::thread;
use std::time::Duration;

fn main() {
    let mut data = vec![1, 2, 3];

    for i in 0..3 {
        thread::spawn(move || {
            data[i] += 1;
        });
    }

    thread::sleep(Duration::from_millis(50));
}

rs49-1

Rust knows this wouldn’t be safe! If we had a reference to data in each thread, and the thread takes ownership of the reference, we’d have three owners!

So, we need some type that lets us have more than one reference to a value and that we can share between threads, that is it must implement Sync.

We’ll use Arc<T>, Rust’s standard atomic reference count type, which wraps a value up with some extra runtime bookkeeping which allows us to share the ownership of the value between multiple references at the same time.

The bookkeeping consists of a count of how many of these references exist to the value, hence the reference count part of the name.

The Atomic part means Arc<T> can safely be accessed from multiple threads. To do this the compiler guarantees that mutations of the internal count use indivisible operations which can’t have data races.

use std::thread;
use std::sync::Arc;
use std::time::Duration;

fn main() {
    let mut data = Arc::new(vec![1, 2, 3]);

    for i in 0..3 {
        let data = data.clone();
        thread::spawn(move || {
            data[i] += 1;
        });
    }

    thread::sleep(Duration::from_millis(50));
}

rs49-2

We now call clone() on our Arc<T>, which increases the internal count. This handle is then moved into the new thread.

And… still gives us an error 😦

Arc<T> assumes one more property about its contents to ensure that it is safe to share across threads: it assumes its contents are Sync. This is true for our value if it’s immutable, but we want to be able to mutate it, so we need something else to persuade the borrow checker we know what we’re doing.

It looks like we need some type that allows us to safely mutate a shared value, for example a type that can ensure only one thread at a time is able to mutate the value inside it at any one time.

For that, we can use the Mutex<T> type!

Here’s the working version:

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3]));

    for i in 0..3 {
        let data = data.clone();
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            data[i] += 1;
        });
    }

    thread::sleep(Duration::from_millis(50));
}

rs49-3

Note that the value of i is bound (copied) to the closure and not shared among the threads.

Also note that lock method of Mutex has this signature:

fn lock(&self) -> LockResult<MutexGuard<T>>

and because Send is not implemented for MutexGuard<T>, the guard cannot cross thread boundaries, ensuring thread-locality of lock acquire and release.

Let’s examine the body of the thread more closely:

thread::spawn(move || {
    let mut data = data.lock().unwrap();
    data[i] += 1;
});

First, we call lock(), which acquires the mutex’s lock. Because this may fail, it returns an Result<T, E>, and because this is just an example, we unwrap() it to get a reference to the data. Real code would have more robust error handling here. We’re then free to mutate it, since we have the lock.

Lastly, while the threads are running, we wait on a short timer. But this is not ideal: we may have picked a reasonable amount of time to wait but it’s more likely we’ll either be waiting longer than necessary or not long enough, depending on just how much time the threads actually take to finish computing when the program runs.

A more precise alternative to the timer would be to use one of the mechanisms provided by the Rust standard library for synchronizing threads with each other. Let’s talk about one of them: channels.

Channels

Here’s a version of our code that uses channels for synchronization, rather than waiting for a specific time:

use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;

fn main() {
    let data = Arc::new(Mutex::new(0));

    let (tx, rx) = mpsc::channel();

    for _ in 0..10 {
        let (data, tx) = (data.clone(), tx.clone());

        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            *data += 1;

            tx.send(()).unwrap();
        });
    }

    for _ in 0..10 {
        rx.recv().unwrap();
    }
}

rs49-4

We use the mpsc::channel() method to construct a new channel. We just send a simple () down the channel, and then wait for ten of them to come back.

While this channel is just sending a generic signal, we can send any data that is Send over the channel!

use std::thread;
use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    for i in 0..10 {
        let tx = tx.clone();

        thread::spawn(move || {
            let answer = i * i;

            tx.send(answer).unwrap();
        });
    }

    for _ in 0..10 {
        println!("{}", rx.recv().unwrap());
    }
}

rs49-5

Here we create 10 threads, asking each to calculate the square of a number (i at the time of spawn()), and then send() back the answer over the channel.

Panics

A panic! will crash the currently executing thread. You can use Rust’s threads as a simple isolation mechanism:

fn main() {
    use std::thread;
    
    let handle = thread::spawn(move || {
        panic!("oops!");
    });
    
    let result = handle.join();
    
    println!("{}", result.is_err());
    
}

rs49-6

Thread.join() gives us a Result back, which allows us to check if the thread has panicked or not.

Bello 😀 uno dei motivi per usare Rust 😀
:mrgreen:

Rust – il linguaggio – 48

stella

Oggi qui: /usr/local/share/doc/rust/html/book/iterators.html, continuando da qui.

Iteratori

Let’s talk about loops.

Remember Rust’s for loop? Here’s an example:

fn main() {
    for x in 0..10 {
        print!("{} ", x);
    }
    println!("");
}

rs48-0

Now that you know more Rust, we can talk in detail about how this works. Ranges (the 0..10) are ‘iterators’. An iterator is something that we can call the .next() method on repeatedly, and it gives us a sequence of things.

Like this:

fn main() {
    let mut range = 0..10;
    
    loop {
        match range.next() {
            Some(x) => {
                print!("{} ", x);
            },
            None => { break }
        }
    }
     println!("");
}

rs48-1

We make a mutable binding to the range, which is our iterator. We then loop, with an inner match. This match is used on the result of range.next(), which gives us a reference to the next value of the iterator. next returns an Option<i32>, in this case, which will be Some(i32) when we have a value and None once we run out. If we get Some(i32), we print it out, and if we get None, we break out of the loop.

This code sample is basically the same as our for loop version. The for loop is just a handy way to write this loop/match/break construct.

for loops aren’t the only thing that uses iterators, however. Writing your own iterator involves implementing the Iterator trait. While doing that is outside of the scope of this guide, Rust provides a number of useful iterators to accomplish various tasks. But first, a few notes about limitations of ranges.

Ranges are very primitive, and we often can use better alternatives. Consider the following Rust anti-pattern: using ranges to emulate a C-style for loop. Let’s suppose you needed to iterate over the contents of a vector. You may be tempted to write this:

fn main() {
    let nums = vec![1, 2, 3];
    
    for i in 0..nums.len() {
        println!("{}", nums[i]);
    }
}

rs48-2

This is strictly worse than using an actual iterator. You can iterate over vectors directly, so write this:

fn main() {
    let nums = vec![1, 2, 3];
    
    for num in &nums {
        println!("{}", num);
    }
}

rs48-3

There are two reasons for this. First, this more directly expresses what we mean. We iterate through the entire vector, rather than iterating through indexes, and then indexing the vector. Second, this version is more efficient: the first version will have extra bounds checking because it used indexing, nums[i]. But since we yield a reference to each element of the vector in turn with the iterator, there’s no bounds checking in the second example. This is very common with iterators: we can ignore unnecessary bounds checks, but still know that we’re safe.

There’s another detail here that’s not 100% clear because of how println! works. num is actually of type &i32. That is, it’s a reference to an i32, not an i32 itself. println! handles the dereferencing for us, so we don’t see it. This code works fine too:

fn main() {
    let nums = vec![1, 2, 3];
    
    for num in &nums {
        println!("{}", *num);
    }
}

rs48-4

Now we’re explicitly dereferencing num. Why does &nums give us references? Firstly, because we explicitly asked it to with &. Secondly, if it gave us the data itself, we would have to be its owner, which would involve making a copy of the data and giving us the copy. With references, we’re just borrowing a reference to the data, and so it’s just passing a reference, without needing to do the move.

So, now that we’ve established that ranges are often not what you want, let’s talk about what you do want instead.

There are three broad classes of things that are relevant here: iterators, iterator adaptors, and consumers. Here’s some definitions:

  • iterators give you a sequence of values.
  • iterator adaptors operate on an iterator, producing a new iterator with a different output sequence.
  • consumers operate on an iterator, producing some final set of values.

Let’s talk about consumers first, since you’ve already seen an iterator, ranges.

Consumers

No, non ho idea di come trardurlo 😉
A consumer operates on an iterator, returning some kind of value or values. The most common consumer is collect(). This code doesn’t quite compile, but it shows the intention:

let one_to_one_hundred = (1..101).collect();

As you can see, we call collect() on our iterator. collect() takes as many values as the iterator will give it, and returns a collection of the results. So why won’t this compile? Rust can’t determine what type of things you want to collect, and so you need to let it know. Here’s the version that does compile:

fn main() {
    let one_to_one_hundred = (1..101).collect::<Vec>();
    println!("{} {} {}", one_to_one_hundred[0], 
                         one_to_one_hundred[41], 
                         one_to_one_hundred[99]);
}

rs48-5

If you remember, the ::<> syntax allows us to give a type hint, and so we tell it that we want a vector of integers. You don’t always need to use the whole type, though. Using a _ will let you provide a partial hint:

fn main() {
    let one_to_one_hundred = (1..101).collect::<Vec>();
    println!("{} {} {}", one_to_one_hundred[0], 
                         one_to_one_hundred[41], 
                         one_to_one_hundred[99]);
}

rs48-6

This says “Collect into a Vec<T>, please, but infer what the T is for me.” _ is sometimes called a “type placeholder” for this reason.

collect() is the most common consumer, but there are others too. find() is one:

fn main() {
    let greater_than_forty_two = (0..100)
                                 .find(|x| *x > 42);
    
    match greater_than_forty_two {
        Some(_) => println!("Found a match!"),
        None => println!("No match found :("),
    }
}

rs48-7

find takes a closure, and works on a reference to each element of an iterator. This closure returns true if the element is the element we’re looking for, and false otherwise. find returns the first element satisfying the specified predicate. Because we might not find a matching element, find returns an Option rather than the element itself.

Another important consumer is fold. Here’s what it looks like:

fn main() {
    let sum = (1..4).fold(0, |sum, x| sum + x);
    println!("{}", sum);
}

rs48-8

fold() is a consumer that looks like this: fold(base, |accumulator, element| ...). It takes two arguments: the first is an element called the base. The second is a closure that itself takes two arguments: the first is called the accumulator, and the second is an element. Upon each iteration, the closure is called, and the result is the value of the accumulator on the next iteration. On the first iteration, the base is the value of the accumulator.

Okay, that’s a bit confusing. Let’s examine the values of all of these things in this iterator:

tabella

We called fold() with these arguments:

.fold(0, |sum, x| sum + x);

So, 0 is our base, sum is our accumulator, and x is our element. On the first iteration, we set sum to 0, and x is the first element of nums, 1. We then add sum and x, which gives us 0 + 1 = 1. On the second iteration, that value becomes our accumulator, sum, and the element is the second element of the array, 2. 1 + 2 = 3, and so that becomes the value of the accumulator for the last iteration. On that iteration, x is the last element, 3, and 3 + 3 = 6, which is our final result for our sum. 1 + 2 + 3 = 6, and that’s the result we got.

Whew. fold can be a bit strange the first few times you see it, but once it clicks, you can use it all over the place. Any time you have a list of things, and you want a single result, fold is appropriate.

Consumers are important due to one additional property of iterators we haven’t talked about yet: laziness. Let’s talk some more about iterators, and you’ll see why consumers matter.

Iterators

As we’ve said before, an iterator is something that we can call the .next() method on repeatedly, and it gives us a sequence of things. Because you need to call the method, this means that iterators can be lazy and not generate all of the values upfront. This code, for example, does not actually generate the numbers 1-99, instead creating a value that merely represents the sequence:

let nums = 1..100;

Since we didn’t do anything with the range, it didn’t generate the sequence. Let’s add the consumer:

let nums = (1..100).collect::<Vec<i32>>();

Now, collect() will require that the range gives it some numbers, and so it will do the work of generating the sequence.

Ranges are one of two basic iterators that you’ll see. The other is iter(). iter() can turn a vector into a simple iterator that gives you each element in turn:

let nums = vec![1, 2, 3];

for num in nums.iter() {
   println!("{}", num);
}

(già fatto girare, vedi sopra, terzo screenshot).

These two basic iterators should serve you well. There are some more advanced iterators, including ones that are infinite.

That’s enough about iterators. Iterator adaptors are the last concept we need to talk about with regards to iterators. Let’s get to it!

Iterator adaptors

Iterator adaptors take an iterator and modify it somehow, producing a new iterator. The simplest one is called map:

(1..100).map(|x| x + 1);

map is called upon another iterator, and produces a new iterator where each element reference has the closure it’s been given as an argument called on it. So this would give us the numbers from 2-100. Well, almost! If you compile the example, you’ll get a warning:

fn main() {
    (1..100).map(|x| x + 1);
}

rs48-9

Laziness strikes again! That closure will never execute. This example doesn’t print any numbers:

fn main() {
    (1..100).map(|x| println!("{}", x));
}

rs48-10

If you are trying to execute a closure on an iterator for its side effects, just use for instead.

There are tons of interesting iterator adaptors. take(n) will return an iterator over the next n elements of the original iterator. Let’s try it out with an infinite iterator:

fn main() {
    for i in (1..).take(5) {
        print!("{} ", i);
    }
    println!("");
}

rs48-11

filter() is an adapter that takes a closure as an argument. This closure returns true or false. The new iterator filter() produces only the elements that the closure returns true for:

fn main() {
    for i in (1..100).filter(|&x| x % 2 == 0) {
        print!("{} ", i);
    }
    println!("");
}

rs48-12

This will print all of the even numbers between one and a hundred. (Note that because filter doesn’t consume the elements that are being iterated over, it is passed a reference to each element, and thus the filter predicate uses the &x pattern to extract the integer itself.)

You can chain all three things together: start with an iterator, adapt it a few times, and then consume the result. Check it out:

fn main() {
    for i in (1..)
        .filter(|&x| x % 2 == 0)
        .filter(|&x| x % 3 == 0)
        .take(5)
        .collect::<Vec>() {
        print!("{} ", i);}
    println!("");
    
}

rs48-13

This is just a small taste of what iterators, iterator adaptors, and consumers can help you with. There are a number of really useful iterators, and you can write your own as well. Iterators provide a safe, efficient way to manipulate all kinds of lists. They’re a little unusual at first, but if you play with them, you’ll get hooked. For a full list of the different iterators and consumers, check out the iterator module documentation [/usr/local/share/doc/rust/html/std/iter/index.html].

:mrgreen:

Rust – il linguaggio – 47

florence

Proseguendo da qui oggi eccomi qui: /usr/local/share/doc/rust/html/book/documentation.html.

Documentazione

Documentation is an important part of any software project, and it’s first-class in Rust. Let’s talk about the tooling Rust gives you to document your project. Vero 😀 io ultimamente considero solo le cose ben documentate (e aggiornate), meglio se senza bugs.

rustdoc

The Rust distribution includes a tool, rustdoc, that generates documentation. rustdoc is also used by Cargo through cargo doc.
Documentation can be generated in two ways: from source code, and from standalone Markdown files.

Documentare il codice sorgente

The primary way of documenting a Rust project is through annotating the source code. You can use documentation comments for this purpose:

/// Constructs a new `Rc`.
///
/// # Examples
///
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```
pub fn new(value: T) -> Rc {
    // implementation goes here
}

Produce un file .html come quelli che sto esaminando, anzi uno di quelli proprio 😀

The first thing to notice about this annotation is that it uses /// instead of //. The triple slash indicates a documentation comment.

Documentation comments are written in Markdown.

Rust keeps track of these comments, and uses them when generating documentation. This is important when documenting things like enums:

/// The `Option` type. See [the module level documentation](index.html) for more.
enum Option {
    /// No value
    None,
    /// Some value `T`
    Some(T),

The above works, but this does not:

/// The `Option` type. See [the module level documentation](index.html) for more.
enum Option {
    None, /// No value
    Some(T), /// Some value `T`
}

You’ll get an error:

hello.rs:4:1: 4:2 error: expected ident, found `}`
hello.rs:4 }
           ^

This unfortunate error is correct: documentation comments apply to the thing after them, and there’s nothing after that last comment.

Seguono una serie di consigli che non riporto, anche se impo, nèh! 😀
Come pure non riporto come produrre la documentazione con rustdoc o cargo test.

Documentare i moduli

Rust has another kind of doc comment, //!. This comment doesn’t document the next item, but the enclosing item. In other words:

mod foo {
    //! This is documentation for the `foo` module.
    //!
    //! # Examples

    // ...

This is where you’ll see //! used most often: for module documentation. If you have a module in foo.rs, you’ll often open its code and see this:

//! A module for using `foo`s.
//!
//! The `foo` module contains a lot of useful functionality blah blah blah

Check out RFC 505 for full conventions around the style and format of documentation.

Altre documentazioni

All of this behavior works in non-Rust source files too. Because comments are written in Markdown, they’re often .md files.

When you write documentation in Markdown files, you don’t need to prefix the documentation with comments. For example:

/// # Examples
///
/// ```
/// use std::rc::Rc;
///
/// let five = Rc::new(5);
/// ```

Seguono poi tutta una serie di dritte, troppo specifiche per il mio esame, tanto sono di là 😀
In conclusione: i tools per produrre documentazione con Rust rockzs! 😀

:mrgreen:

Rust – il linguaggio – 46

1227

Continuo da qui, oggi qui: /usr/local/share/doc/rust/html/book/conditional-compilation.html.

Compilazione condizionale

Rust has a special attribute, #[cfg], which allows you to compile code based on a flag passed to the compiler. It has two forms:

#[cfg(foo)]
#[cfg(bar = "baz")]

e anche istruzioni più specifiche, combinabili:

#[cfg(any(unix, windows))]
#[cfg(all(unix, target_pointer_width = "32"))]
#[cfg(not(foo))]
#[cfg(any(not(unix), all(target_os="macos", target_arch = "powerpc")))]

Sono passati i tempi in cui si usava make. E per me questo rende inutili i tentativi (molto indietro nello scorso millennio) di convincere i fortrainer locali a usarlo 😀

As for how to enable or disable these switches, if you’re using Cargo, they get set in the [features] section of your Cargo.toml:

[features]
# no features by default
default = []

The “secure-password” feature depends on the bcrypt package.

secure-password = ["bcrypt"]

When you do this, Cargo passes along a flag to rustc:

--cfg feature="${feature_name}"

The sum of these cfg flags will determine which ones get activated, and therefore, which code gets compiled. Let’s take this code:

#[cfg(feature = "foo")]
mod foo {
}

If we compile it with cargo build --features "foo", it will send the --cfg feature="foo" flag to rustc, and the output will have the mod foo in it. If we compile it with a regular cargo build, no extra flags get passed on, and so, no foo module will exist.

cfg_attr

You can also set another attribute based on a cfg variable with cfg_attr:

#[cfg_attr(a, b)]

Will be the same as #[b] if a is set by cfg attribute, and nothing otherwise.

cfg!

The cfg! syntax extension lets you use these kinds of flags elsewhere in your code, too:

if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
    println!("Think Different!");
}

These will be replaced by a true or false at compile-time, depending on the configuration settings.

Rust ambisce a diventare il nuovo C. Ci riuscirà? ai posteri … (cit.) ma –così a sentimento– mi sembra che abbia tutte le carte in regola. Il C è nato in un ambiente molto nerd (si usava allora questa parola?) in condizioni serendipiche quando il mondo era ancora relativamente nuovo; oggi invece grande confusione sotto il cielo (cit.), ai posteri 😳

:mrgreen: