Rust – Borrowing Ownership – Pożyczanie własności

W dwóch poprzednich odcinkach cyklu omówiliśmy:

Dziś skupimy się na mechanizmie Borrowing, czyli na pożyczaniu własności.

Po co nam pożyczanie?

Do tej pory pokazałem, że możemy przekazać własność do zmiennej. A co, gdy nie chcemy oddawać własności?

W jakich sytuacjach to może okazać się przydatne? Gdy chcemy nadal w scopie mieć własność, a funkcja potrzebuje tylko wykorzystać tą zmienną do swoich celów bez zmiany właściciela.

Wróćmy do przykładu z książką. To my jesteśmy właścicielem książki. W momencie, w którym nasz kolega chce tylko coś w książce sprawdzić, to możemy mu ją chwilowo pożyczyć. Gdy skończy, to książka wraca w nasze ręce i to na nas spoczywa odpowiedzialność za odłożenie jej na półkę, gdy skończymy czytać.

Różnica między przeniesieniem (Moving), a pożyczaniem (Borrowing) jest bardzo istotna. W pierwszym przypadku nastąpi zmiana właściciela, w drugim wypadku nie.

Dzięki zastosowaniu pożyczenia możliwe jest korzystanie ze zmiennej w obu scopach – zarówno przez właściciela, jak i przez pożyczającego.

Jak to zrobić w kodzie?

Do pożycznia służy operator: & przy operacji przypisania.

Uruchom przykład

Specjalnie stworzyłem nowy scope (linia 3 – początek, linia 6 – koniec). W tym zakresie “żyje” zmienna book2.

Powyższy kod powoduje, że zmienna book2 tylko pożycza sobie własność.

“Pożycza”, czyli:

  • Zmienna pożyczająca book2 nie staje się właścicielem zawartości zmiennej (czyli powiązanego z nią fragmentu pamięci),
  • Po wyjściu z zakresu życia zmiennej pożyczającej nie nastąpi zwolnienie segmentu pamięci.
  • Tylko wyjście właściciela (w naszym wypadku zmiennej book) poza zakres użycia jest jednoznaczne ze zwolnieniem segmentu pamięci

Można tu zauważyć kilka różnic między przenoszeniem, a pożyczeniem. Gdybyśmy w linii 4 zrealizowali przeniesienie (gdybyśmy nie użyli operatora &) to kod by się nie skompilował. Pożyczenie sprawia, że możemy swobodnie uzyskać dostęp do zmiennej book bez zmiany właściciela.

Można na ten mechanizm patrzeć jak na tworzenie aliasu do zmiennej.

Wyobraźcie sobie, że pożyczając zmienną do metody, mam możliwość korzystania z niej, ale bez praw związanych z bycia jej właścicielem, wykorzystując do tego ten alias.

Jest to przydatne, gdy chcemy przekazać np: zmienną do funkcji i nadal móc z niej korzystać w macierzystym zakresie bez konieczności przenoszenia własności z powrotem.

Ograniczenia mechanizmu

  • Nie można przenieść własności, jeśli własność została pożyczona wcześniej, do momentu zakończenia pożyczenia.

Poniższy kod zakończy się błędem na poziomie kompilacji:

Uruchom przykład

Jeśli lekko zmodyfikujemy kod i sprawimy, że zmienna pożyczająca (book2) wyjdzie z zakresu – co jest jednoznaczne z zakończeniem wypożyczenia, to kod będzie się kompilował.

Uruchom przykład

Prawidłowe jest natomiast takie działanie, by najpierw przekazać własność (book -> book2), a potem ją pożyczyć (book2->book3). Po zakończeniu operacji .

Uruchom przykład

Kolejność wykonywania operacji jest tutaj bardzo istotna i trzeba się zaprzyjaźnić z oboma mechanizmami. Z tego powodu musiałem zakomentować pierwszego println.

Funkcje a pożyczanie

  • Funkcje mogą pożyczać własność od prawowitego właściciela z wykorzystaniem operatora & przy parametrze. Wymaga to także użycia opreatora & przy wywołaniu funkcji.

