#Klooienmetcomputers

Borrowing en mutability

Arnout van Kempen over rommelen in een digitale wereld.

De vorige keer zagen we hoe we een waarde van een variabele aan een functie konden doorgeven, zonder ownership door te geven. Dat is handig, want daardoor verliest de originele variabele het ownership niet, raakt niet uit scope en als de variabele in de functie bij het einde van de functie uit scope raakt, wordt de referentie gedropped en niet de waarde waar het om gaat. Het proces van borrowing werkt door een referentie door te geven en dat doe je door zowel voor de variabele als voor het type een & te plaatsen. Dus je kreeg zoiets als dit:

let a = 1;

let b = een_functie(&a);

fn een_functie(arg : &i32)->i32;

Maar daarbij heb ik een niet onbelangrijk detail genegeerd. Op deze manier kunnen we wel een waarde aan een functie doorgeven, en die functie kan die waarde dan gebruiken zonder dat het problemen met de ownership-regels geeft, maar de waarde is niet mutable en de borrow mag dus ook niets aan de waarde veranderen. 

Wat nu als een functie wel iets aan een waarde moet veranderen? We willen bijvoorbeeld een increment-functie gebruiken:

fn main() {
     let a = 1;
     inc(&a);
}

fn inc(arg : &i32) {
     arg +=1;
}

Hier gaat een aantal zaken mis. Om te beginnen, het type van arg is geen integer, maar een referentie naar een integer (een pointer dus). Als je de waarde waar arg naar verwijst met 1 wil verhogen, dan moet je die referentie wel de-referencen. Net als in C is de tegenhanger van & in Rust ook een *. Dus we moeten niet arg met 1 verhogen, maar *arg. De dereferentie van de referentie van a is immers weer a, maar nu via de borrow-route en zonder ownership over te nemen.

Lastiger als je nog niet gewend bent aan het feit dat variabelen in Rust standaard niet gewijzigd kunnen worden, is het feit dat je overal moet aangeven dat we hier wel met een mutable te maken hebben. In de variabele, de referentie, de typering van het argument van de functie. De werkende versie van dit programma wordt dus:

fn main() {
     let mut a : i32 = 1;
     inc(&mut a);
}

fn inc(arg : &mut i32) {
     *arg += 1;
}

Overigens, om het helemaal te snappen, probeer eens te bedenken waarom dit ook werkt:

fn main() {
     let mut a = 1;
     a = inc(&a);
}

fn inc(arg: &i32) -> i32 {
     *arg +1
}

Wat nu als je meerdere mutable referenties naar dezelfde waarde maakt? Hoe voorkom je dan dat de ene mutatie de andere niet in de weg zit? Het antwoord is simpel: dat voorkom je niet. En dan gaan er dus onherroepelijk een keer ongelukken gebeuren. De oplossing van Rust is ook hier weer vrij simpel, maar wel met consequenties om rekening mee te houden: de borrowing, of referentie regels.

1. Je kan of één mutable referentie naar een waarde in scope hebben, of zoveel niet mutable referenties als je wil, maar naast één mutable referentie kan nooit nog een andere referentie in scope zijn.

2. Referenties moeten altijd geldig zijn en blijven.

Ingewikkeld? Misschien wel. In C heb je hiervan geen last, de compiler laat je ze slordig programmeren als je wil, maar de prijs die je betaalt is dat je code zo betrouwbaar is als je zelf netjes bent. In Python heb je er ook geen last van, de interpreter neemt geheugenbeheer volledig van je over, maar de prijs die je betaalt is dat de prestaties dramatisch slechter zijn.

En zo ingewikkeld is het nu ook weer niet. De ownership- en borrowing-rules zijn redelijk voor de hand liggend in de praktijk, het is vooral de strikte handhaving door de compiler waar je aan moet wennen. En met bacon in de achtergrond valt ook dat nog wel mee.

Wie mee wil doen met #klooienmetcomputers kan dat doen via GitHub. Maak een account op github.com en zoek naar Abmvk/kmc. Het account Abmvk volgen kan ook. Lezers zijn vrij te gebruiken wat ze willen en om zelf zaken toe te voegen of aan te passen, vragen te stellen of commentaar te leveren.

Gerelateerd

reacties

Reageer op dit artikel

Spelregels debat

    Aanmelden nieuwsbrief

    Ontvang elke werkdag (maandag t/m vrijdag) de laatste nieuwsberichten, opinies en artikelen in uw mailbox.

    Bent u NBA-lid? Dan kunt u zich ook aanmelden via uw ledenprofiel op MijnNBA.nl.