Tipi Primitivi¶
Riferimento originale
📖 Documentazione originale 🔄 Ultimo aggiornamento: Ottobre 2025 📝 Versione Rust: 1.90+
I tipi primitivi sono i mattoni fondamentali del linguaggio Rust. Questi tipi sono integrati nel linguaggio stesso e forniscono le operazioni base per lavorare con numeri, testo, valori logici e strutture dati fondamentali.
Panoramica¶
Rust fornisce 18 tipi primitivi principali, organizzati in diverse categorie:
- Tipi numerici: interi con e senza segno, numeri in virgola mobile
- Tipi testuali: caratteri e string slice
- Tipi logici: booleani
- Tipi composti: array, slice, tuple e unit type
- Puntatori e riferimenti: puntatori raw, riferimenti, function pointer
A differenza di molti altri linguaggi, in Rust i tipi primitivi sono tutti a dimensione nota a compile-time (tranne slice e str che sono dynamically sized types - DST).
Tipi Numerici Interi¶
Rust offre una vasta gamma di tipi interi, permettendoti di scegliere la dimensione appropriata in base alle tue esigenze.
Interi con Segno (Signed)¶
Gli interi con segno possono rappresentare sia numeri positivi che negativi usando la rappresentazione in complemento a due.
| Tipo | Dimensione | Range | Uso tipico |
|---|---|---|---|
i8 |
8 bit | -128 a 127 | Piccoli numeri, ottimizzazione memoria |
i16 |
16 bit | -32,768 a 32,767 | Valori numerici medi |
i32 |
32 bit | -2,147,483,648 a 2,147,483,647 | Tipo di default per interi |
i64 |
64 bit | -9,223,372,036,854,775,808 a 9,223,372,036,854,775,807 | Grandi numeri, timestamp |
i128 |
128 bit | Estremamente ampio | Calcoli crittografici, UUID |
isize |
Dipende dall'architettura | 32 o 64 bit | Indicizzazione, dimensioni memoria |
let a: i8 = -128;
let b: i32 = 42; // Tipo di default
let c: i64 = -1_000_000;
let d: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727;
Interi Senza Segno (Unsigned)¶
Gli interi senza segno possono rappresentare solo numeri positivi (incluso lo zero), permettendo di raddoppiare il range massimo positivo.
| Tipo | Dimensione | Range | Uso tipico |
|---|---|---|---|
u8 |
8 bit | 0 a 255 | Byte, dati binari, pixel |
u16 |
16 bit | 0 a 65,535 | Port numbers, ID |
u32 |
32 bit | 0 a 4,294,967,295 | Contatori, hash |
u64 |
64 bit | 0 a 18,446,744,073,709,551,615 | File size, timestamp Unix |
u128 |
128 bit | Estremamente ampio | Crittografia, UUID |
usize |
Dipende dall'architettura | 32 o 64 bit | Indici di array e collection |
Underscore nei letterali numerici
Puoi usare _ nei numeri per migliorare la leggibilità: 1_000_000 è equivalente a 1000000.
isize e usize¶
I tipi isize e usize hanno una dimensione che dipende dall'architettura del sistema:
- 32 bit su sistemi a 32 bit
- 64 bit su sistemi a 64 bit
let index: usize = 0;
let offset: isize = -10;
let v = vec![1, 2, 3, 4, 5];
let first = v[0]; // Gli indici sono sempre usize
Quando usare isize/usize
- usize: per indicizzare array, Vec, slice e altre collection
- isize: per rappresentare offset o differenze tra puntatori
- La libreria standard usa questi tipi per dimensioni e indici
Letterali Numerici¶
Rust permette di specificare i letterali numerici in vari formati:
let decimal = 98_222; // Decimale
let hex = 0xff; // Esadecimale
let octal = 0o77; // Ottale
let binary = 0b1111_0000; // Binario
let byte = b'A'; // Byte (solo u8)
// Suffissi di tipo
let x = 42u32; // u32
let y = 100_i64; // i64
Overflow e Comportamenti¶
In modalità debug, Rust controlla l'overflow degli interi e causa panic se si verifica:
In modalità release, Rust usa wrapping behavior (complemento a due):
Gestione esplicita dell'overflow
Per controllare esplicitamente il comportamento in caso di overflow, usa i metodi dedicati:
let x: u8 = 200;
let y: u8 = 100;
// wrapping_* - comportamento wrapping
let z = x.wrapping_add(y); // 44
// checked_* - restituisce Option
let z = x.checked_add(y); // None
// saturating_* - satura al min/max
let z = x.saturating_add(y); // 255
// overflowing_* - restituisce tupla (risultato, overflow)
let (z, overflow) = x.overflowing_add(y); // (44, true)
Tipi Floating-Point¶
Rust fornisce due tipi per numeri in virgola mobile, seguendo lo standard IEEE 754.
f32 e f64¶
| Tipo | Dimensione | Precisione | Uso tipico |
|---|---|---|---|
f32 |
32 bit | ~6-9 cifre decimali | Grafica, ottimizzazione memoria |
f64 |
64 bit | ~15-17 cifre decimali | Tipo di default, calcoli scientifici |
let x = 2.0; // f64 (default)
let y: f32 = 3.0; // f32
// Notazione scientifica
let large = 1e6; // 1,000,000.0
let small = 1e-6; // 0.000001
Operazioni Floating-Point¶
let sum = 5.0 + 10.0;
let difference = 95.5 - 4.3;
let product = 4.0 * 30.0;
let quotient = 56.7 / 32.2;
let remainder = 43.0 % 5.0;
// Funzioni matematiche
let abs = (-3.5f64).abs();
let floor = 3.7f64.floor();
let ceil = 3.2f64.ceil();
let round = 3.5f64.round();
let sqrt = 2.0f64.sqrt();
let pow = 2.0f64.powi(3); // 8.0
Attenzione: Confronti con floating-point
I numeri in virgola mobile possono avere problemi di precisione. Evita confronti diretti con ==:
Valori Speciali¶
I tipi floating-point hanno valori speciali:
let inf = f64::INFINITY;
let neg_inf = f64::NEG_INFINITY;
let nan = f64::NAN;
// Controlli
let x = 1.0 / 0.0;
assert!(x.is_infinite());
assert!(x.is_sign_positive());
let y = 0.0 / 0.0;
assert!(y.is_nan());
NaN non è uguale a se stesso
Tipo Booleano¶
Il tipo bool rappresenta un valore logico che può essere true o false.
let t = true;
let f: bool = false;
// Operatori logici
let and = t && f; // false
let or = t || f; // true
let not = !t; // false
// Uso in condizioni
if t {
println!("È vero!");
}
Conversione da Booleani¶
// Bool -> numero
let n = true as u8; // 1
let m = false as u8; // 0
// Numero -> bool richiede confronto esplicito
let x = 5;
let is_positive = x > 0; // true
Non conversione implicita
A differenza di C, Rust non converte automaticamente numeri in bool:
Tipo Carattere¶
Il tipo char rappresenta un valore scalare Unicode e occupa sempre 4 byte.
Caratteristiche di char¶
- Rappresenta un Unicode Scalar Value (da U+0000 a U+D7FF e da U+E000 a U+10FFFF)
- Dimensione: 4 byte (32 bit)
- Si scrive con apici singoli
'a'(le stringhe usano apici doppi"abc")
let letter = 'A';
let digit = '5';
let unicode = '\u{1F600}'; // 😀 (emoji)
// Metodi utili
assert!(letter.is_alphabetic());
assert!(digit.is_numeric());
assert_eq!(letter.to_lowercase().next(), Some('a'));
char vs byte
String Slice (str)¶
Il tipo str è un tipo primitivo per rappresentare stringhe UTF-8. Normalmente lo si incontra nella sua forma borrowed &str.
Caratteristiche di str¶
- Sequenza di byte UTF-8 immutabile
- Dynamically Sized Type (DST) - dimensione non nota a compile-time
- Quasi sempre usato come
&str(riferimento a str)
// Letterali stringa sono &str
let s1 = "hello";
let s2: &str = "world";
// String slice da String
let string = String::from("hello world");
let slice = &string[0..5]; // "hello"
// Metodi comuni
let len = s1.len(); // 5
let is_empty = s1.is_empty(); // false
let contains = s1.contains("ll"); // true
let upper = s1.to_uppercase(); // "HELLO" (restituisce String)
str vs String
str(&str): vista immutabile su una stringa UTF-8 (slice)String: stringa heap-allocated, mutabile e proprietaria
Iterazione su str¶
let s = "hello";
// Caratteri Unicode
for c in s.chars() {
println!("{}", c);
}
// Byte grezzi
for b in s.bytes() {
println!("{}", b);
}
Indicizzazione non disponibile
Non puoi indicizzare direttamente una &str perché i caratteri UTF-8 possono occupare byte variabili:
Array¶
Un array [T; N] è una collezione a dimensione fissa di elementi dello stesso tipo allocata sullo stack.
let a: [i32; 5] = [1, 2, 3, 4, 5];
let b = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// Accesso
let first = a[0];
let second = a[1];
Caratteristiche degli Array¶
- Dimensione fissa nota a compile-time
- Allocati sullo stack
- Accesso con indice: O(1)
- Dimensione parte del tipo:
[i32; 3]è diverso da[i32; 5]
let months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
// Lunghezza
let len = months.len(); // 12
// Iterazione
for month in months.iter() {
println!("{}", month);
}
Bounds checking
Rust verifica sempre i limiti degli array a runtime:
Array multidimensionali¶
Slice¶
Una slice [T] è una vista dynamically sized su una sequenza contigua di elementi. Come str, viene quasi sempre usata come riferimento &[T].
Creare Slice¶
let a = [1, 2, 3, 4, 5];
let complete = &a[..]; // Tutta l'array
let from_start = &a[..3]; // [1, 2, 3]
let to_end = &a[2..]; // [3, 4, 5]
let middle = &a[1..4]; // [2, 3, 4]
Slice da Vec¶
let v = vec![1, 2, 3, 4, 5];
let slice: &[i32] = &v[1..3]; // [2, 3]
// Funzione che accetta slice
fn sum(slice: &[i32]) -> i32 {
slice.iter().sum()
}
let total = sum(&v); // ✅ Vec
let partial = sum(&a); // ✅ Array
let some = sum(slice); // ✅ Slice
Accetta slice, non array o Vec
Quando scrivi funzioni che leggono sequenze di dati, accetta &[T] invece di &Vec<T> o &[T; N]. Questo rende la funzione più flessibile e idiomatica.
Slice mutabili¶
let mut a = [1, 2, 3, 4, 5];
let slice: &mut [i32] = &mut a[1..4];
slice[0] = 10;
// a è ora [1, 10, 3, 4, 5]
Metodi comuni su Slice¶
let slice = &[1, 2, 3, 4, 5];
// Lunghezza e controlli
let len = slice.len();
let is_empty = slice.is_empty();
let first = slice.first(); // Some(&1)
let last = slice.last(); // Some(&5)
// Ricerca
let contains = slice.contains(&3); // true
let pos = slice.iter().position(|&x| x == 3); // Some(2)
// Suddivisione
let (left, right) = slice.split_at(2); // ([1, 2], [3, 4, 5])
// Windows e chunks
for window in slice.windows(2) {
println!("{:?}", window); // [1,2], [2,3], [3,4], [4,5]
}
for chunk in slice.chunks(2) {
println!("{:?}", chunk); // [1,2], [3,4], [5]
}
Tuple¶
Una tupla è una collezione eterogenea a dimensione fissa di valori che possono avere tipi diversi.
let tup: (i32, f64, u8) = (500, 6.4, 1);
// Destructuring
let (x, y, z) = tup;
// Accesso con indice
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
Caratteristiche delle Tuple¶
- Dimensione fissa nota a compile-time
- Tipi eterogenei - ogni elemento può avere un tipo diverso
- Accesso con
.0,.1,.2, ecc.
// Tuple come valori di ritorno
fn calculate(x: i32) -> (i32, i32, i32) {
(x + 1, x * 2, x * x)
}
let (a, b, c) = calculate(5);
// a = 6, b = 10, c = 25
// Tuple nidificate
let nested = ((1, 2), (3, 4));
let ((a, b), (c, d)) = nested;
Tuple con un elemento¶
Unit type ()
La tupla vuota () è chiamata unit type e rappresenta l'assenza di un valore significativo. È il tipo di ritorno di default delle funzioni che non restituiscono nulla.
Unit Type¶
Il tipo () (pronunciato "unit") è una tupla vuota che rappresenta l'assenza di valore.
let unit: () = ();
// Funzioni senza return esplicito restituiscono ()
fn print_hello() {
println!("Hello!");
} // Restituisce implicitamente ()
let result: () = print_hello();
Uso del Unit Type¶
// Equivalenti
fn do_something() -> () {
println!("doing");
}
fn do_something() {
println!("doing");
}
// Result con unit
fn may_fail() -> Result<(), String> {
if true {
Ok(())
} else {
Err(String::from("failed"))
}
}
Unit in pattern matching
Pointer (Puntatori Raw)¶
I puntatori raw *const T e *mut T sono puntatori non sicuri che bypassano le regole di borrowing di Rust.
let x = 5;
let raw_ptr: *const i32 = &x as *const i32;
let mut y = 10;
let mut_ptr: *mut i32 = &mut y as *mut i32;
Caratteristiche dei Puntatori Raw¶
- Non garantiscono che il puntatore sia valido
- Non rispettano le regole di borrowing
- Richiedono un blocco
unsafeper dereferenziarli - Possono essere null
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
// Dereferenziazione richiede unsafe
unsafe {
println!("r1 is: {}", *r1);
*r2 = 10;
println!("r2 is: {}", *r2);
}
Usa i puntatori raw solo quando necessario
I puntatori raw bypassano tutte le garanzie di sicurezza di Rust. Usali solo quando: - Interagisci con codice C tramite FFI - Implementi strutture dati unsafe di basso livello - Hai bisogno di prestazioni estreme in scenari specifici
Nella maggior parte dei casi, i riferimenti normali sono sufficienti e più sicuri.
Creare puntatori null¶
let null_ptr: *const i32 = std::ptr::null();
let null_mut_ptr: *mut i32 = std::ptr::null_mut();
// Verifica
if null_ptr.is_null() {
println!("Il puntatore è null!");
}
Reference (Riferimenti)¶
I riferimenti &T e &mut T sono il modo sicuro di Rust per prestare l'accesso a un valore senza prenderne ownership.
let x = 5;
let r: &i32 = &x; // Riferimento immutabile
let mut y = 10;
let mr: &mut i32 = &mut y; // Riferimento mutabile
*mr = 20;
Regole del Borrowing¶
Rust applica due regole fondamentali:
- Puoi avere uno qualsiasi di questi, ma non entrambi:
- Un riferimento mutabile (
&mut T) -
Qualsiasi numero di riferimenti immutabili (
&T) -
I riferimenti devono sempre essere validi (non dangling)
let mut s = String::from("hello");
let r1 = &s; // ✅ OK
let r2 = &s; // ✅ OK - multipli riferimenti immutabili
// let r3 = &mut s; // ❌ Errore! Non puoi avere &mut mentre esistono &
println!("{} {}", r1, r2);
let r4 = &mut s; // ✅ OK ora - r1 e r2 non sono più usati
r4.push_str(" world");
Riferimenti immutabili¶
fn calculate_length(s: &String) -> usize {
s.len()
} // s esce dallo scope, ma non dealloca perché non ha ownership
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("Length of '{}' is {}", s1, len); // s1 è ancora valido!
Riferimenti mutabili¶
fn append_world(s: &mut String) {
s.push_str(" world");
}
let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s); // "hello world"
Pattern idiomatico: accetta riferimenti
Lifetime nei riferimenti¶
I riferimenti hanno sempre un lifetime che garantisce che il dato puntato sia ancora valido:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
let string1 = String::from("long string");
let string2 = String::from("short");
let result = longest(string1.as_str(), string2.as_str());
Function Pointer¶
Il tipo fn rappresenta un puntatore a una funzione. A differenza delle closure, i function pointer sono tipi primitivi.
Passare funzioni come argomenti¶
fn apply_operation(x: i32, operation: fn(i32) -> i32) -> i32 {
operation(x)
}
fn double(x: i32) -> i32 {
x * 2
}
fn triple(x: i32) -> i32 {
x * 3
}
let result1 = apply_operation(5, double); // 10
let result2 = apply_operation(5, triple); // 15
Function pointer vs Closure¶
// Function pointer
let f: fn(i32) -> i32 = |x| x + 1;
// Closure con cattura
let y = 5;
let closure = |x| x + y; // Non può essere convertita in fn pointer
// Usare trait bound per accettare entrambi
fn call_with_10<F>(f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(10)
}
fn implementa Fn, FnMut e FnOnce
Un function pointer implementa automaticamente tutti e tre i trait delle closure, quindi può essere usato ovunque si accetti una closure.
Tabella Riepilogativa¶
Tipi Numerici¶
| Categoria | Tipi | Dimensione | Note |
|---|---|---|---|
| Interi con segno | i8, i16, i32, i64, i128, isize | 8-128 bit | Default: i32 |
| Interi senza segno | u8, u16, u32, u64, u128, usize | 8-128 bit | usize per indici |
| Floating-point | f32, f64 | 32-64 bit | Default: f64 |
Tipi Testuali e Logici¶
| Tipo | Dimensione | Descrizione |
|---|---|---|
| bool | 1 byte | true o false |
| char | 4 byte | Unicode scalar value |
| str | Variabile | String slice UTF-8 (DST) |
Tipi Composti¶
| Tipo | Sintassi | Dimensione | Allocazione |
|---|---|---|---|
| Array | [T; N] | Fissa | Stack |
| Slice | [T] | Dinamica (DST) | Vista su dati esistenti |
| Tuple | (T, U, ...) | Fissa | Stack |
| Unit | () | 0 | N/A |
Puntatori e Riferimenti¶
| Tipo | Sintassi | Sicurezza | Note |
|---|---|---|---|
| Reference immutabile | &T | Sicuro | Borrow checker |
| Reference mutabile | &mut T | Sicuro | Borrow checker |
| Raw pointer const | *const T | Unsafe | Nessuna garanzia |
| Raw pointer mut | *mut T | Unsafe | Nessuna garanzia |
| Function pointer | fn(T) -> U | Sicuro | Puntatore a funzione |
Best Practices¶
Scelta del tipo numerico¶
// ✅ Usa i32 per interi generici (default)
let count = 42;
// ✅ Usa usize per indici e dimensioni
let index: usize = 0;
let len = vec.len();
// ✅ Usa u8 per byte e dati binari
let byte: u8 = 0xFF;
// ✅ Usa f64 per floating-point (default)
let pi = 3.14159;
// ✅ Usa tipi più grandi solo quando necessario
let timestamp: i64 = 1634567890;
Gestione dell'overflow¶
// ✅ Usa metodi espliciti per gestire l'overflow
let result = x.checked_add(y).unwrap_or(0);
let clamped = value.saturating_sub(10);
// ❌ Evita di fare affidamento sul wrapping implicito
let result = x + y; // Panic in debug, wrapping in release
Preferisci riferimenti a ownership¶
// ✅ Accetta riferimenti quando non serve ownership
fn process(data: &[i32]) {
// ...
}
// ❌ Non prendere ownership inutilmente
fn process_owned(data: Vec<i32>) {
// ...
}
Usa slice invece di array o Vec¶
// ✅ Accetta slice per massima flessibilità
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// Funziona con array, Vec, e slice!
let arr = [1, 2, 3];
let vec = vec![1, 2, 3];
sum(&arr);
sum(&vec);
Evita puntatori raw quando possibile¶
// ✅ Usa riferimenti normali
let x = &value;
// ❌ Evita puntatori raw a meno che strettamente necessario
let x = &value as *const i32;
Conversioni tra Tipi¶
Casting con as¶
let x = 5u32;
let y = x as i32; // u32 -> i32
let z = y as f64; // i32 -> f64
let c = 'A';
let byte = c as u8; // char -> u8 (solo ASCII safe!)
Conversioni lossy
Il cast con as può perdere informazioni:
Metodi From e Into¶
// Conversioni sicure
let x: i32 = 5;
let y: i64 = i64::from(x); // i32 -> i64 sempre sicuro
let byte: u8 = 65;
let character: char = char::from(byte); // u8 -> char (se valido)
Metodi TryFrom e TryInto¶
use std::convert::TryFrom;
let x: i32 = 1000;
let y: Result<u8, _> = u8::try_from(x); // Err - fuori range
match y {
Ok(val) => println!("Successo: {}", val),
Err(e) => println!("Errore di conversione: {}", e),
}
Quando Usare Ogni Tipo¶
Usa i32/u32 quando¶
- Hai bisogno di un intero generico
- Il range è sufficiente per i tuoi dati
- Le prestazioni non sono critiche
Usa usize quando¶
- Indicizzi array o collection
- Lavori con dimensioni di memoria
- Calcoli offset o distanze tra puntatori
Usa f64 quando¶
- Hai bisogno di numeri in virgola mobile
- La precisione è importante
- Non hai vincoli di memoria stretti
Usa &str quando¶
- Hai stringhe immutabili
- Passi stringhe a funzioni (senza ownership)
- Lavori con letterali stringa
Usa slice (&[T]) quando¶
- Passi sequenze a funzioni
- Non hai bisogno di modificare la dimensione
- Vuoi massima flessibilità (array, Vec, slice)
Usa reference (&T, &mut T) quando¶
- Vuoi accesso temporaneo senza ownership
- Rispetti le regole del borrow checker
- Non hai bisogno di unsafe
Vedi Anche¶
- Vec
- Array dinamico - String - Stringa heap-allocated
- Option e Result - Gestione valori opzionali ed errori
- Collections - Altre strutture dati
- References and Borrowing - Approfondimento sul borrowing
Hai trovato errori o imprecisioni?
Aiutaci a migliorare questa traduzione! Apri una issue o proponi una modifica.