C++ im Vergleich zu RUST


  1. Einleitung

Einleitung

Mir wurde kürzlich ein 3-tägiger Rust-Crashkurs angeboten, um die Programmiersprache kennen zu lernen.
Da sie sich an einigen Stellen sehr von C++ unterscheidet, dachte ich, dass es Sinn macht, die gleiche Funktionalität in beiden Sprachen zu implementieren.

So kann man direkt die Syntax vergleichen. Schließlich sagt ein Code mehr tausend Wort.

Hello World

1//C++
2int main()
3{
4  printf("Hello World\n");
5  return 0;
6}
1//Rust
2fn main() {
3  println!("Hello World");
4  std::process::exit(0);
5}

Funktionen und Parameter

 1//C++
 2static double add(int a, float b)
 3{ // local function
 4  double d = a + b;
 5  return d;
 6}
 7
 8void run_add()
 9{ // public extern function
10  double d = add(12, 34.0f);
11  printf("%lf\n", d);
12}
 1fn add(a: i32, b: f32) -> f64 {
 2  // local function
 3  let d = a as f64  + b as f64;
 4  // last line without ";" is equal to "return d;"
 5  d
 6}
 7
 8pub fn run_add() {
 9  let d = add(12, 34.0);
10  println!("{}", d);
11}

Objekte

  • In C++ werden Klassen definiert aus denen dann Objektinstanzen zur Laufzeit erzeugt werden.
  • Objektinstanzen einer Klasse können Membervariablen verwalten.
  • Eine Konstruktorfunktion initialisiert eine Objektinstanz.
  • Klassenmethoden können statisch (Klassen-bezogen, siehe get_type_description) und nicht-statisch (Objekt-bezogen) sein.
  • Nicht-statische Methoden können als const qualifiziert sein und dürfen den Objektinhalt nicht ändern (siehe get_foo). Nicht-const Methoden dürfen den Inhalt ändern (siehe set_bar).
 1//C++
 2class Thing
 3{
 4private:
 5  int m_foo;
 6  int m_bar;
 7public:
 8  Thing(int foo)
 9  : m_foo(foo),
10    m_bar(0)
11  {    
12  }
13  static char const* get_type_description()
14  {
15    return "This is a Thing type.";
16  }
17  int get_foo() const
18  {
19    return this->m_foo;
20  }
21  int get_bar() const
22  {
23    return this->m_bar;
24  }
25  void set_bar(int value)
26  {
27    m_foo = value;
28  }
29};
30int use_thing()
31{
32  int foo = 12;
33  Thing thing(foo);
34  thing.set_bar(13);
35  int n = thing.get_foo() + thing.get_bar();
36  printf("n = %d\n", n);
37  return 0;
38}
  • Rust kennt keine Klassen, sonder definiert structs für zusammengehörige Variablen.
  • Für structs werden Funktionen implementiert (impl), die frei assoziiert sind (siehe get_type_description), oder als Methode an eine struct Instanz gebunden sind (siehe get_foo).
  • Erzeugt werden struct Instanzen häufig über eine assoziierte new Funktion, die das Objekt initialisiert. Die Erzeugung kann innerhalb eines Rust Modules aber auch direkt als Struct { member: initvalue, member2: initvalue2 } erfolgen.
  • Methoden dürfen den Inhalt der struct nur ändern, wenn sie als mut auf die self Referenz zugreifen (siehe set_bar), ansonsten dürfen die Membervariablen nur gelesen werden (siehe get_bar).
 1//Rust
 2struct Thing {
 3  m_foo: i32,
 4  m_bar: i32
 5}
 6
 7impl Thing {
 8  pub fn new(foo: i32) -> Self {
 9    // return a new constructed Thing
10    Self {
11      m_foo: foo,
12      m_bar: 0
13    }
14  }
15  pub fn get_type_description() -> &'static str {
16    // return a static immutable string reference
17    "This is a Thing type."
18  }
19
20  pub fn get_foo(&self) -> i32 {
21    // does not mutate object
22    // full return statement
23    return self.m_foo;
24  }
25  pub fn get_bar(&self) -> i32 {
26    // does not mutate object
27    // short return statement
28    self.m_bar
29  }
30  pub fn set_bar(&mut self, bar: i32) {
31    // mutates object
32    self.m_bar = bar;
33  }
34}
35
36pub fn use_thing() {
37  let foo = 12;
38  let mut thing = Thing::new(foo);
39  thing.set_bar(13);
40  let n = thing.get_foo() + thing.get_bar();
41  println!("Description = {}", Thing::get_type_description());
42  println!("n = {}", n);
43}
44

