Az előző leckében megtanultad, hogy a let és a mut hogyan viselkedik, és mit jelent a shadowing. Most egy lépéssel tovább megyünk: megnézzük, milyen típusú értékeket tehetünk egyáltalán egy változóba. A Rust erősen és statikusan típusos nyelv, ami azt jelenti, hogy minden érték típusa fordítási időben ismert és rögzített — ez a szigorúság az, ami később (amikor eljutunk az ownershiphez) segít elkerülni egész hibaosztályokat. Kezdjük a legegyszerűbb, úgynevezett skalár típusokkal, majd nézzük meg, hogyan tudunk több értéket egy csomagba rendezni tuple-ökkel és array-ekkel, végül tisztázzuk a szöveges típusok kettősségét.

Skalár típusok: integer, float, bool, char

A skalár típus egyetlen értéket ír le. A Rustban négy fő csoportjuk van:

  • egész számok (integer): i8, i16, i32, i64, i128, isize (előjeles), valamint u8, u16, u32, u64, u128, usize (előjel nélküli). Alapértelmezésben a fordító i32-t választ, ha nem adsz meg típust.
  • lebegőpontos számok (float): f32 és f64. Alapértelmezés f64.
  • logikai érték (bool): true vagy false.
  • karakter (char): egyetlen Unicode-karakter, mindig 4 bájton tárolva — ezért a char nem csak ASCII-t, hanem például emojit vagy ékezetes betűt is tud tárolni.
fn main() {
    let age: u8 = 32;
    let temperature: f64 = -3.5;
    let is_rust_fun: bool = true;
    let initial: char = 'R';

    println!("Kor: {}, hőmérséklet: {}, szereted a Rust-ot: {}, kezdőbetű: {}", age, temperature, is_rust_fun, initial);
}

A típusok mérete korlátozott, és a fordító ezt fordítási időben ellenőrzi is, ha a literál nyilvánvalóan nem fér bele:

fn main() {
    let small: u8 = 300; // u8 csak 0 és 255 közötti értéket bír el
    println!("{}", small);
}
error: literal out of range for `u8`
  |
  = note: the literal `300` does not fit into the type `u8` whose range is `0..=255`
Tipp

Ha nem vagy biztos egy típus határaiban, a rustc fordító hibaüzenete általában pontosan megmondja a megengedett tartományt — érdemes elolvasni, nem csak átugrani rajta.

Tuple: rögzített hosszú, kevert típusú csoport

A tuple több értéket fog össze egyetlen összetett típusba, és az egyes elemek típusa eltérhet egymástól. A tuple mérete fix: annyi elemet tartalmaz, amennyivel létrehoztad, se többet, se kevesebbet.

fn main() {
    let point: (f64, f64, &str) = (3.5, -2.1, "A pont");

    println!("Az X koordináta: {}", point.0);
    println!("Az Y koordináta: {}", point.1);
    println!("Címke: {}", point.2);

    // az elemek egyszerre is kibonthatók:
    let (x, y, label) = point;
    println!("{}: ({}, {})", label, x, y);
}

Az egyes elemeket ponttal és sorszámmal éred el (point.0, point.1, ...), nullától indexelve. Ha egy tuple-t mut-tal hozol létre, az elemei módosíthatók:

fn main() {
    let mut pair: (i32, i32) = (1, 2);
    pair.0 = 10;
    println!("{}, {}", pair.0, pair.1);
}

Array: fix méretű, egységes típusú tömb

Az array a tuple-lel ellentétben csak egyfajta típusú elemet tartalmazhat, de méretét is fixen rögzíti a típusa: [T; N], ahol T az elemtípus, N pedig az elemszám.

fn main() {
    let scores: [i32; 5] = [10, 20, 30, 40, 50];

    println!("Első elem: {}", scores[0]);
    println!("Utolsó elem: {}", scores[4]);
    println!("Az array mérete: {}", scores.len());
}

Az elemeket szögletes zárójellel és indexszel éred el, a len() pedig megadja az elemek számát. Mivel a méret a típus része, egy [i32; 5] sosem lehet 6 elemű — ezt a fordító garantálja. Ha a típusok nem egyeznek az elemek között, a fordító azonnal jelez:

fn main() {
    let numbers = [1, "kettő", 3];
    println!("{}", numbers[0]);
}
error[E0308]: mismatched types
  |
  |     let numbers = [1, "kettő", 3];
  |                       ^^^^^^^ expected integer, found `&str`

A mut kulcsszó array esetén is ugyanúgy működik, mint eddig megismerted: mut nélkül az elemek nem módosíthatók, mut-tal viszont igen.

fn main() {
    let mut scores: [i32; 3] = [10, 20, 30];
    scores[1] = 99;
    println!("Módosított második elem: {}", scores[1]);
}

