RozdziałyStruktury danychStruktury danych

Struktury danych

Typy struktur

  1. Struktura jednostkowa/pusta (ang. unit-like structure)
    struct EmptyStructure; 
     
    fn main() {
        let es = EmptyStructure;
    }
  2. 🤔 Czy i w jakim celu możemy definiować takie struktury?
  3. Struktura a’la krotka (ang. tuple structure)
    struct Color(i32, i32, i32); // fields are without names (are access as in tuple)
    struct Point(i32, i32, i32); // a different structure with the same types
     
    fn main() {
        let black = Color(0, 0, 0);
        let origin = Point(0, 0, 0);
    }
  4. Po prostu struktura 😈:
    struct Point {
        x : f32,    // named attributes like in classes
        y : f32,
    }

Tworzenie instancji struktury

  1. W przykładowym projekcie, w katalogu examples dodaj plik rectangle.rs.

  2. Otwórz powyższy plik, a w nim utwórz strukturę danych opisującą prostokąt:

    #[derive(Debug)] // allows to print the structure in debug mode (ie. to use {:?})
    struct Rectangle {
        x : f32,
        y : f32
    }
  3. Dodaj metodę main, w której utwórz nową instancję struktury Rectangle i wypisz ją na konsole:

    fn main() {
        let r = Rectangle{x: 1.0, y: 2.0}; // create a new instance using constructor
     
        println!("{:?}", r);
        
        println!("x: {}, y: {}", r.x, r.y); // access particular fields using . operator
    }
  4. Skompiluj kod i uruchom przykład:

    cargo run --example rectangle
  5. Możesz również utworzyć instancje na bazie innej instancji (poniższy kod dopisz do funkcji main z poprzedniego punktu)

    let r2 = Rectangle{x : 5, ..r1} // the rest of r2 parameters are copied from r1
    println!("{:?}", r);
    println!("x: {}, y: {}", r2.x, r2.y);

Atrybuty struktur danych, w kontekście posiadania wartości, zachowują się w analogicznych sposób jak zmienne.

Jeśli atrybut jest typu, który posiada daną wartość (ang. owned type), to właśność tej wartości należy do instancji struktury. Oznacza to, że wartość ta zostanie usunięta z pamięci w sytuacji, kiedy instancja struktury wyjdzie poza zakres.

Jeśli natomist atrybut struktury jest referencją, to konieczne będzie określenie czasu życia takiej wartości - o czym będzie więcej w sekcji Czas życia.

Dostęp i modyfikacja atrybutów

  1. Instancja niemutowalna - wszystkie atrybuty są niemutowalne
    let r3 = Rectangle{x : 5.0, y : 9.0};
    println!("[{}, {}]", r3.x, r3.y);
  2. Próba ustawienia wartości atrubutu
    r3.x = 6.0; // error
  3. Instacja mutowalna
    let mut r4 = Rectangle{x : 5.0, y : 9.0};
    println!("[{}, {}]", r4.x, r4.y);
    r4.x = 6.0;
    r4.y = 7.0;
    println!("[{}, {}]", r4.x, r4.y);

Mutowalność struktury (na poziomie tworzenia nowej instancji). Nie można ustawiać mutowalności pojedynczych pól struktury.

Metody

Metody instancyjne

  1. Pod definicją struktury Rectangle dodaj blok będący miejscem implementacji metod związanych z naszą strukturą

    impl Rectangle {
        // the place for rectangle methods
    }
  2. Wewnętz powyższego bloku zdefiniuje metodę obliczającą pole prostokąta.

    fn area(&self) -> f32 {
        self.x * self.y
    }

    Zwróć uwage na pierwszy parametr metody (&self), który oznacza referencję do instancji struktury (musi to być zawsze pierwszy parametr metod instancyjnej). Referencję self możesz wykorzystać w ciele metody, aby odwołać się do pól instancji struktury lub innych metod instancyjnych.

  3. W funkcji main dodaj kod, który wywoła metodę area na stworzonej wcześniej instrancji Rectangle

    println!("Area of {:?} is {}", r, r.area());
  4. W analogiczny sposób dodaj metodę wyznaczającą obwód prostokąta.

  5. Dodaj metodę umożliwiającą przeskalowanie prostokąta o zadany współczynnik.

    fn scale(&mut self, factor:f32) {
        self.x = self.x * factor;
        self.y = self.y * factor;
    }

    Zwróć uwagę na pierwszy parametr metody (&mut self), który oznacza, że metoda ta będzie modyfikować instancję struktury.

  6. W metodzie main utwórz nową instancję i wywołaj na nią metodę scale:

    let r = Rectangle{ x : 5.0, y : 4.0};
    r.scale(2.0);
    println!("Area of r is {}", r.area());
  7. Czy kod się skompilował? Dlaczego?

  8. Jak poprawić powyższy przykład?

Metody powiązane

  1. W bloku impl utwórz metodę new_square, który utworzy nowy kwadrat (metody tego typu nazywane są metodami fabrycznymi). Blok ten powinien wyglądać następująco:
    impl Rectangle {
        // ... other methods
        
        fn new_square(x : f32) -> Rectangle { // there is no self in the argument list
            Rectangle{x : x, y : x}
        }
    }
  2. Metody powiązane wywołujemy za pomocą nazwy struktury, a następnie dwóch znaków dwukropka, w naszym przypadku będzie to Rectangle::new_square. W metodzie main utwórz nowy kwadrat i wydrukuj “go” na konsolę:
    let square = Rectangle::new_square(5.0);
    println!("square: {:?}", square);
  3. Możesz uprościć metodę new_square wykorzystując regułę, pozwalającą na automatyczne przypisane parametru funkcji do parametru tworzonej instancji struktury o tej samej nazwie:
    fn new_square(x : f32) -> Rectangle {
        Rectangle{x, y : x} // the first argument can be passed in simplified form
    }