Jak oni rdzewieją (Advent of Code 2021)

Jak oni rdzewieją (Advent of Code 2021)
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
9

Wrzucam na githuba moje mizerne próby z Rustem w ramach AoC, więc jeśli ktoś chciałby się popastwić i wyjaśnić czemu to wszystko jest źle, to zapraszam: https://github.com/Pharisaeus/AoC2021/tree/master/src :)

edit: dla jasności nie przerobiłem nawet żadnego kursu Rusta, odpalam IntelliJ i pisze "na czuja" :D


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
KamilAdam
Czy pod coś takiego nie jest dział oceny i recenzje :p
Shalom
Ale tam nikt nie zagląda :P
p_agon
Zglaszac moda/ nie zglaszac. A zglosze xD
Shalom
Dodatkowo w sumie interesują mnie raczej "językowe" rzeczy, tzn "jak to powinno sie napisać w ruście"
LU
Ja mam jakieś zadanka z 2019 jeszcze, w pythonie, ale w połowie się zaciąłem i nie kontynuowałem bo nie było czasu :P https://github.com/lukascode/AoC/tree/master/2019
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:28 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
5

Łooo panie, ile kodu. Nawet traity implementujesz. Jest sens implementować traity jak i tak nie potrzebujesz polimorfizmu? (Chyba że potrzebujesz, tylko okiem z komórki rzuciłem). Ja wiem że w OOP się trzecie te interfejsy i klasy czy jest to potrzebne czy nie (wyjątkiem jest chyba tylko Kotlin) ale czy w Ruscie tez jest taka konwencja?


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
Shalom
Sugerujesz ze napisałem kod w Javie, który tylko udaje że jest Rustem? To jest całkiem możliwe :P
KamilAdam
Sugeruję że wielu z nas jest spaczonych OOP :D
jarekr000000
Kiedyś pisałem javę w Haskellu. Można.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 10 godzin
1
KamilAdam napisał(a):

Łooo panie, ile kodu. Nawet traity implementujesz. Jest sens implementować traity jak i tak nie potrzebujesz polimorfizmu?

Gdzie te implementacje traitów? impl Cośtam { ... } to nie jest implementacja traitu. Implementacja traitu to raczej coś a'la impl TraitName for StructName { ... }.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
4
  1. Ostatnia instrukcja jest niejako "auto return", tzn. wystarczy:

    Kopiuj
    fn part1(commands: &[Command]) -> i32 {
        /* ... */
        pos * depth
    }
    

    Explicit return wykorzystywany jest jedynie w sytuacjach early-exit, wyjść z pętli itd.

  2. Dzięki temu, że pętla for item in items desugaruje się do:

    Kopiuj
    let items = items.into_iter();
    
    while let Some(item) = items.next() {
        /* ... */
    }
    

... oraz że istnieje IntoIterator dla &[T], wystarczy np. for command in commands {, bez wprost .iter().

  1. Kopiuj
    fn part1_fun(commands: &[Command]) -> i32 {
        let final_state = commands.iter()
            .fold(State1 { pos: 0, depth: 0 }, move_sub1);
        return final_state.pos * final_state.depth;
    }
    

    =>

    Kopiuj
    #[derive(Default)]
    struct State1 {
        /* ... */
    }
    
    impl State1 {
        fn score(self) -> i32 {
            /* ... */
        }
    }
    
    fn part1_fun(commands: &[Command]) -> i32 {
        commands
            .iter()
            .fold(Default::default(), move_sub1)
            .score()
    }
    
  2. Zwyczajowo dorzuca się adnotacje tylko do tych typów, z którymi kompilator ma problem - tzn.:

    Kopiuj
    let commands: Vec<Command> = contents.lines()
        .map(|line| Command::new(line))
        .collect();
    

    =>

    Kopiuj
    let commands: Vec<_> = contents.lines().map(Command::new).collect();
    
  3. Jeśli istnieje trait pokrywający daną funkcjonalność, to zazwyczaj nie robi się "freestanding" funkcji:

    Kopiuj
    impl Board {
        /* ... */
    
        fn clone(&self) -> Board {
            /* ... */
        }
    }
    

    ... a implementuje dany trait:

    Kopiuj
    impl Clone for Board {
        /* ... */
    }
    

    (clippy ma nawet na to linta! :-))

  4. Zwyczajowo settery nazywa się set_foo(), lecz gettery po prostu foo() (https://rust-lang.github.io/api-guidelines/naming.html).

  5. no sentinel values, only ADTs angry doge meme

    Kopiuj
    fn part1(data: &InputData) -> i32 {
        /* ... */
        for number in data.numbers.iter() {
            /* ... */
            if boards.is_any_winning() {
                return /* ... */;
            }
        }
        return -1;
    }
    

    =>

    Kopiuj
    fn part1(data: &InputData) -> Option<u32> {
        /* ... */
        for number in &data.numbers {
            /* ... */
            if boards.is_any_winning() {
                return Some(/* ... */);
            }
        }
        None
    }
    
  6. Kopiuj
    struct BinNumber {
        value: Vec<i32>,
    }
    

    =>

    Kopiuj
    struct BinNumber {
        value: Vec<bool>,
    }
    

    (to uprości np. negowanie)

Tak ogólnie to fajen - kod wygląda całkiem dobrze, a widzę, że i nawet miejsce na Itertools się znalazło :-)


