Aktywne Wpisy
Ryksa +37
Czy brak samochodu u faceta to red flag? Ma prawko, ale co mi po nim. Każdy poprzedni miał samochód i zabierał mnie na randki za miasto. Muszę wszędzie jeździć komunikacją, robię to już do pracy. Lubie jeździć tramwajami i pociągiem, ale tak dziwnie, facet powinien mieć auto, choćby miało stać na parkingu. To jednak jakiś status dorosłości
jednakenergetyk +202
Ukradli mi kilka dni temu dopiero co zakupioną dwuletnią Corollę, dałem za nią 60k, generalnie już się pogodziłem że stratą, nie miałem AC bo przez kilka szkód wychodziło ponad 10% wartości co wydało mi się zbyt wiele. Kilka godzin po zakupie OC auta i przerejestrowaniu nie było pod moim blokiem. Auto odebrałem tego samego dnia co zginęło. Największa farsa w tym kraju to jednak policja. Policjant powiedział że dziennie w Warszawie ginie
Aktywne Znaleziska
Zawiera treści 18+
Ta treść została oznaczona jako materiał kontrowersyjny lub dla dorosłych.
Zawiera treści 18+
Ta treść została oznaczona jako materiał kontrowersyjny lub dla dorosłych.
Część 1. Po kiego chu… grzyba, czyli co wyróżnia Rusta z pośród reszty.
Już jakiś czas temu napisałem wpis na Mirko gdzie porównywałem Rusta i parę innych "nowoczesnych" języków, tutaj się trochę rozpiszę i przy okazji dam małe wprowadzenie co i jak.
Rozdział 1. Niezmienność jest domyślna
W językach takich jak C, C++, C#, Java niezmienność jest wyborem. Wszystkie zmienne są w domyśle mutowalne. Ma to swoje zalety jako, że nasz mózg przyzwyczajony jest do tego iż zmienną można zmieniać od momentu jej powstania. Jest parę języków, które tego nie wspierają i są to głównie języki funkcyjne (z wyjątkiem - Scalą). W Ruscie to, że zmienna ma być mutowalna jest kwestią wyboru, nie na odwrót.
Przykład:
let a = 10;
a = 20; // błąd
let mut a = 10; // absolutnie legalne w Ruscie, można przesłonić zmienną w tym samym bloku
a = 20; // OK
Takie założenie pozwala w teorii kompilatorowi na trochę większą optymalizację kodu, a dodatkowo wymusza lepsze przemyślenie programu przez programistę.
Rozdział 2. Bezpieczeństwo pamięci
Do dnia dzisiejszego były 2 wybory jeśli chodziło o zarządzanie pamięcią w programie (jeśli się mylę to niech ktoś mnie oświeci a odszczekam):
+ ręczne - uciążliwe z wiadomych powodów: łatwo o wyciek, słabo współpracuje z wątkami i wyjątkami, dość niebezpieczne (patrz GHOST/Heartbleed)
+ GC - wygodne, ale problemy z wydajnością (global lock przy załączeniu się czyszczenia) czy wielowątkowością (jak uruchomić GC na wielowątkowym procesie)
Świętym Graalem hakerów było jednoczesne uzyskanie wydajności i "niskopoziomowych" zabawek przy jednoczesnym zapewnieniu bezpieczeństwa pamięci. C++11 jako tako próbuje sobie z tym poradzić przy pomocy nowych *inteligentnych*
(inaczej) wskaźników jednak ma to ten problem, że wymaga dalej uwagi programisty (trzeba ręcznie pozmieniać wszystkie wskaźniki na te wynalazki) oraz słabo współpracuje z istniejącymi bibliotekami.
Rust wpadł na rozwiązanie: lifetime. Jest to dodatkowy atrybut przypinany do każdej referencji, który określa kiedy kończy się "czas życia" zmiennej. Ten system pozwala na określenie w czasie kompilacji czy istnieje możliwość, by dana referencja wskazywała na zwolnioną pamięć.
Zagadka: Czy dany program w C++ jest poprawny?
#include
#include
int main() {
std::vector vec{};
vec.pushback(42);
int& ref = vec[0];
vec.pushback(69);
std::cout << ref;
}
Odpowiedź i wyjaśnienie (o ile nie będzie w komentarzach) przeczytacie w kolejnej części, gdzie postaram się przybliżyć lifetime i resztę zarządzania pamięcią z Rusta.
Rozdział 3. Typowanie
Typy w językach kompilowanych są dość istotną sprawą - w końcu zapewniają nam bezpieczeństwo kodu. Nie inaczej jest w Ruscie. Z pewnymi wyjątkami.
Wyjątek 1 - naprawdę silne typowanie.
C czy C++ mimo iż mają namiastki silnego typowania to są absolutnie szczęśliwe w przypadku takiego kodu (nie ma nawet upomnienia):
#include
int main() {
int a = 10;
unsigned int b = a;
printf("%d\n", b);
return 0;
}
Czemu brak upomnienia jest tutaj zły? Bo ani C ani C++ nie określają w jaki sposób obsłużyć moment kiedy
a < 0
. Możliwe, że większość z was teraz mi powie, że U2, ale standard nigdzie nie zapewnia, że liczby mają być kodowane w U2. Mogąbyć zakodowane w dowolny sposób. Rust nie dopuści do takiej profanacji, co więcej, nie dopuści nawet do sytuacji, która w teorii nie jest szkodliwa:
fn main() {
let a: i64 = 10;
let b: i32 = a; // błąd kompilacji, mimo iż w teorii nie tracimy żadnych
// danych (standard zapewnia kodowanie U2)
println!("{}", b);
}
Wyjątek 2 - system typów Henleya-Milnera.
Jest to taki sam system typów jak w np. Haskellu. Czym on się różni od
auto
(C++) lubvar
(C#/Scala)? Ano tym, że w przypadku tamtych języków typ jest definiowany w miejscu, tzn. że w momencie deklaracji cały typ zmiennej musi być znany.Przykład (tym razem C#):
var list = new List();
list.Add(10);
list.Add(20);
Gdzie
T
musi być znane w momencie wywołania tej linii. Kompilator nie potrafi sam "wydedukować" typu zmiennej.Przykład w Ruscie:
let mut vec = Vec::new(); // nie określamy typu zmiennych w wektorze
vec.push(10); // tutaj jest on określany jako typ całkowity
vec.push(20);
println!("{:?}", vec); // => [10, 20]
// z racji, że nie ma dokładnego określenia jaki to ma być typ zmiennej całkowitej
// to kompilator przyjmuje, że jest to
isize
czyli domyślny typ całkowitoliczbowy// dla danej platformy
Typy zmiennych są dedukowane w czasie kompilacji na bazie całego życia zmiennej. W ten sposób programista nie musi się martwić wyjątkiem 1. (w większości przypadków).
Rozdział 4. Brak dziedziczenia i system
traits
ówRust nie wspiera dziedziczenia. Jakiegokolwiek. W dowolnej postaci. Kropka.
Zawsze można użyć kompozycji.
W zamian mamy system traitsów, który jest formą interfejsów (i działa podobnie jak w Scali). By się nie rozpisywać, przykładzik:
struct Foo(u32); // Struktura zawierająca tylko 1 anonimowe pole o typie
u32
impl Add for Foo { // operator +
type Output = Foo;
fn add(self, other: Foo) -> Foo {
let Foo(this) = self;
let Foo(other) = other;
Foo(this + other) // (prawie) każde wyrażenie w Ruscie zwraca wartość, brak
;
na końcu funkcji oznacza, że to jest wartość zwracana}
}
println!("{:?}", Foo(10) + Foo(20)); // => Foo(30)
Rozdział 5. Pattern matching, typy wyliczeniowe, monady i obsługa błędów
Jeśli ktoś z was bawił się kiedyś językami funkcyjnymi to jest świadomy opcji pattern matchingu w takowych językach. Jest to swoistego rodzaju odpowiednik bloku
switch
dla ludzi z C/C++. Przykładenum Foo {
Bar,
Baz(u32) // w Ruscie typy wiliczeniowe mogą nieść wartość (mogą być monadami)
}
let a = Foo::Baz(10);
match a {
Foo::Bar => println!("Take me to the Gay Bar"),
Foo::Baz(val) => println!("Nuke Bar with {} nukes", val)
};
Z racji, że Rust nie posiada wyjątków obsługa błędów jest obsługiwana przez monadę
Result
:let num = -1.0;
let sqrt = match num.sqrt() {
Ok(val) => val,
Err(err) => panic!("{}", err)
}
Epilog części 1.
To by było chyba na tyle. Najważniejsze różnice zostały przedstawione. Oczywiście, z racji, że Rust jest mocno funkcyjny, nie mogło w nim zabraknąć lambd, ale to omówię innym razem (najprawdopodobniej a propos wielowątkowości).
#programowanie #rustlang #naukaprogramowania
Ciąg dalszy będzie pod tagiem #haulethuczyrusta
I wołam zainteresowanych @Acrene, @siepet, @yonah, @kuhar, @pp555, @sosnnaa, @kao3991, @tytyryty, @Aysorth, @Felonious_Gru, @Existanza, @kornik20082, @GlenPL, @notauser, @Analityk, @mpisz, @Seima, @Pietrek558, @gress
Jak kogoś zapomniałem to trudno.
potem przeczytam, może nawet spróbuję ( ͡°( ͡° ͜ʖ( ͡° ͜ʖ ͡°)ʖ ͡°) ͡°)
@Hauleth:
implementation defined
;)@Hauleth: Mówią, cytatem rzucę za kilka minut.
N3797, §4.7/2 [conv.integral]
:tl;dr: dodajesz lub odejmujesz
Co do liczb, to rzeczywiście, zwracam honor. C++ musiał jak zwykle #!$%@?ć.
O "braku dziedziczenia" rozpiszę się później i wtedy dasz znać czy to taka wada. Bo rozwiązane jest to na tyle sprytnie, że ciężko to uznać za wadę ;) Chyba, że opiszesz, czemu to aż taka wada.
@dziedziczenie: jutro opiszę mój use-case, moƶe źle na to patrzę i moƶna to ładniej zrobić (inna sprawa, ƶe nie wiem jak to wygląda w ruście, więc moƶe faktycznie jest dostatecznie sprytne)