#Klooienmetcomputers

Borrowing en strings

Arnout van Kempen over rommelen in een digitale wereld.

In vorige afleveringen hebben we de algemene ownership regels van Rust leren kennen. En we hebben gezien hoe ownership in de praktijk werkt met een String variabele. Daarbij is meteen een klein beetje OOP onze wereld in geslopen, maar we gaan eerst door op ownership. In feite was dat een oplossing voor het gebruik van dynamisch gealloceerd geheugen, zoals we dat uit C kennen met functies als malloc() en free(). Alles wat je in C in een programma fout kan doen op dat vlak, wordt door Rust al door de compiler geweigerd. Het gevolg is dat je langer bezig bent je programma goed te krijgen, maar het eindresultaat is veel robuuster. En om dat "langer bezig zijn" wat te helpen, hebben we plugins in Vim geïntroduceerd en bacon. Samen met het al veel eerder geïntroduceerde Cargo hebben we zo een behoorlijk goede set hulpmiddelen. Klaar voor de volgende stap dus en dan hebben we het moeilijkste van Rust wel min of meer te pakken.

We weten dat een variabele in scope moet zijn om te kunnen gebruiken, en dat als de variabele uit scope gaat, deze door Rust automatisch gedropped wordt. En we weten dat een overdracht van ownership de variabele ook ongeldig maakt. Maar als we functies gaan schrijven, zoals we ook in C deden, dan is het wel lastig met die strikte ownership en scope-regels van Rust. De oplossing kennen we ook al uit C, maar in Rust wordt die systematischer en vooral: afgedwongen.

Voor het mentale model is het handig te bedenken dat als een waarde een owner heeft en we ownership niet ongestraft kunnen overdragen, het een oplossing kan zijn dat "iemand anders" de waarde kan lenen, ofwel borrowing. De aardigheid van borrowing is dat je de waarde nog steeds kan gebruiken, maar als de borrower uit scope gaat, geef je de waarde gewoon terug aan de owner, en wordt die dus niet gedropped.

In het Rust-handboek dat ik gebruik, staan twee listings die functioneel hetzelfde doen, maar die goed laten zien wat borrowing zo handig maakt.

Eerst zonder borrowing:

fn main() {
     let s1 = String::from(“hello”);

     let (s2, len) = calculate_length(s1);

     println!(“The length of ‘{}’ is {}.”, s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
     let length = s.len();

     {s, length)
}

Je maakt hier gebruik van de mogelijkheid een tule als return-waarde van een functie te geven. Zo kan je uit één functie in feite meerdere return-waardes tegelijk krijgen. Maar waarom is dat eigenlijk nodig? Waarom niet gewoon de lengte van s1 teruggeven, en dan s1 en die lengte printen? Dat werkt niet, omdat je bij de aanroep van de functie s1 mee geeft als argument. Zodra de functie start wordt ownership van s1 overgedragen naar de lokale variabele s. Als de functie klaar is, gaat s uit scope, en wordt automatisch gedropped. Bij terugkeer in main heeft s1 geen geldigheid meer, want bij overdracht van ownership hield s1 op betekenis te hebben. Maar s, in de functie, is automatisch gedropped toen s uit scope ging. Kortom, de waarde van s1 is weg. Daarom is s2 nodig, die via de tuple-return uit de functie de waarde van, op dat moment, s krijgt, en dus ook ownership.

Het werkt, maar het is nogal gedoe. En dankzij borrowing ook niet nodig. Het kan ook zo:

fn main() {
     let s1 = String::from(“hello”);

     let len = calculate_length(&s1);

     println!(“The length of ‘{}’ is {}.”, s1, len);
}

fn calculate_length(s: &String) -> usize {
     s.len()
}

Waarom werkt dit? Omdat we niet langer ownership van s1 overdragen naar de functie en dus naar s. We maken gebruik van borrowing, door niet s1 als argument mee te geven, maar een referentie naar s1, door er een & voor te zetten. Bij borrowing wordt ownership niet overgedragen, dus als de functie eindigt en s uit scope gaat, wordt s wel gedropped, maar dat is alleen een referentie naar s1, niet s1 zelf. En omdat s1 nooit ownership heeft verloren, is s1 gewoon nog geldig, in scope, als de functie klaar is. Let wel op, het type van s, in de functie, is nu natuurlijk ook geen String meer, maar een referentie naar een String, dus &String.

Het gebruik van borrowing brengt een stuk eenvoud terug, die door de ownership regels verloren zou zijn gegaan. Je kan natuurlijk zeggen: maar in C had ik gewoon de string zelf kunnen doorgeven aan de functie en was er niks aan de hand geweest, dan is borrowing toch nog steeds complexer dan wat C doet? Ja, dat klopt. De veiligheid van Rust heeft een prijs. Maar een vrij kleine prijs, zo blijkt.

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.