#Klooienmetcomputers

Ownership en strings

Arnout van Kempen over rommelen in een digitale wereld.

Nu vim en bacon klaar staan om ons te helpen, duiken we terug in Rust en met name in ownership. Ik adviseer dringend om de broncode die ik op GitHub heb gezet over te nemen in een cargo project. Bijvoorbeeld met

cargo new strings
cp <<waar je de broncode hebt>> /src/main.rs

en dan bacon mee te laten lopen. Iedere keer dat je iets aan de code aanpast, doe je in vim een :w en je ziet in bacon direct wat er mis is met de broncode. De bedoeling is dat je van alles mis laat gaan, om zo te begrijpen hoe Rust met variabelen omgaat.

Om te beginnen is het van belang te begrijpen hoe Rust twee soorten strings kent, met ieder een eigen type. De exacte betekenis van die typen komt nog wel, voor dit moment is van belang te begrijpen dat een string-literal, dat is een tekenreeks die niet zal wijzigen, het type &str heeft, ook wel een string-slice genoemd. En een tekenreeks die wel kan wijzigen heeft type String. Een variabele van type &str is niet de eigenaar van de slice, een variabele van het type String is wel de eigenaar.

In de broncode op GitHub heb ik alle foutieve code met // tot commentaar gemaakt, dus de code zoals die er nu staat compileert. Het is interessant te zien wat er gebeurt als je die // weghaalt. Je krijgt soms errors, soms warnings, maar je zal in bacon, als je de optie c hebt aangezet, altijd goede uitleg krijgen wat er mis gaat.

Iets wat in de broncode niet wordt uitgelegd, maar wat wel interessant is, komt uit het concept van “object georiënteerd programmeren”, of OOP: bij een type kunnen functies of methoden horen. Zo heeft het type String onder andere de functie push_str(arg)waarbij arg van het type &str is. Zo zie je in de broncode dat simpelweg optellen van strings in Rust niet werkt, maar dat je wel gebruik kan maken van deze methode, die via een punt aan de variabele wordt verbonden: s.push_str(“ deze string wordt aan s toegevoegd”);

Iets ingewikkelder wordt het in de tweede helft van de broncode. Hier wordt het verschil duidelijk tussen simpele typen en complexere typen. De techniek daarachter is dat een simpel type door de compiler op de stack wordt geplaatst, en bij een complex type wordt de waarde in de heap geplaatst, met een pointer naar die waarde op de stack. (zie hier voor grondige uitleg)

Als je variabelen maakt waarbij de compiler al weet hoeveel geheugen ze in beslag nemen, dan worden deze op de stack geplaatst en werken ze helemaal zoals je zou verwachten. Dus na

let x = 1;
let y = x ;

hebben x en y beiden de waarde 1. Het tweede commando heeft de waarde van x gekopieerd in y.

Maar nu een String, waarbij de compiler niet kan weten hoeveel geheugen nodig zal zijn, het is immers een dynamisch type in die zin dat het kan groeien en krimpen gedurende het programma. Wat gebeurt er nu?

let sa = String::from(“Hallo”);
let sb = sa;

Het eerste commando plaatst "Hallo" in de heap, en een pointer daarnaartoe op de stack. sa krijgt in feite die pointer mee. Het tweede commando plaatst die pointer in sb maar kopieert de waarde "Hallo" op de heap niet. Ownership van "Hallo" is dus overgedragen van sa naar sb. Op zich zou dat in C ongeveer hetzelfde werken, maar nu grijpt Rust in. Aangezien sa ownership kwijt is, verliest het ook geldigheid, en de compiler zal niet langer accepteren dat je sa probeert te gebruiken. In de broncode bij deze aflevering wordt dat toch geprobeerd, weliswaar na //. Als je die dubbel strepen weg haalt zal je, na een :w zien dat bacon direct een foutmelding geeft. Gebruik je bacon niet, dan merk je het zodra je de compiler aan het werk zet. De code wordt geweigerd, omdat sa wordt gebruikt nadat het geldigheid verloor, bij het overzetten van de ownership. 

Het is van belang te bedenken dat Rust dus een probleem van C oplost, maar je werkt nog steeds dicht op het computer-niveau. Iedere R- of Python-programmeur kan zich afvragen waar je je in hemelsnaam druk over maakt. Het antwoord is natuurlijk: omdat we geen garbage collector hebben, en variabelen direct op het computergeheugen zitten, zijn er fouten mogelijk die je in R, Python of andere hogere talen simpelweg niet kent.

En welke fouten voorkomen we door deze ownership-rules? 

1.Doordat verlies van ownership automatisch de variabele ongeldig maakt, kan je niet per vergissing met die loze pointer nog iets doen en ongedefinieerd gedrag oproepen.

2.Doordat overdracht van ownership automatisch ook verlies van ownership betekent, en er dus altijd maar één owner van dezelfde data kan bestaan, voorkom je dat je data muteert via een andere variabele dan die waarvan je dacht dat dat de juiste verwijzing was. 

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.

Arnout van Kempen di CCO CISA is Senior manager Risk & Compliance bij Baker Tilly. Hij schrijft op persoonlijke titel. Hij is lid van de Commissie Financiƫle verslaggeving & Accountancy van de AFM en lid van de signaleringsraad van de NBA. Daarnaast is hij diaken van het bisdom 's-Hertogenbosch.

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.