Uruchom przykład

Ważne jest to, że jeśli pożyczymy własność, to nie możemy jej przenieść. Poniższy kod zakończy się błędem kompilacji.

Na pierwszy rzut oka może to wyglądać myląco, gdyż informacja mówi o mismatch type. Wynika to z tego, że zmienna jest typu &Book, a my staramy się zwrócić Book.

Gdy bliżej się przyjrzymy, to widać, że staramy się zwrócić pożyczoną zmienną jako własną.

Uruchom przykład Zasady są proste:

  • Funkcje, które pożyczają własność od zmiennej, mogą tylko przekazać prawo do pożyczenia.
  • Funkcje, które mają własność, mogą przekazać własność dalej.

Przekazanie pożyczenia można zauważyć w poniższym kodzie:

Uruchom przykład

Jedna rzecz jest godna uwagi. Ważny jest typ zmiennej zwracanej, a nie jej nazwa. Zmienna book jest typu &Book, co oznacza, że jest pożyczającą własność.

Wiszące referencje

W poprzednich artykułach tylko delikatnie zarysowałem problem wiszących referencji. Skoro już wiesz, jak działa borrowing, to pora pokazać, jak Rust sobie radzi z tym problemem.

Wyobraź sobie, że posiadasz książkę, która została Ci pożyczona. W momencie, w którym otwierasz tą książkę by ją przeczytać, okazuje się, że nie ma zawartości. Kolega, który Ci ją pożyczył, zdążył ją odebrać, usunąć lub komuś sprzedać.

Gdy wrócimy na poziom kodu, to powyżej opisana sytuacja odpowiada pożyczeniu komuś prawa do zmiennej, która została usunięta.

Efekt:

Po odwołaniu się do zmiennej możesz natrafić na … No właśnie – na co? Na pewno nie jest to poprawna wartość?

To jest zależne od języka. Mogą to być śmieci, może to być jeszcze nie usunięty obiekt. Duża doza niepewności.

Rust, by wyeliminować tę niepewność, w ogóle nie dopuszcza do takich sytuacji. Na etapie kompilacji ujrzymy stosowny komunikat błędu.

Jak by to wyglądało – w funkcji create_dangling_reference tworzymy zmienną book. Zmienna book jest właścicielem żyjącym w zakresie funkcji.

Uruchom przykład

Podsumowanie

Borrowing, czyli pożyczanie, to drugi oprócz przenoszenia (Moving Ownership) mechanizm w języku Rust, zajmujący się zakresem zarządzania pamięcią.

Pożyczając innej zmiennej lub parametrowi funkcji (który też jest zmienną) jakąś własność powodujemy, że możliwe jest użycie pożyczonej zmiennej w innym zakresie. Ale nadal zmienna będąca właścicielem jest odpowiedzialna za ten segment pamięci.

Może istnieć tylko jeden właściciel zmiennej. To pozostaje niezmienne.

Dopiero po wyjściu poza zakres zmiennej, która posiada daną własność, zmienna zostanie usunięta.

Można pożyczyć własność tylko do takiej zmiennej, do której my mamy własność.

Funkcje pożyczają własność na tej samej zasadzie, co zmienne.

W dzień Senior Software Developer w firmie Future Processing, w nocy śpi. Ponad 8 lat doświadczenia w zakresie wytwarzania oprogramowania w różnych technologiach oraz domenach, również w takich, w których nikt nie chciał pracować. Zafascynowany rozwojem technologii związanej z przetwarzaniem danych a w szczególności tworzeniem rozwiązań z rodziny Big Data. Prelegent oraz organizator licznych wydarzeń, których głównym celem jest dzielenie się wiedzą oraz krzewienie potrzeby stosowania dobrych praktyk, w celu maksymalizacji jakości wytwarzanego produktu. Współorganizator Wakacyjnych Praktyk w Future Processing oraz prowadzący przedmiot na Politechnice Śląskiej „Tworzenie Oprogramowania w Zmiennym Środowisku Biznesowym”.
PODZIEL SIĘ