Wątek przeniesiony 2021-08-05 11:51 z Inne języki programowania przez cerrato.

Rust - wszechobecne borrow-y i mut-y

1

Niedawno zaczalem uczyc sie rusta, na poczatku spodobaly mi sie koncepty immutability by default i ownership, ale jak zaczalem sobie cos tam dlubac to koniec koncow 90% moich funkcji przyjmowalo parametry & mut ..., czy to self czy jakies kontenery. Zawsze jak musze dodac do parametru czy to &, czy mut to mam wrazenie, ze cos robie nie tak lub naookolo i powinienem tego za wszelka cene unikac. Z drugiej strony wezmy przykladowy kod ze strony nphysics:

fn main() {
    let mut mechanical_world = DefaultMechanicalWorld::new(Vector3::new(0.0, -9.81, 0.0));
    let mut geometrical_world = DefaultGeometricalWorld::new();

    let mut bodies = DefaultBodySet::new();
    let mut colliders = DefaultColliderSet::new();
    let mut joint_constraints = DefaultJointConstraintSet::new();
    let mut force_generators = DefaultForceGeneratorSet::new();

    loop {
        // Run the simulation.
        mechanical_world.step(
            &mut geometrical_world,
            &mut bodies,
            &mut colliders,
            &mut joint_constraints,
            &mut force_generators
        )
    }
}
```

Wszedzie muty i borrowy. Pomysly z immutability i ownershipem sa sie fajne, ale w praktyce wydaja sie uciazliwe jak kula u nogi. Czy tak wyglada wiekszosc kodu napisanego w ruscie?
2

W Ruscie jeszcze nie pisałem, tylko czytałem o nim, więc co borrow się nie wypowiem. Za to co do immutability jak najbardziej. Tak jest możliwe pisanie kodu, zwłaszcza biznesowego, gdzie wszystkie zmienne są immutability. Ale oczywiście potrzebujemy odpowiednich bibliotek do tego. Ewentualnie można opakowywać biblioteki z interfejsem przyjmującym mutowalne zmienne/stan w takie które czegoś takiego nie mają. Jeśli ktoś spierdzielił API biblioteki której chcesz użyć to często nie będziesz miał wyboru. Ale dalej należy walczyć o to żeby zmienne mutowalne miały jak najmniejszy zasięg

6

Rozważ zapis

x = y

To, co on oznacza zależy od języka. W ruście przenosisz zawartość y do zmiennej x, zostawiając y w stanie niezainicjalizowanym. W C++ kopiujesz obiekt, konstruktorem kopiującym, który czasem może być bardzo ciężki. Jak chcesz przenieść to musisz jawnie zrobić std::move. Przekazywanie parametru jest dokładnie takie same: w C++ domyślnie kopiuje, w Ruście domyślnie przenosi. A zatem poniższy kod się w Ruście nie kompiuluje:

struct Label {
    number: u32
}

fn print(l: Label) {
    println!("STAMP:{}", l.number);
}

fn main() {
    let l = Label { number: 3 };
    print(l);
    println!("My label number is: {}", l.number);
}

W main tworzysz obiekt l, a wywołując print(l) przekazujesz go print. Od tej pory należy on do niej. W rezultacie println! w kolejnej linii próbowałby pobrać pole z "opróżnionego" obiektu, który jest już gdzie indziej, u kogoś innego. To jest błąd i rust wykrywa to już w czasie kompilacji. Masz więc dwa wyjścia. Albo, jak w C++, przekażesz do print kopię:

#[derive(Copy, Clone)]
struct Label {
    number: u32
}

fn print(l: Label) {
    println!("STAMP:{}", l.number);
}

fn main() {
    let l = Label { number: 3 };
    print(l);
    println!("My label number is: {}", l.number);
}

Albo pożyczysz funkcji print na chwilę obiekt l przekazując referencję:

struct Label {
    number: u32
}

fn print(l: &Label) {
    println!("STAMP:{}", l.number);
}

fn main() {
    let l = Label { number: 3 };
    print(&l);
    println!("My label number is: {}", l.number);
}

Ergo: tak, borrowy są wszechobecne, bo zawsze ktoś musi być właścicielem danego obiektu. Możesz posiadany obiekt albo komuś oddać i nigdy więcej nie używać, albo komuś pożyczyć, albo komuś pożyczyć i pozwolić modyfikować (C++-owy const correctness by default) albo stworzyć mu jego własną kopię. I tak, bywa to uciążliwe, ale taka jest cena za odporność na błędy. C++ daje ci swobodę, którą większość niedoświadczonych użytkowników nie potrafi się posługiwać, kończąc strzelając sobie w stopę (a i zaawansowanym co chwilę się to zdarza). Rust jest bardziej odporny na różne nadużycia, ale wymaga bardziej świadomej kontroli nad przekazywaniem posiadania obiektów.

3

Nie programuję w rust, ale wygląda to tak że ten język silnie promuje brak zmienności stanu, zapewne zmienność stanu jest tam tylko na wypadek gdyby kompilator nie umiał czegoś dobrze zoptymalizować.
Co do samej niezmienności stanu, to jest ona zupełnie praktyczna i niesie wiele korzyści. To bardziej kwestia nawyku. Ja też w większości taki kod piszę. Trzeba było się przestawić na bardziej funkcyjny styl programowania, ale zdecydowanie warto.

1

Tak i nie. Wszystko zależy co i jak piszesz. W części kodu będzie trochę & oraz &mut ale często jest to nie potrzebne, bo move semantics załatwi sprawę.

3

Jak piszę w C++ to też często używam przekazywania parametrów przez const &. Częściej jednak pożycza się obiekt aby go nie zmieniać, więc podejście w Rust jest bardziej praktyczne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1