String vs &str: két szöveges típus, két szerep

A Rustban két gyakran használt szöveges típussal találkozol: a String és a &str. Elsőre zavaró lehet, hogy miért kettő is van, de a szerepük valójában eltérő.

A sztring literál (a "..." idézőjelek közé írt szöveg) mindig &str típusú: ez egy fix, csak olvasható szövegdarab, amely már valahol létezik a memóriában. A String ezzel szemben egy saját, növelhető szövegpuffer, amit futás közben bővíthetsz, módosíthatsz.

fn main() {
    let greeting: &str = "Szia, Rust!"; // string literál, típusa mindig &str
    let mut name: String = String::from("Anna");

    name.push_str(" Kovács"); // csak String bővíthető így

    println!("{}", greeting);
    println!("{}", name);
    println!("A név hossza: {}", name.len());
}

A &str nem bővíthető, mert nem a saját memóriáját birtokolja, csak "belenéz" egy meglévő szövegbe:

fn main() {
    let text: &str = "Rust";
    text.push_str(" lang"); // &str-nek nincs push_str metódusa
    println!("{}", text);
}
error[E0599]: no method named `push_str` found for reference `&str` in the current scope
Megjegyzés

Előretekintés: a &str neve nem véletlenül tartalmaz egy & jelet — ez arra utal, hogy ez egy nem tulajdonló, csak rápillantó típus. A & szimbólum pontos jelentésével (referenciák, borrowing) egy későbbi leckében foglalkozunk részletesen, most elég annyit tudni, hogy a String a szöveg tulajdonosa, a &str pedig csak egy nézet rá.

Ökölszabályként: ha egy szöveget építeni, bővíteni akarsz, String-et használj; ha csak olvasod vagy átadod egy meglévő szöveget, gyakran elég a &str is.

Próbáld ki!

Hozz létre egy mut nélküli tuple-t három elemmel: egy &str névvel, egy u8 korral és egy bool értékkel (pl. hogy tanult-e már Rustot korábban). Írd ki mindhárom elemet külön println!-nel a .0, .1, .2 szintaxissal. Ezután hozz létre egy [f64; 4] array-t négy tetszőleges hőmérséklet-adattal, írd ki az elsőt és az utolsót, majd mut hozzáadásával módosítsd az array második elemét, és írd ki újra a teljes eredményt.

Önellenőrzés

1. Miért ad fordítási hibát a let small: u8 = 300; sor, miközben a 300 egy teljesen érvényes egész szám?

Megoldás Mert a `u8` típus csak 0 és 255 közötti értékeket tud ábrázolni (8 bites, előjel nélküli). A `300` nem fér bele ebbe a tartományba, és ezt a fordító már fordítási időben észreveszi, mivel a literál értéke és a célváltozó típusa is ismert.

2. Mi a fő különbség a tuple és az array között?

Megoldás A tuple elemei eltérő típusúak lehetnek, és `.0`, `.1` stb. formában érhetők el, míg az array minden eleme azonos típusú, és indexeléssel (`tomb[0]`) érhető el. Az array típusa a méretét is tartalmazza (`[T; N]`), a tuple típusa pedig az egyes elemek típusainak sorozata (`(T1, T2, ...)`).

3. Mikor válassz String-et, és mikor elég egy &str?

Megoldás `String`-et akkor használj, ha a szöveget futás közben szeretnéd építeni, bővíteni vagy módosítani (ehhez a saját, növelhető memóriaterületre van szükség). `&str`-t akkor, ha csak egy meglévő, változatlan szövegre van szükséged — például egy string literálra, vagy amikor csak olvasod, átadod a szöveget anélkül, hogy tulajdonosává válnál.

Összefoglaló

  • A Rust skalár típusai (integer, float, bool, char) fordítási időben rögzített méretűek és tartományúak — a fordító már a cargo build/cargo check során jelzi, ha egy literál nem fér bele egy típusba.
  • A tuple (T1, T2, ...) eltérő típusú elemeket fűz össze fix hosszban, elérésük .0, .1, ... szintaxissal történik.
  • Az array [T; N] azonos típusú elemeket tárol fix, a típusba kódolt méretben, indexeléssel elérve.
  • A String a szöveg tulajdonosa és bővíthető, a &str egy nem tulajdonló, csak olvasható nézet egy szövegre — a mögöttük álló & jelentését hamarosan, az ownership-témakörnél tisztázzuk részletesen.
  • A mut kulcsszó ugyanúgy vezérli a módosíthatóságot ezeknél az összetett típusoknál is, mint amit az előző leckében a skalár változóknál láttál.

A következő leckében megtanulod, hogyan irányíthatod a program futását: jön az if/else, a match, valamint a loop és társai — innentől már nem csak kiszámolni, hanem el is dönteni tudsz dolgokat a kódodban.

További olvasmányok: