Rust - from Mozilla

Le stringhe

Le stringhe, in Rust come in molti altri linguaggi, sono delle normali sequenze di caratteri alfanumerici (Unicode, codificati UTF-8) Tuttavia questo linguaggio tratta tali sequenze in maniera un po' diversa da ltri linguaggi come il C o Java, per cui bisogna fare un po' di attenzione. Ad esempio le stringhe non sono null terminated ma possono contenere valori null. Da un punto di vista pratico siamo comunque davanti alle solite sequenze di caratteri indicizzati partendo dall'indice 0.
Rust ha due tipi di stringhe:
  • &str
  • String
Il primo tipo è un puntatore ad una sequenza immutabile di caratteri UTF-8 e vengono definite string slices. Iniziamo ad occuparci di queste, allargheremo poi il discorso.

let ciao = "ciao";

è un esempio di stringa letterale che fa parte delle string slices. La stringa è creata statica e, come detto, risulta immutabile. Questo tipo di dato fa parte dei tipi "unsized" la cui dimensione non può essere conosciuta a compile time.
Una stringa letterale può essere spezzata su più righe utilizzando due formati che si differenziano per il trattamento degli spazi e del "a capo".

  Esempio 13.1
1
2
3
4
5
6
7
8
9
fn main()
{
  let s1 = "ciao
  Mondo";
  let s2 = "ciao\
  Mondo";
  println!("{}",s1);
  println!("{}",s2);
}

L'output è:
ciao
        Mondo
ciaoMondo

le stringhe così definite, come abbiamo detto, sono riferite tramite puntatori e l'accesso non avviene in forma diretta bensì tramite l'operatore &.

Quando ci riferiamo alle stringhe di secondo tipo, indicato precedentemente come String, che è allocato nello heap e viene spesso ricavato da stringhe definite nel modo appena visto e convertite tramite il metodo to_string().

  Esempio 13.2
1
2
3
4
5
6
fn main()
{
  let s1 = "ciao mondo";
  let s2 = s1.to_string();
  println!("{}",s2);
}

s2 è una String. In questa fase dobbiamo osservare che la conversione costa memoria e, per i nostri scopi di base, non vi è al momento motivo di insistere con queste conversioni. Esiste un altro tipo di conversione che si basa sul metodo to_owned() che risulta molto meno pesante dal momento che si limita a bufferizzare il contenuto di una stringa &str.
A quiesto punto è utile vedere qualche metodo che ci sarà utile nella pratica.


len() - restituisce la lunghezza della stringa.

let s1 = "ciao mondo";
println!("{}",s1.len());

estrarre un elemento ad un dato indice è banale in quasi tutti i linguaggi ma in Rust è (naturalmente) un po' più complesso:

let a = "abcdefg";
println!( "a[2]: {}", a.chars().nth(2).unwrap());

stampa il carattere c che è il terzo della stringa e quindi ha indice 2. Le versioni definite unstable hanno a disposizione un comodo metodo charAt che, appunto, è "instabile" e quindi non potete usarlo con le release ufficiali. Che dire... boh....

is_empty ci dice se la stringa è vuota oppure no, restituendo true o false rispettivamente:

let a = "abcdefg";
println!( "{}", a.is_empty());

concatenare le stringhe non è cosa immediata restando in ambito delle slice:

let s1 = "abc";
let s2 = "def";
let s3 = format!("{}{}", s1, s2);
let s3 = &s3[..];
println!("{}", s3);

in questo senso è meglio lavorare sulle String, come si vede dal seguente esempio che fa uso di to_owned().

let s = "hello".to_owned();
println!("{}", s);
let s1 = s + " world";
println!("{}", s1);

come si vede è un po' meglio permettendoci di appendere uno slice alla stringa creata. In pratica, allo stato attuale, si può appendere una &str ad una String.

Restando nell'ambito delle String abbiamo visto che queste sono ottenute convertendo delle slice. Oltre ai metodi to_string() e to_owned() possiamo fare così:

let s1 = String::from("Hello, ");

Oppure, se preferite creala vuota:

let s1 = String::new();

alla quale potrete appendere i caratteri che volete tramite il metodo push. Con il metodo capacity invece potete effettuare delle interrogazioni sulla capacità:

  Esempio 13.3
1
2
3
4
5
6
7
fn main() {
  let mut s1 = String::new();
  println!("{}", s1.capacity());
  s1.push('h');
  s1.push('e');
  println!("{}", s1.capacity());
}

Anzichè un singolo carattere potete caricare direttamente una stringa:

s1.push_string("ciao");

Ovviamente l'operazione di aggiunta di un elemento è dispendiosa in termini prestazionali perchè va aggiunto spazio ad ogni ciclo. Per evitare questo è possibile creare una stringa vuota con una capacità iniziale predefinita:

let s1 = String::with_capacity(25);