Cél és előfeltétel
A másolás elkerülésének első lépése nem egy különleges pointer, hanem a tulajdonjogi igény pontos megfogalmazása. Ebben a leckében ugyanaz a függvény néha kölcsönzött szövegszeletet, máskor saját Stringet ad vissza. Előfeltétel a szövegkezelési gyakorlat és az allokációk megértése. A Cow rövidítése a másolást szükség esetére halasztó viselkedésre utal; itt a str változatát használjuk.
A Cow két változata Borrowed és Owned. Az első a bemenetre hivatkozik, a második saját adatot birtokol. A felhasználó mindkettőt olvashatja szövegként, de az élettartamokat továbbra is tiszteletben kell tartania. A Borrowed eredmény nem tehető hosszabb életűvé attól, hogy egy enumba került. Ha a hívó a bemenet elengedése után is tárolná az eredményt, saját tulajdonra van szüksége.
Mikor szükséges valóban új szöveg?
A szöveg megváltozott jelentése nem mindig kíván új bájtokat. A trim például egy rövidebb szeletet adhat vissza a meglévő bemenetből. Ezzel szemben a belső betűk átírásához általában saját módosítható puffer kell. A döntést tehát nem pusztán egy „változott-e?” jelzőre építjük, hanem arra, hogy a kívánt eredmény kifejezhető-e a meglévő adat megfelelő nézeteként.
A Cow nem garantál adott memóriaméretet, enumelrendezést vagy stabil Rust ABI-t. Nem kezelt megosztott referencia-számláló, és nem tesz minden műveletet allokációmentessé. A példák a nyilvános változatokat és a látható tartalmat vizsgálják. Egy teljes alkalmazás foglalásszámát ebből nem lehet megállapítani: a környező gyűjtemények, formázások és tulajdonossá alakítások továbbra is foglalhatnak.
1. feladat: tisztíts szelettel, ne másolattal
Írj függvényt, amely eltávolítja a szélső whitespace karaktereket, és Borrowed eredményt ad. Ellenőrizd a már tiszta, a szóközös és a kizárólag whitespace karakterekből álló bemenetet. Ne keverd a rövidebb nézet készítését a bájtok helyben módosításával.
Megoldás
use std::borrow::Cow;
fn clean(text: &str) -> Cow<'_, str> {
Cow::Borrowed(text.trim())
}
fn main() {
let input = String::from(" árvíz ");
let result = clean(&input);
assert_eq!(result, "árvíz");
assert!(matches!(&result, Cow::Borrowed(_)));
assert!(matches!(clean("kész"), Cow::Borrowed(_)));
assert_eq!(clean(" \n\t"), "");
assert_eq!(input, " árvíz ");
}
A bemenet megmarad, a visszaadott nézet csak szűkebb területre mutat. Az üres eredmény is lehet kölcsönzött: az üresség nem tulajdonjogi kategória. A függvény önmagában nem igényel Cow-t, mert minden esetben szeletet adhatna; itt a típus viselkedését tanuljuk, mielőtt a következő feladatban valóban szükségessé válik a kétféle visszatérés.
2. feladat: másolj csak az ASCII-nagybetűs ágon
Készíts ASCII-kisbetűsítő függvényt. Ha nincs ASCII-nagybetű, kölcsönözz; ellenkező esetben készíts saját eredményt. Az ékezetes betűk kisbetűsítését most ne ígérd. Vizsgáld meg a változatot a tartalom mellett, mert egy mindig Stringet gyártó megoldás a tartalmi teszteken önmagában átmenne.
Megoldás
use std::borrow::Cow;
fn ascii_lower(text: &str) -> Cow<'_, str> {
if text.bytes().any(|b| b.is_ascii_uppercase()) {
Cow::Owned(text.to_ascii_lowercase())
} else {
Cow::Borrowed(text)
}
}
fn main() {
let same = ascii_lower("árvíz");
let changed = ascii_lower("RUST árvíz");
assert_eq!(same, "árvíz");
assert!(matches!(&same, Cow::Borrowed(_)));
assert_eq!(changed, "rust árvíz");
assert!(matches!(&changed, Cow::Owned(_)));
assert_eq!(ascii_lower("Á"), "Á");
}
Az ASCII-szerződés világos korlát: nem keverjük össze a nemzetközi szövegfeldolgozást az egyszerű bájtértékek átírásával. Az Owned ág létezése nem teszi feleslegessé a kölcsönzött eredmény élettartamának követését. A közös visszatérési típus azt fejezi ki, hogy a hívónak mindkét esetet el kell tudnia fogadni ugyanazon aláírás mellett.
3. feladat: módosíts és alakíts tulajdonossá
Adj felkiáltójelet egy Cow szöveghez. A kölcsönzött bemenet maradjon változatlan, az eredmény viszont legyen saját tulajdonú. Ezután próbáld ki a már eleve Owned értéket is. Nem pointeregyezést vizsgálunk, hanem azt, hogy a módosítás és a tulajdonjogi határ a dokumentált módon működik.
Megoldás
use std::borrow::Cow;
fn emphasize(mut text: Cow<'_, str>) -> Cow<'_, str> {
text.to_mut().push('!');
text
}
fn main() {
let original = String::from("kész");
let result = emphasize(Cow::Borrowed(&original));
assert_eq!(result, "kész!");
assert!(matches!(&result, Cow::Owned(_)));
assert_eq!(original, "kész");
let owned: Cow<'_, str> = Cow::Owned(String::from("Rust"));
assert_eq!(emphasize(owned).into_owned(), "Rust!");
}
A to_mut a kölcsönzött változatnál szükség szerint saját adatot készít. Az into_owned kölcsönzött értéknél másol, saját értéknél átadhatja a már meglévő tulajdont. Emiatt egy végül minden esetben tulajdonossá alakított feldolgozási láncban a kezdeti kölcsönzés előnye eltűnhet. A teljes láncot kell értékelned, nem egyetlen függvény látványos aláírását.
A felület kiválasztása is teljesítménydöntés
Ha az eredmény mindig csak nézet, egyszerű &str is elegendő. Ha minden eredményt tartósan tárolni kell a forrás nélkül, a String egyszerűbb lehet. Cow akkor segít, amikor valóban értelmes mindkét életút. Egy bonyolultabb típus nem önmagában jobb: a hívók használati mintájához kell illeszkednie. Ellenőrizd, hol történik a tulajdonosváltás, és ki viseli annak költségét.
A felület élettartamát egy kis rajzzal is ellenőrizheted: ki hozza létre a bemeneti Stringet, ki tartja életben az eredmény olvasása alatt, és hol történik az utolsó használat? Ha egy tárolóba gyűjtesz kölcsönzött eredményeket, az összes érintett forrásnak életben kell maradnia. A Cow nem teszi önhivatkozóvá vagy mozgathatatlanná a forrásodat automatikusan; egyszerűen a meglévő kölcsönzési szabályokat követi.
A variánsra vonatkozó teszt szándékosan egy konkrét felületi ígéretet véd. Ha később úgy döntesz, hogy a függvény mindig saját szöveget ad, a tartalom továbbra is helyes lehet, de a megállapodás megváltozik. Ezt ne titkos optimalizálásként kezeld: a hívó kód tárolási döntéseire és a költségmodellre is hatással van. Különösen fontos ez feldolgozási láncokban, ahol egyetlen észrevétlen into_owned minden korábbi kölcsönzést másolattá változtathat.
Elfogadási feltételek
- A trim-megoldás kölcsönöz, és az eredeti szöveget nem írja át.
- Az ASCII-függvény változatlan ága Borrowed, a nagybetűs ága Owned, az ékezetes nagybetűt megtartja.
- A to_mut módosítása után a bemeneti String változatlan, a visszakapott szöveg pedig saját tulajdonú.
Összefoglaló és továbblépés
A kölcsönzés megtakaríthat egy konkrét másolatot, de az eredmény életét a forráshoz köti. A következő adatelrendezési lecke azt vizsgálja, hogyan változik a hozzáférési minta, amikor sok ilyen adatot együtt tárolunk.