edytowany 5x, ostatnio: Patryk27
Shalom
Jeśli istnieje trait pokrywający daną funkcjonalność to bym najpierw musiał wiedzieć co istnieje a co nie :D :D
Patryk27
Ano, i w tym miejscu wchodzi Clippy, cały na biało :D
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 10 godzin
3
Patryk27 napisał(a):
  1. Jeśli istnieje trait pokrywający daną funkcjonalność, to zazwyczaj nie robi się "freestanding" funkcji:
    Kopiuj
    impl Board {
        /* ... */
    
        fn clone(&self) -> Board {
            /* ... */
        }
    }
    
    ... a implementuje dany trait:
    Kopiuj
    impl Clone for Board {
        /* ... */
    }
    
    (clippy ma nawet na to linta! :-))

Można wstawić #[derive(Clone, Copy, Debug, Eq, PartialEq)] przed struct czy enum i mieć za darmo implementacje różnych traitów, pod warunkiem, że wszystkie pola też mają (rekurencyjnie) implementacje tych traitów. Szczegóły: https://doc.rust-lang.org/rust-by-example/trait/derive.html

P.S. Copy to tylko do bardzo lekkich struktur. Do czegokolwiek nietrywialnego należy użyć Clone. Różnica jest taka, że kopiowanie jest niejawne, automatyczne, a klonowanie jest jawne, więc widać gdzie ponosimy koszt klonowania.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Patryk27
True - przez chwilę myślałem, że te fn clone() mają jakąś własną logikę, ale faktycznie to tylko pass-through do .clone() ich pól.
Shalom
te wojny klonów tam wynikają tylko z dzielnej walki z borrow checkerem i lifetimami :P
KR
Różnica między Copy i Clone jest jeszcze na innym poziomie - Copy jest wyłącznie do kopiowania płaskich wartości, które można skopiować kopiując obszar pamięci jednym ciągiem (czyli przez memcpy). Nie da się zrobić głębokiego Copy schodzącego głębiej poprzez referencje. Zauważ, że Copy to tylko marker, nie ma żadnych metod.
KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:4 dni
  • Postów:2964
1

Zwyczajowo dorzuca się adnotacje tylko do tych typów, z którymi kompilator ma problem - tzn.:

Kopiuj
let commands: Vec<_> = contents.lines().map(Command::new).collect();

Można użyć collect_vec() i wtedy w ogóle nie musisz podawać typu.

LW
A jeszcze można: ... .collect<Vec<_>>(). Wtedy plugin w idea wyświetli typ po commands taką samą czcionką co wszędzie indziej.
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Chciałem dziś zrobić ładnie z .fold() ale kopiowanie boarda po oznaczeniu pola strasznie się ślimaczyło, a nie umiem przekonać borrow-checkera do jakiegoś:

Kopiuj
    lines.iter()
        .fold(board, |board, line| board.mark_line(line));

ale w sytuacji kiedy mark_line nie zwraca kopii Board tylko &Board

edit: Żeby wyjaśnić o co pytam. Można by napisać taki kod:

Kopiuj
use itertools::Itertools;

struct Board {
    board: Vec<i32>,
}

impl Board {
    fn new(size: i32) -> Board {
        return Board { board: (0..size).map(|x| 0).collect_vec() };
    }
    fn mark_field(&self, index: usize) -> Board {
        return Board {
            board: self.board
                .iter()
                .enumerate()
                .map(|(i, &value)| if i == index { value + 1 } else { value })
                .collect_vec()
        };
    }
}

pub(crate) fn solve() {
    let board = (0..10000)
        .fold(Board::new(10000), |board, index| board.mark_field(index));
    println!("{}", board.board[1]);
}

Czyli za każdym razem kiedy robimy mark_field zwracamy nową planszę. Tylko że jak plansza jest duża to nagle robi sie to bardzo wolne, z oczywistych względów.

I teraz zastanawiałem się, czy można tak zrobić, zeby nasze mark_field zwracało zmodyfikowaną początkową tablicę, a nie tworzyło nową za każdym razem (pytanie co jest "bardziej rustowe" i moze jest jakaś trzecia opcja?). I pisząc tego posta doszedłem do tego, ze mogę zrobić:

Kopiuj
    fn mark_field(mut self, index: usize) -> Board {
        self.board[index]+=1;
        return self;
    }

