Programowanie w języku Rust
Programowanie w języku Rust to oficjalna książka na temat Rusta: języka programowania na licencji open source, który pozwala nam szybciej pisać niezawodne o
programowanie. Rust daje możliwość kontrolowania szczegółów niskiego poziomu (jak wykorzystanie pamięci) w połączeniu z ergonomią wysokiego poziomu, eliminując kłopoty tradycyjnie związane z językami niskiego poziomu.
W książce Programowanie w języku Rust dwaj członkowie Rust Core Team pokazują, jak w pełni korzystać z właściwości Rusta – od instalacji po tworzenie własnych niezawodnych i skalowalnych programów.
Zaczynamy od podstaw, takich jak tworzenie funkcji, wybieranie typów danych i wiązanie zmiennych i przechodzimy następnie do bardziej zaawansowanych pojęć takich jak:
posiadanie i pożyczanie, czasy życia i cechy bezpieczeństwo pamięci Rusta, które gwarantuje budowanie szybkich, bezpiecznych programów testowanie, obsługa błędów i efektywna refaktoryzacja typy generyczne, inteligentne wskaźniki, wielowątkowość, obiekty cech oraz zaawansowane dopasowywanie wzorców użycie Cargo - wbudowanego w Rust menedżera pakietów, służącego do budowania, testowania i dokumentowania swojego kodu i zarządzania zależnościami używanie zaawansowanego
kompilatora Rusta wraz z technikami programowania opartymi na kompilatorze Książka zawiera dużo przykładów kodu, a także trzy rozdziały poświęcone budowaniu gotowych projektów przeznaczonych do sprawdzenia swojej wiedzy: gra w zgadywanie, implementacja narzędzia wiersza poleceń w języku Rust oraz wielowątkowy serwer.
WSTĘP xix
PODZIĘKOWANIA xxi
WPROWADZENIE xxiii
Dla kogo jest Rust xxiv
Zespoły deweloperskie xxiv
Studenci xxiv
Firmy xxiv
Deweloperzy open source xxv
Ludzie ceniący szybkość i stabilność xxv
Dla kogo jest ta książka xxv
Jak korzystać z tej książki xxv
Źródła i jak uczestniczyć w tworzeniu tej książki xxvii
1. ROZPOCZYNAMY 1
Instalacja 1
Instalacja rustup w systemach Linux i macOS 2
Instalacja rustup w systemie Windows 3
Aktualizacja i odinstalowywanie 3
Usuwanie błędów 4
Lokalna dokumentacja 4
Hello, world! 4
Tworzenie katalogu projektu 5
Pisanie i uruchamianie programu w języku Rust 5
Anatomia programu w języku Rust 6
Kompilacja i uruchomienie to oddzielne kroki 7
Witaj Cargo! 8
Tworzenie projektu za pomocą Cargo 9
Tworzenie i uruchamianie projektu w Cargo 10
Tworzenie gotowej wersji 12
Cargo jako konwencja 12
Podsumowanie 13
2. PROGRAMOWANIE ZGADYWANKI 15
Tworzenie nowego projektu 16
Przetwarzanie odpowiedzi 17
Zapisywanie wartości w zmiennych 18
Obsługa potencjalnych błędów za pomocą typu Result 19
Wyświetlanie wartości za pomocą symboli zastępczych w println! 21
Testowanie pierwszej części 21
Generowanie tajnej liczby 22
Korzystanie ze skrzynki, aby rozszerzyć funkcjonalność 22
Generowanie liczby losowej 24
Porównanie liczby odgadniętej z tajną 26
Dopuszczenie wielu prób za pomocą pętli 30
Kończenie programu po poprawnej odpowiedzi 31
Obsługa niepoprawnych danych na wejściu 31
Podsumowanie 34
3. TYPOWE POJĘCIA Z ZAKRESU PROGRAMOWANIA 35
Zmienne i mutowalność 36
Różnice między zmiennymi a stałymi 38
Zasłanianie 39
Typy danych 40
Typy skalarne 41
Typy złożone 44
Funkcje 47
Parametry funkcji 48
Instrukcje i wyrażenia w treści funkcji 50
Funkcje z wartościami zwrotnymi 51
Komentarze 53
Sterowanie przepływem 54
Wyrażenie if 54
Powtarzanie w pętlach 59
Podsumowanie 62
4. POJĘCIE POSIADANIA 63
Czym jest posiadanie? 63
Reguły posiadania 65
Zakres zmiennej 65
Typ String 66
Pamięć i jej przydział 67
Posiadanie i funkcje 73
Zwracane wartości i zakres 73
Odwołania i pożyczki 75
Odwołania mutowalne 77
Wiszące odwołania 79
Reguły odwołań 81
Typ wycinek 81
Wycinki łańcucha 83
Inne wycinki 87
Podsumowanie 87
5. UŻYWANIE STRUKTUR DO KONSTRUOWANIA POWIĄZANYCH DANYCH 89
Definiowanie struktur i tworzenie ich instancji 89
Używanie skrótu do inicjacji pola, gdy zmienne i pola mają takie same nazwy 91
Tworzenie instancji z innych instancji za pomocą składni aktualizacji struktury 92
Użycie struktur krotki bez nazywanych pól do tworzenia różnych typów 93
Struktury jako jednostki bez żadnych pól 93
Przykład programu z użyciem struktury 95
Refaktoryzacja za pomocą krotek 96
Refaktoryzacja za pomocą struktur 96
Dodawanie użytecznej funkcjonalności za pomocą wyprowadzonych cech 97
Składnia metody 99
Definiowanie metod 100
Metody z większą liczbą parametrów 102
Funkcje powiązane 103
Wiele bloków impl 103
Podsumowanie 104
6. TYP WYLICZENIOWY I DOPASOWANIE WZORCA 105
Definiowanie wyliczenia 106
Wartości wyliczeń 106
Wyliczenie Option i jego zalety w porównaniu z wartościami null 110
Operator sterowania przepływem match 113
Wzorce, które są związane z wartościami 114
Dopasowywanie za pomocą Option 115
Dopasowania wyczerpują wszystkie przypadki 116
Symbol zastępczy _ 117
Sterowanie przepływem za pomocą if let 118
Podsumowanie 119
7. WYKORZYSTANIE MODUŁÓW DO PORZĄDKOWANIA KODU I JEGO PONOWNEGO WYKORZYSTYWANIA 121
mod i Filesystem 122
Definicje modułów 123
Przenoszenie modułów do innych plików 125
Reguły systemów plików modułów 130
Sterowanie widocznością za pomocą pub 131
Ustawienie funkcji jako publicznej 132
Reguły prywatności 135
Przykłady prywatności 135
Odwoływanie się do nazw w różnych modułach 136
Wprowadzanie nazw do zakresu za pomocą słowa kluczowego use 137
Wprowadzanie wszystkich nazw do zakresu za pomocą glob 138
Użycie super do uzyskiwania dostępu do modułu nadrzędnego 139
Podsumowanie 141
8. TYPOWE KOLEKCJE 143
Przechowywanie list wartości za pomocą wektorów 144
Tworzenie nowego wektora 144
Uaktualnianie wektora 145
Usunięcie wektora usuwa jego elementy 145
Czytanie elementów wektora 146
Iterowanie po wartościach w wektorze 148
Użycie wyliczania do przechowywania wielu typów 148
Przechowywanie w łańcuchach tekstu zakodowanego za pomocą UTF-8 149
Czym jest łańcuch? 150
Tworzenie nowego łańcucha 150
Aktualizacja łańcucha 152
Indeksowanie łańcuchów 154
Dzielenie łańcuchów na wycinki 156
Metody iterowania po łańcuchach 157
Łańcuchy nie są takie proste 158
Przechowywanie kluczy z powiązanymi wartościami w mapach skrótów 158
Tworzenie nowej mapy skrótów 158
Mapy skrótów a posiadanie 160
Dostęp do wartości w mapie skrótów 160
Aktualizacja mapy skrótów 161
Funkcje skrótu 163
Podsumowanie 163
9. OBSŁUGA BŁĘDÓW 165
Błędy nienaprawialne z makrem panic! 166
Użycie śladu panic! 167
Błędy do naprawienia za pomocą Result 170
Dopasowywanie przy różnych błędach 172
Skróty do paniki przy błędzie: unwrap i expect 173
Propagowanie błędów 175
Panikować czy nie panikować 178
Przykłady, prototypowy kod i testy 179
Przypadki, w których mamy więcej informacji niż kompilator 179
Wskazówki dotyczące obsługi błędów 180
Tworzenie niestandardowych typów do celów sprawdzania 181
Podsumowanie 183
10. TYPY GENERYCZNE, CECHY I CZASY ŻYCIA 185
Usuwanie duplikacji przez wyodrębnienie funkcji 186
Generyczne typy danych 189
W definicjach funkcji 189
Definicje w strukturze 191
W definicjach wyliczeń 193
W definicjach metod 194
Wydajność kodu z użyciem typów generycznych 196
Cechy – definiowanie wspólnego zachowania 197
Definiowanie cechy 197
Implementowanie cechy na typie 198
Implementacje domyślne 200
Granice cech 202
Naprawa funkcji largest za pomocą granic cech 203
Używanie granic cech do metod implementowanych warunkowo 205
Sprawdzanie odwołań za pomocą czasów życia 207
Zapobieganie „wiszącym” odwołaniom za pomocą czasów życia 207
Kontroler pożyczek 208
Generyczne czasy życia w funkcjach 209
Składnia adnotacji dla czasu życia 211
Adnotacje o czasie życia w sygnaturach funkcji 212
Myślenie w kategoriach czasów życia 214
Adnotacje o czasie życia w definicjach struktur 216
Pominięcie czasu życia 216
Adnotacje o czasie życia w definicjach metod 219
Statyczny czas życia 220
Parametry generycznego typu, granice cech i czas życia w połączeniu 220
Podsumowanie 221
11. PISANIE AUTOMATYCZNYCH TESTÓW 223
Jak pisać testy 224
Anatomia funkcji testowania 224
Sprawdzanie wyników za pomocą makra assert! 228
Testowanie równości za pomocą makr assert_eq! i assert_ne! 231
Dodawanie niestandardowych komunikatów 233
Testowanie kodu pod kątem paniki za pomocą should_panic 235
Sterowanie sposobem uruchamiania testów 238
Uruchamianie testów równolegle lub po kolei 239
Pokazywanie wyników funkcji 239
Uruchomienie podzbioru testów według nazwy 241
Pomijanie niektórych testów, jeśli nie zostaną konkretnie wymienione 243
Organizacja testów 244
Testy jednostkowe 245
Testy integracyjne 246
Podsumowanie 250
12. PROJEKT WE/WY – BUDOWA PROGRAMU WIERSZA POLECEŃ 253
Akceptowanie argumentów wiersza poleceń 254
Czytanie wartości argumentów 255
Zapisywanie wartości argumentów w zmiennych 256
Czytanie pliku 257
Refaktoryzacja w celu poprawienia modułowości i obsługi błędów 259
Oddzielanie problemów w projektach binarnych 260
Naprawa obsługi błędów 264
Wyodrębnianie kodu z main 268
Podział kodu do skrzynki bibliotecznej 271
Tworzenie funkcjonalności biblioteki przy użyciu TDD 272
Pisanie testu zakończonego niepowodzeniem 273
Pisanie testu, który przejdzie 275
Praca ze zmiennymi środowiskowymi 278
Pisanie testu zakończonego niepowodzeniem dla funkcji nierozróżniających wielkości liter 279
Implementacja funkcji search_case_insensitive 280
Pisanie komunikatów o błędach do standardowego błędu zamiast standardowego wyjścia 284
Sprawdzanie, gdzie są zapisywane błędy 284
Zapisywanie błędów do standardowego błędu 285
Podsumowanie 286
13. FUNKCJE JĘZYKA FUNKCYJNEGO: ITERATORY I ZAMKNIĘCIA 287
Zamknięcia – anonimowe funkcje, które mogą przechwycić swoje środowisko 288
Tworzenie abstrakcji zachowania za pomocą zamknięć 288
Wnioskowanie o typie zamknięcia i adnotacje 293
Zapisywanie zamknięć z użyciem parametrów generycznych i cech Fn 295
Ograniczenia implementacji Cacher 298
Przechwytywanie środowiska za pomocą zamknięć 299
Przetwarzanie ciągów elementów za pomocą iteratorów 302
Cecha Iterator i metoda next 303
Metody, które zużywają iterator 304
Metody tworzące inne iteratory 305
Wykorzystanie zamknięć, które przechwytują swoje środowisko 306
Tworzenie własnych iteratorów za pomocą cechy Iterator 307
Ulepszanie naszego projektu we/wy 310
Usuwanie klonu za pomocą iteratora 310
Bardziej przejrzysty kod dzięki adapterom iteratora 313
Porównywanie wydajności – pętle a iteratory 314
Podsumowanie 316
14. WIĘCEJ INFORMACJI O CARGO I CRATES.IO 317
Wersje niestandardowe z profi lami wydania 318
Publikacja skrzynki w Crates.io 319
Dokonywanie użytecznych komentarzy dokumentujących 319
Eksport wygodnego publicznego API za pomocą pub use 323
Tworzenie konta Crates.io 327
Dodawanie metadanych do nowej skrzynki 327
Publikowanie w Crates.io 329
Publikowanie nowej wersji istniejącej skrzynki 329
Usuwanie wersji z Crates.io za pomocą cargo yank 329
Przestrzenie robocze Cargo 330
Tworzenie przestrzeni roboczej 330
Tworzenie drugiej skrzynki w przestrzeni roboczej 331
Instalowanie wersji binarnych z Crates.io za pomocą cargo install 336
Rozszerzanie Cargo za pomocą niestandardowych poleceń 337
Podsumowanie 337
15. INTELIGENTNE WSKAŹNIKI 339
Używanie Box do wskazywania danych na kopcu 341
Używanie Box do zapisu danych na kopcu 341
Włączenie typów rekurencyjnych z pudełkami 342
Traktowanie inteligentnych wskaźników jak zwykłych odwołań z cechą Deref 346
Podążanie za wskaźnikiem do wartości z użyciem operatora wyłuskiwania 346
Używanie Box jak odwołania 347
Definiowanie własnego inteligentnego wskaźnika 348
Traktowanie typu jak odwołania dzięki implementacji cechy Deref 349
Niejawne wymuszanie Deref z funkcjami i metodami 350
Jak wymuszanie Deref współdziała z mutowalnością 351
Uruchamianie kodu czyszczącego z cechą Drop 352
Wczesne odrzucanie wartości z zastosowaniem std::mem::drop 354
Rc – inteligentny wskaźnik ze zliczaniem odwołań 355
Używanie Rc do współdzielenia danych 356
Klonowanie Rc zwiększa licznik odwołań 358
RefCell oraz wzorzec wewnętrznej mutowalności 360
Wymuszanie reguł pożyczania podczas wykonania za pomocą RefCell 360
Mutowalność wewnętrzna – mutowalne pożyczanie niemutowalnej wartości 361
Wielu posiadaczy zmiennych danych dzięki połączeniu Rc i RefCell 367
Odwołania cykliczne mogą prowadzić do wycieku pamięci 369
Tworzenie odwołań cyklicznych 369
Zapobieganie odwołaniom cyklicznym – zmiana Rc na Weak 372
Podsumowanie 377
16. WSPÓŁBIEŻNOŚĆ BEZ OBAW 379
Użycie wątków do równoległego uruchamiania kodu 380
Tworzenie nowego wątku za pomocą spawn 382
Oczekiwanie, aż wszystkie wątki się zakończą, z użyciem JoinHandle 383
Korzystanie z zamknięcia move z wątkami 385
Używanie przekazywania komunikatów do transferu danych między wątkami 388
Kanały i przeniesienie własności 391
Wysyłanie wielu wartości i oczekujący nadajnik 392
Tworzenie wielu producentów przez klonowanie nadajnika 393
Współbieżność ze współdzieleniem zasobów 395
Wykorzystanie muteksów w celu zezwolenia na dostęp do danych z jednego wątku naraz 395
Podobieństwa między RefCell/Rc a Mutex/Arc 402
Elastyczna współbieżność z cechami Sync i Send 403
Pozwolenie na przenoszenie posiadania między wątkami za pomocą Send 403
Pozwolenie na dostęp z wielu wątków za pomocą Sync 404
Ręczne implementowanie Send i Sync nie jest bezpieczne 404
Podsumowanie 404
17. WŁASNOŚCI PROGRAMOWANIA OBIEKTOWEGO W JĘZYKU RUST 407
Charakterystyka języków obiektowych 408
Obiekty zawierają dane i działanie 408
Enkapsulacja, która ukrywa szczegóły implementacji 408
Dziedziczenie jako system typów i jako współdzielenie kodu 410
Wykorzystywanie obiektów cech, które dopuszczają wartości różnych typów 411
Definiowanie cechy dla jednolitego zachowania 412
Implementowanie cechy 414
Obiekty cech wykonują dynamiczne wysyłki 417
Bezpieczeństwo obiektowe jest wymagane dla obiektów cech 418
Implementowanie wzorca projektu obiektowego 419
Definiowanie postu i tworzenie nowej instancji w stanie Draft 421
Przechowywanie tekstu treści postu 422
Gwarantowanie, że treść projektu postu jest pusta 422
Żądanie oceny postu zmienia jego stan 423
Dodawanie metody approve, która zmienia zachowanie treści 424
Kompromisy wzorca stanu 427
Podsumowanie 432
18. WZORCE I DOPASOWYWANIE 433
Wszystkie miejsca, w których można korzystać ze wzorców 434
Gałęzie match 434
Wyrażenia warunkowe if let 435
Warunkowe pętle while let 436
Pętle for 436
Instrukcja let 437
Parametry funkcji 438
Podważalność – czy dopasowanie do wzorca może się nie udać 439
Składnia wzorca 441
Dopasowywanie literałów 441
Dopasowywanie nazwanych zmiennych 442
Wiele wzorców 443
Dopasowywanie zakresów wartości za pomocą składni. 443
Destrukturyzacja w celu rozdzielenia wartości 444
Pomijanie wartości we wzorcu 448
Tworzenie odwołań we wzorcach za pomocą ref i ref mut 453
Dodatkowe instrukcje warunkowe ze strażnikami dopasowania 455
Wiązania @ 457
Podsumowanie 458
19. FUNKCJE ZAAWANSOWANE 459
Niebezpieczny Rust 460
Niebezpieczne super możliwości 460
Wyłuskiwanie pierwotnego wskaźnika 461
Wywoływanie niebezpiecznej funkcji lub metody 463
Uzyskiwanie dostępu do modyfi kowalnej zmiennej statycznej lub jej modyfi kowanie 468
Implementacja bezpiecznej cechy 470
Kiedy korzystać z kodu, który nie jest bezpieczny 470
Zaawansowane czasy życia 471
Podtypy czasów życia jako gwarancja, że jeden czas życia jest dłuższy od innego 471
Granice czasu życia w odwołaniach do typów generycznych 476
Wnioskowanie o czasach życia obiektów cech 478
Zaawansowane cechy 479
Określanie typów symboli zastępczych w defi nicjach cech z powiązanymi typami 479
Domyślne parametry typów generycznych i przeciążenie operatora 481
Składnia w pełni kwalifi kowana dla jednoznaczności – wywoływanie metod z taką samą nazwą 483
Użycie super cech, aby wymagać funkcjonalności jednej cechy wewnątrz innej cechy 487
Użycie wzorca newtype do implementacji zewnętrznych cech na zewnętrznych typach 489
Typy zaawansowane 490
Użycie wzorca newtype dla bezpieczeństwa typów i abstrakcji 490
Tworzenie synonimów typu z aliasami typów 491
Typ „nigdy”, który nigdy niczego nie zwraca 493
Typy o dynamicznie określanych rozmiarach i cecha Sized 495
Zaawansowane funkcje i zamknięcia 497
Wskaźniki funkcji 497
Zwracanie zamknięć 498
Podsumowanie 499
20. OSTATNI PROJEKT – BUDOWANIE WIELOWĄTKOWEGO SERWERA WWW 501
Budowanie jednowątkowego serwera WWW 502
Nasłuchiwanie połączeń TCP 502
Czytanie żądania 505
Bliższe spojrzenie na żądanie HTTP 507
Pisanie odpowiedzi 507
Zwracanie rzeczywistego HTML 508
Sprawdzanie poprawności żądania i selektywne odpowiadanie 510
Nieco refaktoryzacji 512
Zamiana serwera jednowątkowego na wielowątkowy 513
Symulowanie powolnego żądania w bieżącej implementacji serwera 513
Poprawienie przepustowości za pomocą puli wątków 514
Płynne zakończenie i czyszczenie 534
Implementacja cechy Drop na ThreadPool 534
Sygnalizowanie wątkom, aby przestały nasłuchiwać zadań 537
Podsumowanie 541
A SŁOWA KLUCZOWE 543
Słowa kluczowe obecnie stosowane 543
Słowa kluczowe zarezerwowane do użycia w przyszłości 545
B OPERATORY I SYMBOLE 547
Operatory 547
Symbole inne niż operatory 549
C CECHY WYPROWADZONE 555
Debugowanie wyjścia dla programisty 556
PartialEq i Eq do porównań równościowych 556
PartialOrd i Ord do porównywania kolejności 557
Klonowanie i kopiowanie zduplikowanych wartości 557
Cecha Hash do odwzorowania wartości na wartość o ustalonym rozmiarze 558
Cecha Default dla wartości domyślnych 559
D. MAKRA 561
Różnica między makrami a funkcjami 562
Makra deklaratywne z macro_rules! do celów ogólnego metaprogramowania 563
Makra proceduralne do celów niestandardowych wyprowadzeń 565
Przyszłość makr 570
INDEKS 571