Interfaces

In C++ werden Schnittstellen und Polymorphismus durch virtuell Methoden in Klassen definiert. Implementiert werden Schnittstellen dann durch Ableitung einer neuen Klasse von der Interface-Klasse.

 1class Animal
 2{
 3public:
 4  virtual ~Animal() {}
 5  virtual std::string get_name() const = 0;
 6  virtual void speak() const = 0;
 7};
 8class Cat : public Animal
 9{
10private:
11  std::string m_name;
12public:
13  Cat(char const* name)
14  : m_name(name)
15  {
16  }
17  std::string get_name() const override
18  {
19    return m_name;
20  }
21  void speak() const override
22  {
23    printf("Meow\n");
24  }
25};
26class Dog : public Animal
27{
28private:
29  std::string m_name;
30public:
31  Dog(char const* name)
32  : m_name(name)
33  {
34  }
35  std::string get_name() const override
36  {
37    return m_name;
38  }
39  void speak() const override
40  {
41    printf("Meow\n");
42  }
43};
44static void talk_with_animal(Animal const& animal) 
45{
46  std::cout << "Animals's name is: " << animal.get_name();
47  animal.speak();
48}
49
50void handle_animals() 
51{
52  Cat c("Kitty");
53  Dog d("Sparky");
54  
55  talk_with_animal(c);
56  talk_with_animal(d);
57}

In Rust kann jede Struktur über das dyn Schlüsselwort zu einer Schnittstelle gemacht werden. Aufrufe von Methoden eines dyn Objektes werden “dynamisch” durchgeführt. Ähnlich wie bei C++ werden dann Methodentabellen eingesetzt anstatt die Implementierungen direkt aufzurufen.
Auf diese Weise lassen sich polymorphe Zugriffe umsetzen.

 1pub trait Animal {
 2  fn get_name(&self) -> String;
 3  fn speak(&self);
 4}
 5
 6struct Cat {
 7  m_name: String
 8}
 9
10impl Cat {
11  pub fn new(name: &str) -> Self {
12    Self {
13      m_name: name.to_string()
14    }
15  }
16}
17
18impl Animal for Cat {
19  fn get_name(&self) -> String {
20    self.m_name.clone()
21  }
22  fn speak(&self) {
23    println!("Meow");
24  }
25}
26
27struct Dog {
28  m_name: String
29}
30
31impl Dog {
32  pub fn new(name: &str) -> Self {
33    Self {
34      m_name: name.to_string()
35    }
36  }
37}
38
39impl Animal for Dog {
40  fn get_name(&self) -> String {
41    self.m_name.clone()
42  }
43  fn speak(&self) {
44    println!("Woof");
45  }
46}
47
48fn talk_with_animal(animal: &dyn Animal) {
49  println!("Animals's name is: {}", animal.get_name());
50  animal.speak();
51}
52
53pub fn handle_animals() {
54  let c = Cat::new("Kitty");
55  let d = Dog::new("Sparky");
56
57  let a: &dyn Animal = &c;
58  talk_with_animal(a);
59
60  let a: &dyn Animal = &d;
61  talk_with_animal(a);
62}
63

Operatoren für Typen

In C++ können für alle (nicht-primitiven) Typen Operatorfunktionen überladen werden. Somit lassen sich Objekte mit +, -, *, / wie auch anderen Zeichen verknüpfen. Operatorfunktionen können entweder in einer Klasse oder global definiert werden. Wird operator+(T, T) für einen Typen implementiert, kann im Code
T t3 = t1 + t2; den entsprechenden Additionscode aufrufen.

 1//C++
 2struct Point
 3{
 4  float x;
 5  float y;
 6
 7  Point(float xx, float yy) 
 8  : x(xx), y(yy)
 9  {    
10  }
11}
12
13Point operator+(Point const& l, Point const& r)
14{
15  return Point(l.x + r.x, l.y + r.y);
16}
17Point& operator+=(Point& l, Point const& r)
18{
19  l.x += r.x;
20  l.y += r.y;
21  return l;
22}
23
24Point operator-(Point const& l, Point const& r)
25{
26  return Point(l.x - r.x, l.y - r.y);
27}
28Point& operator-=(Point& l, Point const& r)
29{
30  l.x -= r.x;
31  l.y -= r.y;
32  return l;
33}
34
35Point operator*(Point const& l, float const& factor)
36{
37  return Point(l.x * factor, l.y * factor);
38}
39Point& operator*=(Point& l, float const& factor)
40{
41  l.x *= factor;
42  l.y *= factor;
43  return l;
44}
45
46Point operator/(Point const& l, float const& denominator)
47{
48  return Point(l.x / denominator, l.y / denominator);
49}
50Point& operator/=(Point& l, float const& denominator)
51{
52  l.x /= denominator;
53  l.y /= denominator;
54  return l;
55}
56

