Review of Remarkable2 after 1 month

Review of Remarkable2 after 1 month

Used ~daily for almost a month with Staedtler Noris Digital Mini Pen

The software is minimal but effective, my usage patterns are:

Note taking

I used to write a lot of back-of-the-envelope notes, diagrams, math. Lots of paper, lost, merged, mixed. Now I use RM2 for everything. From school meetings (as a parent) to code reviews.

Notes about a code review with drawings

Do not expect a Google Cursive-like experience, there are no stylus gestures here. Just pick the "select" tool and use it. Given the focused UI of RM2, this is not a real problem. Still, the Cursive UX is one of the best I have tried.

Layers are very useful, there is no feedback on what layer you are in, but copy and undo are here to help. You can draw a grid and reuse it almost like one of the many background templates.

The writing experience is very good, the latency is low. The only problem is the tiny offset between the edge of the pointer and the pixel you are writing on (the thickness of the glass). This kills my arrows, I can't align the tip <_ with the line. I hope I'll get into absorbing this offset in my muscle memory.

The response of the screen is good, but the closer you are to the edge, the more distortion you get. Just swipe with your fingers to write ~1cm from the edge.

Regarding the virtual tools, I mostly use the fineliner, but I love the Calligraphy pen for headers. The highlighter and colors (blue and red only) are useful and very pleasing only in export. Maybe also in screen sharing, but I haven't tried this feature yet (the official app on my Debian doesn't want to go).

I have not used the OCR feature, just tested it once and it "worked", not exactly as expected because I wrote on two columns and it merged the lines.

PDF Active Reading

I only used it a few times to read and annotate PDFs, sometimes to proofread my own writing.

You can move the PDF page in all directions, so that you have a virtually infinite margin, so you can really fit the proof of Fermat's theorem on it :P. You can also add new pages to the PDF for more extensive notes.

Fun with my kids

The "toy" is quite expensive, so they are only allowed to draw under my supervision. They found the UX very intuitive, loved trying out all the pens and drawing stuff in the Quick Sheets (which are pages in a single notebook, but you can later export them to a new titled notebook).

Resources

Conclusion

The toy is expensive, in 1 month I created 8 folders, with 1~6 notebooks each and dropped my paper usage to ~0. I'm able to categorize and tag the notebooks to find them easily. The interface should scale up to dozens of folders and dozens of documents (you can choose whether to see the first page or the current one in the preview), so I hope to be able to find things even when everything is fuller.

Trusting the 100-day trial, I am still considering the purchase. The competitor that has given me the most pause is PineNote, only it is still really too immature, although the developments look promising!

I still need to test the after-the-free-trial cloud experience, I fear it will be a sad experience.

Comments on Mastodon

Procedural macro in Rust 101

Procedural macro in Rust 101

How to pick a function and make it a macro with added superpowers

For some social interaction, have a look at the dev.to version of this article.

One upon a time I had the need to take a list of items and return all the combinations of all couples.

This task in Python is easily accomplished using the built-in itertools.combinations:

itertools.combinations(iterable, r) Return r length subsequences of elements from the input iterable. Combinations are emitted in lexicographic sort order. So, if the input iterable is sorted, the combination tuples will be produced in sorted order.

That offers a baseline implementation in Python (the real one is in C and based on iterators).

def combinations(iterable, r):
    # combinations('ABCD', 2) --> AB AC AD BC BD CD
    # combinations(range(4), 3) --> 012 013 023 123
    pool = tuple(iterable)
    n = len(pool)
    if r > n:
        return
    indices = list(range(r))
    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != i + n - r:
                break
        else:
            return
        indices[i] += 1
        for j in range(i+1, r):
            indices[j] = indices[j-1] + 1
        yield tuple(pool[i] for i in indices)

What we have here is a functions that takes a list and an integer r and returns a tuple of length r... hmm, not (yet) possible in plain Rust, but let's focus on my problem, I currently only need couples!

Here is a basic porting in Rust (generic enough to be modified for r > 2):

