Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rc<T>, viittauslaskennan älykäs osoitin

Useimmissa tapauksissa omistajuus on selvä: tiedät tarkalleen, mikä muuttuja omistaa tietyn arvon. On kuitenkin tilanteita, joissa yhdellä arvolla voi olla useita omistajia. Graafitietorakenteissa esimerkiksi useat reunat voivat osoittaa samaan solmuun, ja kyseinen solmu on käsitteellisesti kaikkien siihen osoittavien reunojen omistama. Solmua ei pitäisi siivota, ellei siihen osoita yhtään reunaa ja sillä siten ei ole omistajia.

Usean omistajuuden täytyy ottaa käyttöön eksplisiittisesti Rustin tyypillä Rc<T>, joka on lyhenne sanasta reference counting (viittauslaskenta). Rc<T>-tyyppi seuraa viittausten määrää arvoon ja päättelee, onko arvo vielä käytössä. Jos arvoon ei ole yhtään viittausta, arvo voidaan siivota ilman, että viittaukset muuttuvat virheellisiksi.

Kuvittele Rc<T> perhehuoneen televisioksi. Kun yksi henkilö tulee katsomaan televisiota, hän kytkee sen päälle. Muut voivat tulla huoneeseen ja katsoa televisiota. Kun viimeinen henkilö poistuu huoneesta, hän sammuttaa television, koska sitä ei enää käytetä. Jos joku sammuttaisi television, vaikka muut vielä katsoisivat sitä, jäljellä olevat katsojat protestoisivat!

Käytämme Rc<T>-tyyppiä, kun haluamme varata dataa keolle ohjelman useille osille luettavaksi emmekä voi käännösaikana tietää, mikä osa lopettaa datan käytön viimeisenä. Jos tietäisimme, mikä osa lopettaa viimeisenä, voisimme tehdä siitä datan omistajan, ja käännösaikana voimaan tulevat tavalliset omistajuussäännöt riittäisivät.

Huomaa, että Rc<T> on tarkoitettu vain yksisäikeisiin skenaarioihin. Kun käsittelemme rinnakkaisuutta luvussa 16, käymme läpi, miten viittauslaskentaa tehdään monisäikeisissä ohjelmissa.

Datan jakaminen

Palataan cons-listan esimerkkiin listauksessa 15-5. Muistathan, että määrittelimme sen käyttäen Box<T>:tä. Tällä kertaa luomme kaksi listaa, jotka molemmat jakavat omistajuuden kolmannesta listasta. Käsitteellisesti tämä näyttää samankaltaiselta kuin kuvassa 15-3.

A linked list with the label 'a' pointing to three elements. The first element contains the integer 5 and points to the second element. Th
e second element contains the integer 10 and points to the third element. The third element contains the value 'Nil' that signifies the end of the l
ist; it does not point anywhere. A linked list with the label 'b' points to an element that contains the integer 3 and points to the first element o
f list 'a'. A linked list with the label 'c' points to an element that contains the integer 4 and also points to the first element of list 'a' so th
at the tails of lists 'b' and 'c' are both list 'a'.

Kuva 15-3: Kaksi listaa, b ja c, jakavat omistajuuden kolmannesta listasta, a

Luomme listan a, joka sisältää 5:n ja sitten 10:n. Sitten teemme kaksi listaa lisää: b, joka alkaa 3:lla, ja c, joka alkaa 4:llä. Sekä b- että c-listat jatkavat ensimmäiseen a-listaan, joka sisältää 5:n ja 10:n. Toisin sanoen molemmat listat jakavat ensimmäisen listan, joka sisältää 5:n ja 10:n.

Yritys toteuttaa tämä skenaario List-määrittelyllämme Box<T>:llä ei toimi, kuten listauksessa 15-17 näytetään.

Filename: src/main.rs
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-17/src/main.rs}}
Listing 15-17: Osoitus siihen, ettei meillä ole lupaa käyttää kahta Box<T>-listaa, jotka yrittävät jakaa omistajuuden kolmannesta listasta

Kun käännämme tämän koodin, saamme tämän virheen:

{{#include ../listings/ch15-smart-pointers/listing-15-17/output.txt}}

Cons-variantit omistavat pitämänsä datan, joten kun luomme b-listan, a siirretään b:hen ja b omistaa a:n. Sitten kun yritämme käyttää a:ta uudelleen luodessamme c:n, se ei ole sallittua, koska a on jo siirretty.

Voisimme muuttaa Cons-määrittelyä pitämään viittauksia sen sijaan, mutta silloin meidän täytyisi määrittää elinaikaparametrit. Määrittämällä elinaikaparametrit ilmoittaisimme, että jokainen listan elementti elää vähintään yhtä kauan kuin koko lista. Näin on listauksen 15-17 elementeissä ja listoissa, mutta ei kaikissa skenaarioissa.

Sen sijaan muutamme List-määrittelyämme käyttämään Rc<T>:tä Box<T>:n sijaan, kuten listauksessa 15-18. Jokainen Cons-variantti pitää nyt arvon ja Rc<T>:n, joka osoittaa List:iin. Kun luomme b:n, sen sijaan että ottaisimme a:n omistajuuden, kloonaamme Rc<List>:n, jota a pitää, mikä kasvattaa viittausten määrää yhdestä kahteen ja antaa a:n ja b:n jakaa omistajuuden kyseisen Rc<List>:n datassa. Kloonaamme myös a:n luodessamme c:n, mikä kasvattaa viittausten määrää kahdesta kolmeen. Joka kerta kun kutsumme Rc::clone:a, Rc<List>:n sisällä olevan datan viittauslaskuri kasvaa, eikä dataa siivota, ellei viittauksia ole nolla.

Filename: src/main.rs
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-18/src/main.rs}}
}
Listing 15-18: List-määrittely, joka käyttää Rc<T>:tä