Bo mogę przyjąć sobie self a nie &self jako argument.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
Zobacz pozostałe 5 komentarzy
KR
Tak, to prawda. Ale wtedy zamiast fold robisz proste iterowanie z akumulatorem, co jest IMHO bardziej rustowe. Rust ma mechanizmy pozwalające bezpiecznie mutować i nie należy na siłę robić z niego Haskella. fold zaciemnia sytuację, bo właśnie sugeruje że dostajesz coś nowego, a tu chcemy po prostu zmodyfikować jeden istniejący obiekt.
hauleth
(0..size).map(|x| 0).collect_vec() bleh, vec![0, size]
Shalom
Wiedziałem że musi być łatwiejszy sposób :D :D
Patryk27
vec![0; size]; przecinek utworzyłby wektor z dwoma elementami ([0, size] vs [0, 0, 0, ...]).
hauleth
Ah tak, dawno nie pisałem w Ruscie.
FE
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 3 lata
0

Jak wrażenia z Rusta po AoC2021? Jakieś refleksje?


Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

Hmm dość specyficzny język. Moim zdaniem miejscami mocno autystyczny, kiedy np. nie możesz porównać &int z intem i musisz robić jakiś cyrk w stylu &0. Dodatkowo nie wyobrażam sobie pisania w Ruście bez silnego wsparcia IDE, które pokazuje wyniki inferencji typów, szczególnie kiedy nagle po jakimś .iter() czy w jakimś .map() dostajesz &&X.
Swoją drogą, biorąc pod uwagę brak raw-pointerów jako takich (w sensie jakiegoś int* ptr = 0x12345; w C), zastanawiam się, czemu składnia idzie w kierunku pointerów z C (w kontekście & i *) a nie czegoś na wzór referencji.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Shalom
Na co dzień to Java/Python
Shalom
Nie no w pracy raczej nie będę miał okazji użyć nigdzie rusta, a przynajmniej nie przewiduje :D
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
0

Swoją drogą, biorąc pod uwagę brak raw-pointerów jako takich

FWIW, istnieją raw pointery:

Kopiuj
fn main() {
    let ptr = 0x12345 as *const i32;
    println!("{}", unsafe { *ptr }); // 99% szansy na segfault
}

a nie czegoś na wzór referencji.

W sensie, że bez ręcznego pisania *? Taki mechanizm już istnieje i nazywa się auto-deref :-)


edytowany 2x, ostatnio: Patryk27
Shalom
No tak ale unsafe to unsafe, a ja mówie bardziej o takim klasycznym zastosowaniu ;)
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
0
Shalom napisał(a):

Dodatkowo nie wyobrażam sobie pisania w Ruście bez silnego wsparcia IDE, które pokazuje wyniki inferencji typów, szczególnie kiedy nagle po jakimś .iter() czy w jakimś .map() dostajesz &&X.

To może być przyzwyczajenie javowe - zupełnie niepotrzebne natręctwo do podglądania typów. Podobny problem niektórzy raportują w kotlinie... podczas kiedy całkiem dobrze się pisze z wyłączonymi hintami typów. W haskellu to norma, że typów nie widzisz przez całe długie kawałki kodu i zwykle żadnego specjalnego wsparcia IDE nie ma.
A rust z wnioskowaniem typów jest bliżej nawet tego haskella.


jeden i pół terabajta powinno wystarczyć każdemu
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@jarekr000000 może gdyby były jakieś naturalne koercje pomiędzy tymi & to by się dało, ale kiedy nie możesz porównać &int z intem, a gdzieś nagle dostajesz & albo w ogóle && to moim zdaniem jednak ciężko.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
SL
  • Rejestracja:około 7 lat
  • Ostatnio:około 2 godziny
  • Postów:896
0

Swoją drogą, biorąc pod uwagę brak raw-pointerów jako takich (w sensie jakiegoś int* ptr = 0x12345; w C), zastanawiam się, czemu składnia idzie w kierunku pointerów z C (w kontekście & i *) a nie czegoś na wzór referencji.

@Shalom pisanie przez parę lat w C++ skłoniło mnie do konkluzji, że implicit referencje to zło wcielone. Semantyka wskaźników używana w C czy Javie jest prosta i skuteczna. To, że w zależności od kontekstu referencja zachowuje się jak wskazywany typ albo nie powoduje dużo problemów np. nieustanne niepotrzebne kopie, konieczne cuda na kiju (jak std::reference_wrapper) popieprzone wyłomy w systemie typów. Zauważ, że w wielu przypadkach Rust idzie na rękę i sprawia, że referencje są przezroczyste w przypadkach, gdy jest to całkowicie nieszkodliwe np. wołanie metod.

Shalom
czy Javie ? Przecież w Javie masz właśnie ładne "niewidzialne" referencje i nigdy nie ma potrzeby robienia ręcznej dereferencji.
SL
ale nie masz też "normalnych" wartości (oprócz prymitywów, ale te z drugiej strony nie mają referencji/pointerów), więc wprowadzanie dereferencji nie ma sensu. Fundamentalną różnicą jest to, że w C++ da się zaimplementować swap(x, y) a w C/Javie nie.
Shalom
Wydaje mi się że jednak twój problem z referencjami w C++ wynika z mutowalności, co akurat w Ruście nie jest problemem bo tam musisz explicite oznaczyć coś jako mutable.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.