Cél és előfeltétel
Ebben a leckében több értéket tárolsz és dolgozol fel anélkül, hogy minden függvénynek átadnád a teljes gyűjtemény tulajdonjogát. A végére különbséget tudsz tenni egy új eredményvektor készítése és a meglévő adatok helyben módosítása között. Az üres bemenetet és a legszélső egész értékeket is tudatosan kezeljük.
Előfeltétel A Rust alapjai, különösen a kölcsönzés és referenciák, továbbá az előző Option és Result gyakorlat. A példák egyszerű i32 értékeket használnak, ezért a másolható szám és a kölcsönzött referencia különbségére is figyelhetsz. Minden Rust-blokk önálló program egy edition 2024 projektben. A szándékosan hibás blokk kivételével futtasd őket a cargo run paranccsal.
A Vec tárol, a slice hozzáférést ad
A Vec<T> az elemeket birtokló, változó hosszúságú gyűjtemény. Új elemet push segítségével adhatsz hozzá; a művelethez a vektornak módosíthatónak kell lennie. Az elemek sorrendben követik egymást, az első index nulla. A hossz azt mutatja meg, hány elem létezik, nem azt, mennyi helyet foglalt előre a vektor.
Ha egy függvénynek csak olvasnia kell az elemeket, általában elegendő egy &[T] slice. Ez kölcsönzött rálátás egy összefüggő elemsorozatra, nem új gyűjtemény. Kaphatod vektorból, tömbből vagy azok egy részéből. A függvény így nem követel meg szükségtelenül Vec-et, és nem másolja le az elemeket. A módosítható párja, a &mut [T], az elérhető elemek átírására ad jogot, új elem hozzáadására nem.
fn main() {
let mut meresek = vec![12, 9, 15];
let elso = meresek.first().copied();
meresek.push(18);
assert_eq!(elso, Some(12));
assert_eq!(&meresek[1..3], &[9, 15]);
assert_eq!(meresek.get(20), None);
assert_eq!(meresek.len(), 4);
}
A first és a get Option értékkel jelzi a hiányt. Ezért üres vagy rövidebb bemenetnél nem kell érvénytelen indexeléssel kockáztatni. A copied itt a hivatkozott i32 számot másolja ki az Option belsejéből. Az elso már nem az elemre mutató referencia, ezért a későbbi push alatt nincs életben tartandó kölcsönzés.
Miért ütközhet a push egy korábbi kölcsönzéssel?
A vektor bővítése szükségessé teheti új memóriaterület foglalását. Egy korábbi elemre mutató referencia ekkor nem maradhatna a régi címhez kötve. A kölcsönzési szabályok megakadályozzák, hogy az alábbi program ilyen helyzetbe kerüljön. Ez szándékosan nem fordítható példa, a várt diagnosztika E0502:
fn main() {
let mut adatok = vec![10, 20];
let elso = &adatok[0];
adatok.push(30);
println!("{elso}");
}
Ne a referencia klónozásával próbáld elfedni a kérdést. Gondold át, meddig kell a kölcsönzés, és valóban referenciát vagy önálló értéket szeretnél-e megtartani. Az előző működő példában egy másolható számra volt szükségünk, ezért a másolás világos döntés. Másik lehetőség a kölcsönzött érték utolsó felhasználását a módosítás elé helyezni. A puszta előfoglalás önmagában nem írja felül a kölcsönzési szabályokat.
A slice létrehozásánál a határokat is ellenőrizni kell. Az egytől háromig tartó jelölés az elsőként megadott indexet tartalmazza, a végindexet már nem: ezért két elemet jelöl ki. Az üres tartomány önmagában szabályos, de a létező adatokon túlnyúló tartomány nem az. A feladatokban ismert hosszúságú adatokkal szeletelünk; kívülről érkező indexeknél a get megfelelő használata adhat Option eredményt pánik helyett. Érdemes a bemenet méretét a függvény szerződésének részévé tenni: az üres gyűjteményt elfogadjuk, míg egy konkrét helyre mutató kérésnél külön kezeljük a hiányt. A hossz és az érvényes indexek kapcsolatát mindig a művelet előtt gondold végig.
1. feladat – Maximum üres bemenet mellett is
Írj maximum függvényt, amely &[i32] bemenetből a legnagyobb számot adja vissza. Üres elemsorozatnál legyen None. Ne indulj nulláról, mert csak negatív számok között a nulla téves eredmény lenne. A függvény fogadjon vektort és tömböt is, és egyetlen elemnél is működjön. Az összehasonlítás nem igényel számtani műveletet.
Megoldás
fn maximum(adatok: &[i32]) -> Option<i32> {
let mut legnagyobb = *adatok.first()?;
for &ertek in &adatok[1..] {
if ertek > legnagyobb {
legnagyobb = ertek;
}
}
Some(legnagyobb)
}
fn main() {
let vektor = vec![-8, -3, -5];
assert_eq!(maximum(&vektor), Some(-3));
assert_eq!(maximum(&[7]), Some(7));
assert_eq!(maximum(&[]), None);
assert_eq!(maximum(&[i32::MIN, i32::MAX]), Some(i32::MAX));
assert_eq!(vektor, vec![-8, -3, -5]);
}
Az első elem hiányakor a ? None értékkel visszatér, ezért az utána következő slice kezdete már biztosan létezik. Egyelemű bemenetnél a maradék üres, a ciklus egyszer sem fut. A for &ertek a kölcsönzött egészből olvas ki másolható értéket; nem mozgatja ki a gyűjteményt.
2. feladat – Új gyűjtemény készítése
Készíts pozitivak függvényt, amely új Vec-be gyűjti a szigorúan pozitív számokat. Az eredeti sorrend és az ismétlődések maradjanak meg. A nulla ne kerüljön az eredménybe, az eredeti bemenet pedig ne változzon. Vizsgáld az üres és a kizárólag nem pozitív adatokat is. Most egyszerű ciklussal dolgozz; az iterátoros változatot későbbi leckében készítjük el.
Megoldás
fn pozitivak(adatok: &[i32]) -> Vec<i32> {
let mut eredmeny = Vec::new();
for &ertek in adatok {
if ertek > 0 {
eredmeny.push(ertek);
}
}
eredmeny
}
fn main() {
let eredeti = vec![3, -1, 0, 3, 2];
assert_eq!(pozitivak(&eredeti), vec![3, 3, 2]);
assert_eq!(eredeti, vec![3, -1, 0, 3, 2]);
assert_eq!(pozitivak(&[]), Vec::<i32>::new());
assert_eq!(pozitivak(&[-2, 0]), Vec::<i32>::new());
assert_eq!(pozitivak(&[i32::MAX]), vec![i32::MAX]);
}
A visszaadott vektor a saját elemeit birtokolja. Külön gyűjtemény kell, mert az eredmény hossza más lehet, mint a bemeneté. Az i32 másolható, így nincs szükség a teljes bemenet klónozására. Ha az eredmény később módosul, az eredeti számok attól nem változnak meg.
3. feladat – Módosítás egy kölcsönzött tartományban
Írj nullazd_negativakat függvényt &mut [i32] paraméterrel. A negatív számokat cserélje nullára, a többi érték és az elemszám maradjon meg. Ezután csak egy vektor középső részére hívd meg, és bizonyítsd, hogy a tartományon kívüli elemek érintetlenek. Üres bemenet és a legkisebb i32 érték se okozzon hibát.
Megoldás
fn nullazd_negativakat(adatok: &mut [i32]) {
for ertek in adatok {
if *ertek < 0 {
*ertek = 0;
}
}
}
fn main() {
let mut adatok = vec![-9, -2, 3, -4];
nullazd_negativakat(&mut adatok[1..3]);
assert_eq!(adatok, vec![-9, 0, 3, -4]);
assert_eq!(adatok.len(), 4);
let mut hatarertekek = [i32::MIN, i32::MAX];
nullazd_negativakat(&mut hatarertekek);
assert_eq!(hatarertekek, [0, i32::MAX]);
let mut ures: Vec<i32> = Vec::new();
nullazd_negativakat(&mut ures);
assert!(ures.is_empty());
}
A *ertek a referencián keresztül elért számot írja át. Nem előjelet fordítunk, hanem nullát rendelünk hozzá, ezért a legkisebb egész érték kezelése sem igényel túlcsorduló műveletet. A függvény csak a neki átadott slice elemeit látja: a körülötte található adatokhoz nem kap hozzáférést.
Elfogadási feltételek
- A maximum üres adatokra
None, csupa negatív számra a tényleges legnagyobb érték; vektor és tömb egyaránt használható bemenetként. - A pozitív értékek gyűjtése megőrzi a sorrendet és az ismétléseket, az eredeti adatokat változatlanul hagyja.
- A helyben módosító függvény kizárólag a kijelölt tartományt írja át, az üres slice és az
i32::MINesetén is helyesen működik.
Összefoglaló és továbblépés
A függvény paramétere sokat elárul a megengedett műveletekről. A &[T] olvasási hozzáférést kér, a &mut [T] meglévő elemek módosítását engedi, a visszaadott Vec pedig új, saját gyűjteményt jelent. Ezt a különbséget érdemes már a kódolás előtt eldönteni. Következőként a String és Unicode szövegkezelés megmutatja, miért kíván külön figyelmet, amikor az elemek helyett szövegrészeket szeretnél kölcsönözni.