Meidän täytyy lisätä use-lause tuomaan Rc<T> näkyvyysalueelle, koska se ei ole preludissa. main-funktiossa luomme listan, joka pitää 5:tä ja 10:tä, ja tallennamme sen uuteen Rc<List>:iin muuttujassa a. Sitten kun luomme b:n ja c:n, kutsumme Rc::clone-funktiota ja välitämme argumenttina viittauksen a:n Rc<List>:iin.

Olisimme voineet kutsua a.clone():a Rc::clone(&a):n sijaan, mutta Rustin käytäntö on käyttää tässä tapauksessa Rc::clone:a. Rc::clone:n toteutus ei tee syväkopiota kaikesta datasta kuten useimpien tyyppien clone-toteutukset. Rc::clone-kutsu kasvattaa vain viittauslaskuria, mikä ei vie paljon aikaa. Datan syväkopiot voivat viedä paljon aikaa. Käyttämällä Rc::clone:a viittauslaskentaan voimme visuaalisesti erottaa syväkopioivien kloonausten ja viittauslaskuria kasvattavien kloonausten välillä. Kun etsimme suorituskykyongelmia koodista, meidän tarvitsee huomioida vain syväkopioivat kloonaukset ja voimme jättää huomiotta Rc::clone-kutsut.

Kloonaus viittauslaskurin kasvattamiseksi

Muutetaan listauksen 15-18 työesimerkkiä niin, että näemme viittauslaskurin muuttuvan luodessamme ja pudottaessamme viittauksia a:n Rc<List>:iin.

Listauksessa 15-19 muutamme main:ia niin, että siinä on sisäinen näkyvyysalue listan c ympärillä; sitten voimme nähdä, miten viittauslaskuri muuttuu, kun c poistuu näkyvyysalueelta.

Filename: src/main.rs
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-19/src/main.rs:here}}
}
Listing 15-19: Viittauslaskurin tulostaminen

Jokaisessa ohjelman kohdassa, jossa viittauslaskuri muuttuu, tulostamme viittauslaskurin kutsumalla Rc::strong_count-funktiota. Funktio on nimetty strong_count:ksi count:n sijaan, koska Rc<T>-tyypillä on myös weak_count; näemme, mihin weak_count:ia käytetään kohdassa „Viittauskiertojen estäminen Weak<T>:n avulla”.

Tämä koodi tulostaa seuraavaa:

{{#include ../listings/ch15-smart-pointers/listing-15-19/output.txt}}

Näemme, että a:n Rc<List>:llä on aluksi viittauslaskuri 1; sitten joka kerta kun kutsumme clone:a, laskuri kasvaa yhdellä. Kun c poistuu näkyvyysalueelta, laskuri pienenee yhdellä. Meidän ei tarvitse kutsua funktiota viittauslaskurin pienentämiseksi kuten meidän täytyy kutsua Rc::clone:a viittauslaskurin kasvattamiseksi: Drop-traitin toteutus pienentää viittauslaskuria automaattisesti, kun Rc<T>-arvo poistuu näkyvyysalueelta.

Mitä emme näe tässä esimerkissä, on se, että kun b ja sitten a poistuvat näkyvyysalueelta main:in lopussa, laskuri on 0 ja Rc<List> siivotaan kokonaan. Rc<T>:n avulla yhdellä arvolla voi olla useita omistajia, ja laskuri varmistaa, että arvo pysyy voimassa niin kauan kuin yksikään omistajista on olemassa.

Muuttumattomien viittausten kautta Rc<T> antaa sinun jakaa dataa ohjelman useiden osien välillä vain lukemista varten. Jos Rc<T> sallisi myös useita muuttuvia viittauksia, saatat rikkoa yhden luvussa 4 käsitellyistä lainausperiaatteista: useat muuttuvat lainaukset samaan paikkaan voivat aiheuttaa datakilpailuja ja epäjohdonmukaisuuksia. Datan muuttaminen on kuitenkin hyvin hyödyllistä! Seuraavassa osiossa käsittelemme sisäisen muuttuvuuden mallia ja RefCell<T>-tyyppiä, jota voit käyttää yhdessä Rc<T>:n kanssa tämän muuttumattomuusrajoituksen kanssa työskentelyyn.