fn combinations2<T>(pool: &Vec<T>) -> Vec<[&T; 2]> {
    let r = 2; // <----- is the length of this ^
    let mut res = vec![];
    let n = pool.len();
    if r > n {
        return res;
    }
    let mut indices: Vec<_> = (0..r).collect();
    // we cannot `yield` (yet) in Rust, so we need an accumulator
    res.push([&pool[indices[0]], &pool[indices[1]]]);
    loop {
        // since this is not a generator like in Python, we cannot `return`, so the code
        // is slightly changed, `find` replaces the first `for` in Python.
        match (0..r).rev().find(|&i| indices[i] != (i + n - r)) {
            Some(i) => {
                indices[i] += 1;
                for j in (i + 1)..r {
                    indices[j] = indices[j - 1] + 1;
                }
                res.push([&pool[indices[0]], &pool[indices[1]]]);
            }
            None => break,
        }
    }
    res
}

Months later, I tried to achieve something using basic macros by example2 in Rust, but with no luck, it's not easy to take a single integer and expand1 it into a list of something, the code burden was going too high, and I'd have to chose upfront some maximum value for the r number 3.

So I gave up till the more powerful procedural macro reached the almost stable point!

But here be dragons! syn, proc_macro, proc_macro2, quote!, WTF?

Parsing and quoting

Luckily the syn crate repository contains some very useful examples, in particular: examples/lazy-static is An example of parsing a custom syntax within a functionlike!(...) procedural macro.5

But lets start from the syn crate, that quoting from the README is:

Syn is a parsing library for parsing a stream of Rust tokens into a syntax tree of Rust source code.

So, given some Rust-like code, Syn will give us a syntax tree, ready to be used through all the basic types of the syntax tree, like Expr, Ident, Token, Type, and many others. As noted in the README, Syn offers proc_macro2 a Proc macro shim , that allows Syn to support back to Rust 1.15.

The little quirk of proc_macro2 is that we are not allowed to use everything from a single place, because:

In general all of your code should be written against proc-macro2 rather than proc-macro. The one exception is in the signatures of procedural macro entry points, which are required by the language to use proc_macro::TokenStream.

Once we have parsed our input, we need to generate back some Rust code, and here kicks in the quote crate, that:

... provides the quote! macro for turning Rust syntax tree data structures into tokens of source code.

Our workflow will be:

  1. define some syntax for our procedural macro4, for example combinations!(comb3, 3) that will declare a function comb3(values: &[T]) -> Vec<[T; 3]>,
  2. define a struct to store the parsed parts from the macro call: comb3 and 3,
  3. use those values to parametrize the specialized code of combinations2,
  4. return the abstract new function and build back the Rust code for the compiler,
  5. call the new macro-generated function!

Putting the pieces together

The first piece of the puzzle is a struct that will contain the user values in the macro definition, in this case we have only a name and a number:

struct  Combinations {
    name: syn::Ident,
    n: syn::LitInt,
}

And now we need some way to parse something into this struct, and here kicks in the Parse trait a Parsing interface implemented by all types that can be parsed in a default way from a token stream:

impl Parse for Combinations {
    fn parse(input: ParseStream) -> Result<Self> {
        let name = input.parse()?;
        input.parse::<syn::Token![,]>()?;
        let n = input.parse()?;
        Ok(Combinations { name, n })
    }
}

Thanks to the Rust type inference super powers, calling input.parse()? let us parse the values into the right type or return and error if the parsing fails. The strange line input.parse::<syn::Token![,]>()?; is a special syntax to parse simple tokens. Now that we have parsed our input, we can write our super minimal macro, to test that everything worked as expected:

#[proc_macro]
pub fn minimal(input: TokenStream) -> TokenStream {
    let Combinations { name, n } = parse_macro_input!(input as Combinations);
    quote!{
        fn #name() -> i32 {
            #n
        }
    }
}

Ops, compilation error :/

   Compiling comb v0.1.0 (/home/naufraghi/Documents/src/circle-rs.git/comb)
error[E0308]: mismatched types
  --> comb/src/lib.rs:27:5
   |
25 |   pub fn minimal(input: TokenStream) -> TokenStream {
   |                                         ----------- expected `proc_macro::TokenStream` because of return type
26 |       let Combinations { name, n } = parse_macro_input!(input as Combinations);
27 | /     quote!{
28 | |         fn #name() -> i32 {
29 | |             #n
30 | |         }
31 | |     }
   | |_____^ expected struct `proc_macro::TokenStream`, found struct `proc_macro2::TokenStream`
   |
   = note: expected type `proc_macro::TokenStream`
              found type `proc_macro2::TokenStream`

Remember, syn offers a drop-in replacement for everything, except TokenStream, that is supposed to be the original from the compiler interfaces. Luckily the conversion from one into() the other is simple.

#[proc_macro]
pub fn minimal(input: TokenStream) -> TokenStream {
    let Combinations { name, n } = parse_macro_input!(input as Combinations);
    (quote!{
        fn #name() -> i32 {
            #n
        }
    }).into()
}

Without the extra parentheses you will have another strange compilation error, sadly is not even possible to expand the macro to see exactly what's happening, ...

   Compiling comb v0.1.0 (/home/naufraghi/Documents/src/circle-rs.git/comb)
error: expected expression, found `.`
  --> comb/src/lib.rs:31:6
   |
31 |     }.into()
   |      ^ expected expression
error[E0308]: mismatched types
  --> comb/src/lib.rs:27:5
   |
27 | /     quote!{
28 | |         fn #name() -> i32 {
29 | |             #n
30 | |         }
31 | |     }.into()
   | |_____^ expected (), found struct `proc_macro2::TokenStream`
   |
   = note: expected type `()`
              found type `proc_macro2::TokenStream`

Did I said expand? Yes, we can look the macro expanded code with the cargo expand command, and this is the result:

$ cargo expand --bin comb
    Checking circle v0.1.0 (/home/naufraghi/Documents/src/circle-rs.git)
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::v1::*;
#[macro_use]
extern crate std as std;
fn mini3() -> i32 { 3 }    // <----- here is our expansion!
// ...

Summing all up, we have this pipeline:

TokenStream -> parse_macro_input!(...) -> quote!{...} -> TokenStream

And inside quote!{} the variables are accessed with the #name syntax.

Extra powers

Here we are, we have written a lot more code to reach a point we already know how to reach with the good old macros by example, but we had a problem, how to expand a number into a list, because this is the monster of this level:

res.push([&pool[indices[0]], &pool[indices[1], ...]])

We need some way to accumulate a little piece of syntax, &pool[indices[#i]], and at the end join the parts into a comma separated list. Digging a bit into syn, what we need is syn::punctuated:

A punctuated sequence of syntax tree nodes separated by punctuation. Lots of things in Rust are punctuated sequences. - The fields of a struct are Punctuated<Field, Token![,]>. ...

What we can do is:

#[proc_macro]
pub fn punctuated(input: TokenStream) -> TokenStream {
    let Combinations { name, n } = parse_macro_input!(input as Combinations);
    let indices: syn::punctuated::Punctuated<_, syn::Token![,]> = (0..n.value() as i32)
        .map(|i| quote! { #i })  // <-- add the quoted variable, `i` as a token
        .collect();
    (quote! {
      fn #name() -> Vec<i32> {
        vec![#indices]  // put the inner part of the list into something
      }
    })
    .into()
}

We can finally call the macro punctuated!(punct4, 4); that expands into:

fn punct4() -> Vec<i32> { <[_]>::into_vec(box [0i32, 1i32, 2i32, 3i32]) }

We now have all the pieces to make our combinations function generic on the size r of the returned array:

#[proc_macro]
/// `combinations!(comb, N)` macro will define a function `comb` that takes a `&[T]` and returns
/// a vector of arrays of length _N_, `Vec<[&T; N]>`, covering all the combinations of _N_ elements
/// from the source vector.
pub fn combinations(input: TokenStream) -> TokenStream {
    let Combinations { name, n } = parse_macro_input!(input as Combinations);
    let pool_indices: syn::punctuated::Punctuated<_, syn::Token![,]> = (0..n.value() as usize)
        .map(|i| quote! { &pool[indices[#i]] })
        .collect();
    let comb = quote! {
        fn #name<T>(pool: &[T]) -> Vec<[&T; #n]> {
            let r = #n;
            let mut res = vec![];
            let n = pool.len();
            if r > n {
                return res;
            }
            let mut indices: Vec<_> = (0..r).collect();
            res.push([#pool_indices]);
            loop {
                match (0..r).rev().find(|&i| indices[i] != (i + n - r)) {
                    Some(i) => {
                        indices[i] += 1;
                        for j in (i + 1)..r {
                            indices[j] = indices[j - 1] + 1;
                        }
                        res.push([#pool_indices]);
                    },
                    None => break,
                }
            }
            res
        }
    };
    comb.into()
}

Here we are, we can now define with a simple macro call a function with the wanted size, in the future we may also create and call a macro in a single shot, but not yet 4.

A final note, the macro can be used only from another crate, the suggested layout is highlighted in the lazy-static example.

Thanks to @dodomorandi for the review of the first draft and for the suggestions about a more idiomatic combinations2, and for his Iterator based implementation, and thanks to @lu-zero for the review and for the suggestion to avoid syn and search for a simpler method, task I left as an exercise for the reader.

Iterator based implementation
#[proc_macro]
pub fn iter_combinations(input: TokenStream) -> TokenStream {
    let Combinations { name, n } = parse_macro_input!(input as Combinations);
    let pool_indices: syn::punctuated::Punctuated<_, syn::Token![,]> = (0..n.value() as usize)
        .map(|i| quote! { &self.pool[self.indices[#i]] })
        .collect();

    let initial_indices: syn::punctuated::Punctuated<_, syn::Token![,]> =
        (0..n.value() as usize).collect();

    let iter_name = proc_macro2::Ident::new(
        &format!("CombIndicesIter{}", n.value()),
        proc_macro2::Span::call_site(),
    );

    let comb = quote! {
        pub struct #iter_name<'a, T> {
            pool: &'a [T],
            indices: [usize; #n],
            started: bool,
        }

        impl<'a, T> Iterator for #iter_name<'a, T> {
            type Item = [&'a T; #n];

            fn next(&mut self) -> Option<Self::Item> {
                if !self.started {
                    self.started = true;
                    Some([#pool_indices])
                } else {
                    let n = self.pool.len();
                    (0..#n).rev().find(|&i| self.indices[i] != i + n - #n)
                        .map(|i| {
                                self.indices[i] += 1;
                                for j in (i + 1)..#n {
                                    self.indices[j] = self.indices[j - 1] + 1;
                                }
                                [#pool_indices]
                        })
                }
            }
        }

        fn #name<T>(pool: &[T]) -> impl Iterator<Item = [&T; #n]> {
            #iter_name {
                pool,
                indices: [#initial_indices],
                started: false,
            }
        }
    };
    comb.into()
}

  1. I'd have to expand 2 -> 0, 1, and then map it to &pool[indices[0]], &pool[indices[1]] 

  2. Super in-depth intro by DanielKeep@github: A Practical Intro to Macros in Rust 1.0 

  3. Again by DanielKeep, The Little Book of Rust Macros: Counting 

  4. Sadly the usage of a macro in expression position, like let res = combinations!(&data, 3), is feature gated behind #![feature(proc_macro_hygiene)] and limited to the nightly edition, so we use another approach. 

  5. One may try to avoid syn and use a combination of macro by example and procedural macros, but this is left as an exercise for the reader. 

Somewhat safer unpickler

This class inherits all the implementation from the builtin pickle.Unpickler but modifies the self.dispatch dictionary used to unpack the serialized structures.

This is not checked to be safe, but in case you are using pickled files in production and you are searching for some safer way to load them to convert the data to a different format, this class can be handy.

The same idea works for Python 3, subclassing pickle._Unpickler.

Test riproducibili con il tempo (parte 2)

Nella parte 1 di questo blog post eravamo arrivati ad avere una situazione del tipo:

use std::marker::PhantomData;
struct Event<T> {
    message: String,
    stamp: DateTime<Utc>,
    mark: PhantomData<T>,
}
trait Now {
    fn now() -> DateTime<Utc> {
        Utc::now()
    }
}
impl<T: Now> Event<T> {
    fn new(message: String) -> Event<T> {
        Event {
            message,
            stamp: T::now(),
            mark: PhantomData,
        }
    }
}

struct SystemNow;
impl Now for SystemNow {};

let _event: Event<SystemNow> = Event::new("Messaggio".to_string());

Quindi adesso è facile creare un trait che restituisce un momento fisso nel tempo:

struct FixedNow;

impl Now for FixedNow {
    fn now() -> DateTime<Utc> {
        Utc.ymd(2012, 11, 23).and_hms(18, 53, 7)
    }
}

let event: Event<FixedNow> = Event::new("Messaggio 2012".to_string());
assert_eq!(event.stamp.year(), 2012);

Però, molto spesso avremo bisogno di avere una sequenza di particolari timestamp che vadano a testare alcuni aspetti critici della nostra implementazione.

Nel caso del nostro trait abbiamo un problema, abbiamo definito un metodo statico, non abbiamo nessun self in cui memorizzare l'evoluzione della nostra sequenza di timestamp. Dobbiamo trovare qualche altra soluzione, e ci sono almeno un paio di alternative.

Nel caso in cui siamo sicuri che nel test il metodo now() verrà chiamato sempre e solo da un singolo thread, ce la possiamo cavare con una variabile static 1, anzi ad essere precisi static mut, condita con un pizzico di unsafe.

Le variabili static hanno una posizione fissa nella memoria durante l'esecuzione del programma, se si cerca di definire una variabile static mut non c'è modo un Rust safe di modificarla, perché non c'è modo di garantire l'atomicità delle modifiche ad una variabile che può essere potenzialmente modificata da più thread in contemporanea. Però nel nostro caso, in un ambiente di test controllato, possiamo usare una variabile static per avere uno stato in un metodo ... statico.

static mut COUNTER: i32 = 0;
COUNTER = COUNTER + 1;

Provando a modificare direttamente direttamente la variabile COUNTER il compilatore ci segnala l'errore:

error[E0133]: use of mutable static requires unsafe function or block
 --> src/part2.rs:92:1
  |
4 | COUNTER = COUNTER + 1;
  | ^^^^^^^^^^^^^^^^^^^^^ use of mutable static

Per fortuna, ormai ci siamo abituati bene, il compilatore ci suggerisce anche una soluzione, usare un blocco unsafe.

Un blocco unsafe in Rust è un blocco dove il compilatore permette di compiere operazioni più pericolose, e si fida di quello che lo sviluppatore ha scritto. Appena usciti dal blocco unsafe, tutti i normali controlli torno attivi, quindi il blocco unsafe è la parte più pericolosa del codice, quella a cui in una review serve mettere doppia attenzione, ma non è diverso da scrivere in C o C++.

A volte può essere utile (per questioni di performance) oppure necessario (come nel caso della comunicazione con altre librerie tramite l'interfaccia C), in questo caso è solo un esempio, perché come vedremo, la libreria standard ci offre qualche altra opzione.

struct LoopNow;

impl Now for LoopNow {
    fn now() -> DateTime<Utc> {
        static mut COUNTER: i32 = 0;
        let inc = unsafe {
            COUNTER = (COUNTER + 1) % 10;
            COUNTER
        };
        Utc.ymd(2000 + inc, 9, 9).and_hms(1, 46, 40)
    }
}
let event: Event<LoopNow> = Event::new("Messaggio 2001".to_string());
assert_eq!(event.stamp.year(), 2001);
let event: Event<LoopNow> = Event::new("Messaggio 2002".to_string());
assert_eq!(event.stamp.year(), 2002);

Facciamo una breve digressione sulla variabile statica, cosa vuol dire, come funziona questa cosa magica che una funzione senza nessuna variabile globale può avere un suo stato?

Beh, in realtà la COUNTER è in un certo senso globale, ed il suo valore iniziale viene definito prima della prima esecuzione della funzione, è proprio compilato nel codice del programma. Quello che succede nella funzione è andare a trovare l'indirizzo di quei 4 byte di memoria per leggerci e scriverci dentro, mentre sullo stack non c'è niente relativo a COUNTER. Per un approfondimento, nelle note c'è il link ad un articolo interessante 4.

Questo è l'ASM generato con una versione ridotta dell'esempio 5 che abbiamo usato qua (con un po' di cerimonia per non far ottimizzare tutto al compilatore).

playground::count:
    addl    playground::count::COUNT(%rip), %edi
    movl    %edi, playground::count::COUNT(%rip)
    movl    %edi, %eax
    retq

playground::main:
    pushq   %rax
    movl    $1, %edi
    callq   playground::count
    movl    %eax, %edi
    callq   playground::count
    movl    %eax, %edi
    callq   std::process::exit@PLT
    ud2

La strana forma playground::count::COUNT(%rip) rappresenta simbolicamente l'offset rispetto all'indirizzo dell'istruzione corrente (+1 pare, ma è un dettaglio che non ci tange) a cui trovare la zona di memoria in cui scrivere. E l'indirizzo della zona di memoria è fisso e non cambia tra successive chiamate a count, mentre il valore contenuto cambia ogni volta.

Adesso che abbiamo capito il trucco delle variabili static, abbiamo ancora un problema, non siamo ancora in grado di eseguire un test con più thread. Ma le moderne CPU ci vengono in aiuto. Possiamo definire una variabile atomica, una variabile speciale per cui non serviranno strutture di sincronizzazione particolari, perché è il processore stesso a garantire l'atomicità di un particolare set di operazioni.

In un esempio minimale come il precedente 6, con una variabile atomica l'asm generato per la funzione count è:

playground::count:
    lock        xaddq   %rdi, playground::count::COUNTER(%rip)
    movq    %rdi, %rax
    retq
use std::sync::atomic::{AtomicUsize, Ordering};

struct IncrementNow;

impl Now for IncrementNow {
    fn now() -> DateTime<Utc> {
        static COUNTER: AtomicUsize = AtomicUsize::new(1);
        let inc = COUNTER.fetch_add(1, Ordering::SeqCst) as i32;
        Utc.ymd(2000 + inc, 9, 9).and_hms(1, 46, 40)
    }
}

Le variabili atomiche 2 sono il tassello base per la costruzione di primitive di sincronizzazione tra thread, in questo caso abbiamo semplicemente usato il valore più conservativo per l'ordering, perché approfondire questo argomento vorrebbe dire aprire un vaso di Pandora, che sinceramente non ho ancora aperto (almeno, ho solo sbirciato un po'). In breve, anche solo dal fatto che senza usafe il compilatore non ci segnala problemi, possiamo assumere che qualcun altro ha garantito che l'uso di questa primitiva è sicuro.

Però così senza farsi notare abbiamo usato un concetto che ancora non abbiamo presentato, che invece sarà sicuramente saltato all'occhio al lettore attento: come possiamo mutare COUNTER se non lo abbiamo definito mutabile?

Questo è un pattern in Rust detto interior mutability, che sarebbe la possibilità di creare un contenitore ufficialmente non mutabile, che al suo interno si occupa di garantire alcune condizioni al contorno della modifica del valore interno. In questo caso tutte le variabili atomiche in std::sync::atomic sono Sync, cioè sono marcate in modo da garantire al compilatore che è possibile accedere a queste variabili da più thread contemporaneamente.

Il più semplice tipo che offre una "scatola" immutabile per un contenuto mutabile è Cell 3, ma in questo caso non c'è nessun marcatore Sync, quindi il compilatore non ci permetterà di usare Cell in contesti multithread, ma ci permette di avere più di un riferimento mutabile ad una singola variabile. E quindi cosa ci abbiamo guadagnato rispetto ad una normale refrerence stile C++? Semplicemente che nell'implementazione di Cell c'è un controllo ed una mutazione concorrente non prevista farà crashare il programma. Meglio un errore subito che corrompere silenziosamente i dati (per poi magari crashare comunque più tardi).

type IncrementEvent = Event<IncrementNow>;

let event1 = IncrementEvent::new("IncrementEvent 1".to_owned());
assert_eq!(event1.stamp.year(), 2001);
assert_eq!(event1.stamp.minute(), 46);

let event2 = IncrementEvent::new("IncrementEvent 2".to_owned());
assert_eq!(event2.stamp.year(), 2002);
assert_eq!(event2.stamp.minute(), 46);

Verrebbe voglia di scrivere un test per verificare qualche corruzione della memoria nel caso dell'uso dello static mut, ma lo lascio come esercizio per il lettore.

Un ringraziamento al gruppetto di Rustiti anonimi di @develer per la review.

Grazie per la lettura, se questa mini serie vi piaciuta fatemelo sapere, potrebbe essere lo spunto per scrivere qualcos'altro, e non esitate a contattarmi (@naufraghi su twitter) per segnalare errori od omissioni!


  1. Vedi const and static nella prima edizione del Rust Book oppure Accessing or Modifying a Mutable Static Variable nella seconda edizione del libro o anche Static items nella reference del linguaggio. 

  2. Module std::sync::atomic nella documentazione della libreria standard. 

  3. Module std::cell nella documentazione della libreria standard. 

  4. Understanding C by learning assembly, in particolare il capitolo Understanding static local variables

  5. Esempio variabile statica mutabile sul Playground. 

  6. Esempio variabile statica atomica sul Playground. 

Test riproducibili con il tempo (parte 1)

Ormai è difficile non aver sentito parlare di Rust, il nuovo linguaggio di Mozilla, che promette la velocità del C++ senza troppi grattacapi. Il bello di Rust è che pur essendo un linguaggio "di sistema", cioè un linguaggio con un controllo dell'esecuzione simile a quello offerto dal C, è anche un linguaggio che cura molto l'esperienza dello sviluppatore, con tool ergonomici e messaggi di errori di prima qualità.

Quindi che siate un rodato sviluppatore C/C++ o uno sviluppatore Python / Javascript / Ruby, se avete voglia di aggiungere un nuovo linguaggio al vostro repertorio, fate una prova con Rust!

Ormai sto usando Rust come hobby da un annetto, non ho scritto niente di serio ma mi piace cercare di trovare una soluzione ai tipici problemi che sono abituato ad affrontare in Python, magari riuscendo ad avere quel controllo in più a compile time che in Python è ancora un po' faticoso da ottenere.

Quindi non prendete come oro colato quello che scrivo, anzi, se ci fossero correzioni o suggerimenti contattatemi pure.

Immaginiamoci quindi programmatori Rust non navigati, stiamo scrivendo la nostra libreria in Rust, che sia una "indovina il numero" o una lista della spesa, avremo qualche evento a cui vorremo rispondere, e per rendere l'informazione più completa, vogliamo anche avere un timestamp.

Una prima implementazione potrebbe essere qualcosa del tipo:

extern crate chrono;
use chrono::prelude::*;

struct Event {
    message: String,
    stamp: DateTime<Utc>,
}

impl Event {
    fn new(message: String) -> Event {
        Event {
            message,
            stamp: Utc::now(),
        }
    }
}

Ma ora si pone un problema, come si possono scrivere test di una cosa del genere? In Python si possono usare mock nei modi più arditi, ma in Rust è tutta un'altra storia, dobbiamo preparare un po' la strada per iniettare questa dipendenza.

Un approccio potrebbe essere quella di definire un Trait 2 che rappresenti la funzionalità di poter ottenere l'ora corrente.

trait Now {
    fn now() -> DateTime<Utc> {
        Utc::now()
    }
}

Magari non vogliamo che sia proprio Event ad implementare questo Trait, ma una altra struttura di utilità.

struct SystemNow;
impl Now for SystemNow {};

Non serve scrivere molto, stiamo definendo una struttura vuota che usa l'implementazione di default del trait.

Adesso possiamo far usare questo nuovo tipo al nostro Event, ma se facciamo la cosa semplice:

impl Event {
    fn new(message: String) -> Event {
        Event {
            message,
            stamp: SystemNow::now(),
        }
    }
}

Non ci abbiamo guadagnato molto, non sappiamo ancora come modificare quel now() nei test. Potremmo passare una istanza come argomento, e chiamare il metodo now(), ma nell'uso reale non ci serve che sia una istanze, e non vogliamo creare una astrazione "costosa" solo per rendere il codice più testabile, più che altro quando ci sono alternative a costo zero!

In Rust quasi tutto può essere reso generico rispetto al tipo, Option<i32> ed Option<String> sono due oggetti molto simili, la stessa scatola con un contenuto diverso. Ed indipendentemente dal tipo avremo tutti i metodi di Option 3 disponibili in entrambi i casi.

Potremmo definire un Event<T> e poi usare come T il nostro SystemNow, proviamo:

struct Event<T> {
    message: String,
    stamp: DateTime<Utc>,
}

Ma otteniamo un errore di compilazione:

error[E0392]: parameter `T` is never used
 --> src/lib.rs:97:14
  |
6 | struct Event<T> {
  |              ^ unused type parameter
  |
  = help: consider removing `T` or using a marker such as `std::marker::PhantomData`

Hmmm, qua c'è un suggerimento interessante, se vogliamo associare un tipo alla nostra struttura, ma non vogliamo averne una istanza, possiamo usare un marcatore 1.

use std::marker::PhantomData;

struct Event<T> {
    message: String,
    stamp: DateTime<Utc>,
    mark: PhantomData<T>,
}

impl<T> Event<T> {
    fn new(message: String) -> Event<T> {
        Event {
            message,
            stamp: T::now(),
            mark: PhantomData,
        }
    }
}

Ma anche in questo caso abbiamo un errore:

error[E0599]: no function or associated item named `now` found for type `T` in the current scope
  --> src/lib.rs:131:20
   |
22 |             stamp: T::now(),
   |                    ^^^^^^ function or associated item not found in `T`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `now`, perhaps you need to implement it:
           candidate #1: `main::Now`

Anche in questo caso l'errore ci da un buon suggerimento, ed in effetti il compilatore ha ragione, non abbiamo ancora scritto da nessuna parte che il nostro generico T deve implementare il trait Now. Possiamo farlo semplicemente aggiungendo un vincolo alla definizione della nostra struttura Event:

use std::marker::PhantomData;
struct Event<T> {
    message: String,
    stamp: DateTime<Utc>,
    mark: PhantomData<T>,
}
impl<T: Now> Event<T> {
    fn new(message: String) -> Event<T> {
        Event {
            message,
            stamp: T::now(),
            mark: PhantomData,
        }
    }
}

let event: Event<SystemNow> = Event::new("Messaggio".to_string());

Adesso il codice compila di nuovo, perché il compilatore ha modo di verificare che il generico T non è così generico come prima, ma deve implementare i metodi definiti nel trait Now, e tra questi (uno solo, in questo caso) c'è proprio un metodo now() con la firma giusta.

Adesso abbiamo tutti gli ingredienti per usare un trait diverso nei test rispetto a quello usato nella libreria, ma come lo vedremo nella seconda parte di questo post.

I sorgenti di questo progetto sono disponibili qua: gitlab.com/naufraghi/phantomtypes

Grazie a @tglman e @lucabruno per la revisione della bozza.


  1. Potete verificare che il marcatore è a costo zero in questo snippet

  2. I Trait sono una cosa simile alle interfacce del Go o alle interfacce dei linguaggi che usano l'ereditarietà, per maggiori dettagli Traits: Defining Shared Behavior

  3. Option ha un numero enorme di metodi di utilità, che conviene tenere sott'occhio, perché ce ne è uno adatto per quasi ogni caso d'uso che potrebbe capitarvi. 

Simple Rust Pipeline on Bitbucket

Bitbucket offers a builtin execution pipeline (Docker based), here is a minimal configuration to have your Rust test running:

# This is a sample build configuration for all languages.
# Check our guides at https://confluence.atlassian.com/x/VYk8Lw for more examples.
# Only use spaces to indent your .yml configuration.
# -----
# You can specify a custom docker image from Docker Hub as your build environment.
image: python:2.7

pipelines:
  default:
    - step:
        script:
          - curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y
          - source $HOME/.cargo/env
          - cargo test