In Rust werden Operatoren durch Traits implementiert. Diese liegen in std:ops. Wird das Add Trait für einen Typen T implementiert, kann im Code per
let t3 = t1 + t2; der implementierte Additionscode ausgeführt werden.

  1// Rust
  2use std::ops::Add;
  3use std::ops::AddAssign;
  4use std::ops::Sub;
  5use std::ops::SubAssign;
  6use std::ops::Mul;
  7use std::ops::MulAssign;
  8use std::ops::Div;
  9use std::ops::DivAssign;
 10
 11// struct must derive Copy and Clone, 
 12// otherwise operator parameters are consumed and cannot be used again
 13#[derive(Copy, Clone)]
 14pub struct Point {
 15  pub x: f32,
 16  pub y: f32
 17}
 18
 19impl Point {
 20  pub fn new(x: f32, y: f32) -> Self {
 21    Self { 
 22      x: x, 
 23      y: y
 24    }
 25  }
 26}
 27
 28impl Add for Point {
 29  type Output = Point;    
 30  fn add(self, other: Self) -> Self::Output {
 31    Point::new(self.x + other.x, self.y + other.y)
 32  }
 33}
 34impl AddAssign for Point {
 35  fn add_assign(&mut self, other: Self) {
 36    self.x += other.x;
 37    self.y += other.y;
 38  }
 39}
 40impl Sub for Point {
 41  type Output = Self;
 42      
 43  fn sub(self, other: Self) -> Self::Output {
 44    Point::new(self.x - other.x, self.y - other.y)
 45  }
 46}
 47impl SubAssign for Point {
 48  fn sub_assign(&mut self, other: Self) {
 49    self.x -= other.x;
 50    self.y -= other.y;
 51  }
 52}
 53impl Mul<f32> for Point {
 54  type Output = Self;
 55      
 56  fn mul(self, factor: f32) -> Self::Output {
 57    Point::new(self.x * factor, self.y * factor)
 58  }
 59}
 60impl MulAssign<f32> for Point {
 61  fn mul_assign(&mut self, factor: f32) {
 62    self.x *= factor;
 63    self.y *= factor;
 64  }
 65}
 66impl Div<f32> for Point {
 67  type Output = Self;
 68      
 69  fn div(self, denominator: f32) -> Self::Output {
 70    Point::new(self.x / denominator, self.y / denominator)
 71  }
 72}
 73impl DivAssign<f32> for Point {
 74  fn div_assign(&mut self, denominator: f32) {
 75    self.x /= denominator;
 76    self.y /= denominator;
 77  }
 78}
 79        
 80pub fn use_point_operators() {
 81  let p1 = Point::new(1.0, 2.0);
 82  let p2 = Point::new(2.0, 3.0);
 83    
 84  let p3 = p1 + p2;
 85  assert_eq!(p3.x, p1.x + p2.x);
 86  assert_eq!(p3.y, p1.y + p2.y);
 87
 88  let p4 = p2 - p1;
 89  assert_eq!(p4.x, p2.x - p1.x);
 90  assert_eq!(p4.y, p2.y - p1.y);
 91
 92  let p5 = p2 * 10.0;
 93  assert_eq!(p5.x, p2.x * 10.0);
 94  assert_eq!(p5.y, p2.y * 10.0);
 95
 96  let p6 = p5 / 5.0;
 97  assert_eq!(p6.x, p5.x / 5.0);
 98  assert_eq!(p6.y, p5.y / 5.0);
 99}

Fortsetzung folgt