- Variablen sind standardmäßig unveränderbar und müssen mit dem Keyword mutmarkiert werden
- An vielen Stellen hilft es die Variable einfach neu zu definieren
fn main() {
    let a = 1;
    // Hier scheitert der Kompiler
    a = 2;
    // mut definiert eine Variable als veränderbar
    let mut b = 1;
    // damit ist dies möglich
    b = 2;
    // Eine Neudefinition ist ebenfalls möglich
    let c = 1;
    let c = 2;
    println!("a={}", a);
    println!("b={}", b);
    println!("c={}", c);
}
- Viele Typen werden automatisch erschloßen
- Für ganze Zahlen wird automatisch ein 32-Bit signed Integer angenommen
- Für rationale Zahlen wird eine 32-Bit Fließkommazahl angenommen
- Explizite Typisierung meist nicht notwendig, da der Kompiler die Typen erschließen kann
fn main() {
    let integer: u16 = 1;
    let float: f32 = 1.23;
    let boolean: bool = false;
    let a_char: char = 'X';
    let void: () = ();
    let tuple: (u8, char) = (1, 'a');
    let simple_string: &str = "a char array";
    // Standardausgaben der einfachen Datentypen
    println!("integer = {}", integer);
    println!("float = {}", float);
    println!("char = {}", a_char);
    println!("bool = {}", boolean);
    println!("simple string = {}", simple_string);
    // Ein komplexer String
    let complex_string: String = String::from("a more complex string");
    println!("complex_string = {}", complex_string);
    // Für manche ist die Ausgabe nicht verfügbar, dafür aber eine Debug-Ausgabe
    println!("void = {:?}", void);
    println!("tuple = {:?}", tuple);
    let a_huge_integer: i128 = 1_000_000_000_000_000_000_000_000_000_000;
    let a_signed_integer = -1;
    let a_precise_float: f64 = 1.055736284329874932749329479237492374;
    println!("a_huge_integer = {:?}", a_huge_integer);
    println!("a_signed_integer = {:?}", a_signed_integer);
    println!("a_precise_float = {:?}", a_precise_float);
}
- Verschiede größen sind vordefiniert wie unsigned/signed Integer für 8,16,32,64,128 oder Gleitkommatype für 32,64 Bit.
- Funktionen müssen typisiert werden
- die letzte Zeile gibt den Funktionswert zurück
- ist der letzte Zeile durch ein Semikolon beendet, ist der Typ leer
fn void_func() -> () {
    println!("void function");
}
fn another_void_func(value: &str) {
    println!("another void function: {}", value);
}
fn all_together(value: &str) -> usize {
    value.len()
}
fn main() {
    void_func();
    another_void_func("Hello CLT");
    println!("String length {}", all_together("Hello CLT"));
}
- Lambdas sind ebenfalls möglich
fn main() {
    let lambda = |x, y| x*y;
    println!("Lambda result {}", lambda(3, 4));
}
- Variablen können explizit typisiert werden
- Der Kompiler gibt ein Fehler aus, wenn
- ein Typ nicht bekannt ist
- mehrere mögliche Interpretationen gibt
- der Quell- und Ziel Typ nicht zusammen passen
 
- Gerade um den Kompiler bei der richtigen Implementierungswahl zu helfen, ist es of einfacher eine Variablendefintion zu Typisierungen
- Vielen Konvertieren können auch über Traits umgesetzt werden, dazu später mehr
fn main() {
    // Explizite Definition von Variablentypen
    let unsigned_16bit_integer = 1_u16; // Am Wert
    let signed_16bit_integer: i16 = -1; // An der Variablendeklaration
    // Funktionsdefition mit den spezifischen Datyentypen
    fn mul(a: i32, b: i32) -> i32 {
        a * b // ein expliztes return ist nicht notwendig
    }
    // Funktionsdefintion mit leeren Rückgabetyp, dies ist äquivalent
    // fn do_useless_add(a: i32, b: i32) -> () { ... }
    fn do_useless_add(a: i32, b: i32) {
        // Ohne Semikolon würde diese Zeile ein Fehler erzeugen
        a * b;
    }
    let a_result = mul(signed_16bit_integer as i32, unsigned_16bit_integer as i32);
    println!("Result of {} * {} = {}",
        signed_16bit_integer, unsigned_16bit_integer, a_result);
}
- Die meist verbreiteste Referenz dürfte die zu einem einfachen String sein
- Referenzen müssen verwendet werden, wo keine explizte Datentypgröße vorhanden ist bspw. bei generische Datentypen
- Für referenzierte Werte gelten ebenfalls die Regeln für Veränderlichkeit, d.h. per Standard sind die referenzierte Daten unveränderbar
fn main() {
    let a_string: &str = "Hello world";
    println!("a_string={}", a_string);
    let a_u32 = 2022;
    let ref_to_u32: &u32 = &a_u32;
    // Zugriff auf den Wert mittels Dereferenzierung
    println!("ref_to_u32={}", *ref_to_u32);
    // Integer ist veränderlich
    let mut a_mut_u32 = 2022;
    // Die Referenz selbst nicht, aber der referenzierte Wert
    let ref_to_mut_u32 = &mut a_mut_u32;
    // Damit kann auch der referenzierte Wert verändert werden
    *ref_to_mut_u32 = 42;
    println!("ref_to_mut_u32={}", *ref_to_mut_u32);
    // Referenzen an sich dürfen ebenfalls verändert werden
    let mut mut_ref_to_u32 = &1;
    mut_ref_to_u32 = &2;
    println!("mut_ref_to_u32={}", *mut_ref_to_u32);
}
- Wird eine Referenz genutzt, wird automatisch der Borrow Checker involviert und es stellt sich die Frage des Ownerships
- Arrays werden als slicesbezeichnet
- Rust folgt bei Zeichenketten der C++ Definition, es gibt einfache Zeichenketten ähnlich char-Arrays und komplexere Strings
- Hinweis: einfache Strings und Arrays können je nach Definition eine unbekannte Größe haben und müssen als Referenz behandelt werden
fn main() {
    let integer_array: [u8; 3] = [1u8, 2, 3];
    // Auch hier können wir nur eine Debug-Ausgabe durchführen
    println!("integer_array = {:?}", integer_array);
    println!("2nd element of integer_array = {}", integer_array[1]);
}
- Durch die statische Größe, kann der Kompiler auch Range Checks vorab durchführen
- Ein Zugriff außerhalb des Bereichs führt zu einem Kompilierfehler
fn main() {
    let integer_array: [u8; 3] = [1u8, 2, 3];
    integer_array[3];
}