Programmieren in Rust

Objektorientierung

Inhaltsverzeichnis

  1. Konzeption
  2. Klassen
  3. Vererbung
  4. Polymorphie
  5. Klassenvariablen

Konzeption

Unter Objektorientierung versteht man eine Zusammenfassung von Konzepten, die dazu dienen, Prozeduren eng an Daten zu binden. Die Daten werden in diesem Zusammenhang als Objekte bezeichnet, ihre Prozeduren als Methoden.

Die Sprache Rust unterstützt nicht jedes Konzept in direkter Weise. Dies gilt vor allem für dynamische Mechanismen wie Reflektion. Um ein Gefühl dafür zu bekommen, inwieweit Objektorientierung ermöglicht wird, wollen wir Umsetzungen in kurzen Beispielen erläutern. Für solche Mechanismen, die nicht direkt möglich sind, soll hierbei der Versuch einer Emulierung in Angriff genommen werden.

Erwähnen möchte ich noch, dass es prinzipell möglich ist, mit Trait-Objekten ein beliebiges Objektsystem zu konstruieren. Allerdings geht hiermit der Verlust von Optimierungen und von statischer Typsicherheit einher.

Klassen

struct Bird {
    name: String
}
impl Bird {
    fn fly(&self) {
        println!("{} fliegt.", self.name);
    }
}

fn main() {
    let bird = Bird {name: String::from("Donald")};
    bird.fly();
}

Vererbung

Vererbung gibt es in Rust nicht. Sie lässt sich allerdings durch Komposition modellieren.

struct Bird {
    name: String
}
impl Bird {
    fn fly(&self) {
        println!("{} fliegt.", self.name);
    }
}

struct Duck {
    bird: Bird
}
impl Duck {
    fn new(name: String) -> Self {
        Self{bird: Bird {name}}
    }
    fn dive(&self) {
        println!("{} kann tauchen.", self.bird.name);
    }
}

fn main() {
    let duck = Duck::new("Donald".into());
    duck.bird.fly();
    duck.dive();
}

Polymorphie

use std::any::Any;

trait BirdTrait {
    fn as_any(&self) -> &dyn Any;
    fn name(&self) -> &str;
    fn fly(&self);
}

struct Bird {
    name: String
}
impl Bird {
    fn new(name: &str) -> Self {
        Self {name: name.into()}
    }
}
impl BirdTrait for Bird {
    fn as_any(&self) -> &dyn Any {self}
    fn name(&self) -> &str {&self.name}
    fn fly(&self) {
        println!("{} fliegt.", self.name);
    }
}

struct Duck {
    bird: Bird
}
impl Duck {
    fn new(name: &str) -> Self {
        Self {bird: Bird {name: name.into()}}
    }
    fn dive(&self) {
        println!("{} kann tauchen.", self.bird.name);
    }
}
impl BirdTrait for Duck {
    fn as_any(&self) -> &dyn Any {self}
    fn name(&self) -> &str {&self.bird.name}
    fn fly(&self) {
        self.bird.fly();
    }
}

fn main() {
    let birds: Vec<Box<dyn BirdTrait>> = vec![
        Box::new(Duck::new("Donald")),
        Box::new(Bird::new("Bella"))
    ];
    for bird in birds.iter() {
        bird.fly();
        if let Some(duck) = bird.as_any().downcast_ref::<Duck>() {
            duck.dive();
        } else {
            println!("{} ist keine Ente.", bird.name());
        }
    }
}

Klassenvariablen

Klassenvariablen sind Variablen die nicht Speicherplätze von Objekten sind, sondern von Klassen. Das heißt, eine solche Variable liegt nur einmal pro Klasse vor. Zur Modellierung konstruiert man am besten eine Laufzeitumgebung env, die eine Laufzeitdarstellung der Klasse enthält. Damit das Programm an allen Stellen Zugriff auf die Laufzeitumgebung bekommen kann, kommt ein Zeiger auf die Laufzeitumgebung in eine globale Variable ENV. Zur Vereinfachung betrachten wir nur ein Programm ohne Nebenläufigkeit. Die globale Variable wird daher Strang-lokal gemacht.

use std::rc::Rc;
use std::cell::RefCell;

struct DuckClass {
    name: Rc<str>,
    quack: Rc<str>
}

struct Env {
    duck_class: Rc<RefCell<DuckClass>>
}
impl Env {
    fn new() -> Rc<Self> {
        Rc::new(Self {
            duck_class: Rc::new(RefCell::new(DuckClass {
                name: Rc::from("Duck"),
                quack: Rc::from("quacks")
            }))
        })
    }
    fn get() -> Rc<Env> {
        ENV.with(|env| env.clone())
    }
}
thread_local! {
    static ENV: Rc<Env> = Env::new();
}

struct Duck {
    class: Rc<RefCell<DuckClass>>,
    name: String
}
impl Duck {
    fn new(env: &Env, name: &str) -> Self {
        Self {name: name.into(), class: env.duck_class.clone()}
    }
    fn class_name(&self) -> Rc<str> {
        self.class.borrow().name.clone()
    }
    fn name(&self) -> &str {&self.name}
    fn quack(&self) {
        println!("{} {}.", self.name, self.class.borrow().quack);
    }
}

fn load_german(env: &Env) {
    let mut duck_class = env.duck_class.borrow_mut();
    duck_class.name = Rc::from("Ente");
    duck_class.quack = Rc::from("quakt");
}

fn main() {
    let env = &Env::get();
    load_german(env);
    let duck = Duck::new(env,"Donald");
    duck.quack();
    println!("Name von duck ist: {}.", duck.name());
    println!("Klasse von duck ist: {}.", duck.class_name());
}