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

  1. A trim-megoldás kölcsönöz, és az eredeti szöveget nem írja át.
  2. Az ASCII-függvény változatlan ága Borrowed, a nagybetűs ága Owned, az ékezetes nagybetűt megtartja.
  3. 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.