Webseiten-Werkzeuge


rust

Rust

Eine erste stabile Version von Compiler und Standardbibliothek, Rust 1.0, wurde am 15. Mai 2015 veröffentlicht.
Die Entwicklerumfrage von StackOverflow 2017 fand heraus, dass Rust das zweite Jahr in Folge die beliebteste Programmiersprache ist, was ausgesprochen beeindruckend ist.

Rust nutzt als Backend das LLVM-Compilerframework. Damit läuft Rust-Code bereits jetzt auf vielen Architekturen. Der Rust-Code ist freie Software und steht unter einer Dual-Lizenz: Er kann entweder nach den Bedingungen der MIT- oder der Apache-Lizenz genutzt werden.

Kompromiss zwischen High- und Lowlevel-Programmierung

Bisher hatte es einerseits Highlevel-Programmiersprachen gegeben, in denen sich die Entwickler nicht um die Speicherverwaltung kümmern müssen, die aber in Sachen Performance nicht an C-/C++-Code heranreichen. Auf der anderen Seite gab es Lowlevel-Sprachen, deren Entwickler mit den bekannten Problemen der Speicherverwaltung kämpfen. „Rust ist eine Lowlevel-Programmiersprache, die sich oft wie eine Highlevel-Programmiersprache anfühlt“, fasste Rust-Entwickler Steve Klabnik im vergangenen Jahr auf der All-Things-Open-Konferenz die Besonderheit von Rust zusammen.

Rusts Ansatz ist dabei, möglichst viele Fehler bereits bei der Kompilierung zu entdecken. Die Sprache ist so designt, dass fehlerhafter und unsicherer Code in vielen Fällen überhaupt nicht kompilierbar ist und dem Programmierer eine Fehlermeldung liefert. Rust solle somit, so Klabnik, die Lowlevel-Programmierung für Anfänger zugänglicher und für bereits erfahrene Lowlevel-Programmierer sicherer machen.

Wie fange ich an?

FreeBSD

Der Standard-Weg geht nicht:

[fritz@freebsd ~]$ wget -O - https://sh.rustup.rs | sh
info: downloading component 'rustfmt'
info: installing component 'cargo'
error: error: 'sysinfo not supported on this platform'
info: using up to 500.0 MiB of RAM to unpack components
info: installing component 'clippy'
error: error: 'sysinfo not supported on this platform'
info: installing component 'rust-std'
error: error: 'sysinfo not supported on this platform'
...
info: installing component 'rustc'
error: error: 'sysinfo not supported on this platform'
...
info: installing component 'rustfmt'
error: error: 'sysinfo not supported on this platform'
info: default toolchain set to 'stable-x86_64-unknown-freebsd'

  stable-x86_64-unknown-freebsd installed - rustc 1.51.0 (2fd73fabe 2021-03-23)

also machen wir es zu Fuß:

[root@freebsd ~]# cd /usr/ports/lang/rust && make clean && make && make install ; make clean

[fritz@freebsd ~]$ cargo install cargo-edit
[fritz@freebsd ~]$ cargo new projekt01

jetzt können wir auch Module hinzufügen:

[fritz@freebsd ~/projekt01]$ cargo add ffprobe
  Updating 'https://github.com/rust-lang/crates.io-index' index
    Adding ffprobe v0.1.0 to dependencies
[fritz@freebsd ~/projekt01]$ vi hallo_welt.rs
[fritz@freebsd ~/projekt01]$ cargo build
[fritz@freebsd ~/projekt01]$ vi src/main.rs
[fritz@freebsd ~/projekt01]$ cargo run

Linux

https://www.rust-lang.org/learn/get-started

> apt install cinnamon-desktop-environment xterm mc vim screen autofs pluma curl libssl-dev
> snap install code --classic
> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
> rustup component add rust-src

> code
  => ''German Language Pack for Visual Studio Code'' installieren
  => ''rust-analyzer'' installieren
  => ''CodeLLDB'' installieren

> cargo install cargo-edit

jetzt können wir auch Module hinzufügen:

> cargo add ffprobe

Beispiel-Programme

Notizen zu Rust

Buch: Programmierung sicherer Systeme mit Rust: Eine Einführung - vom 01. April 2020

Die folgenden Beispiele wurden alle mit rustc 1.48.0 auf FreeBSD 13.0 RELEASE getestet:

> rustc -V
rustc 1.48.0

> uname -a
FreeBSD erde.lan 13.0-RELEASE FreeBSD 13.0-RELEASE #6 releng/13.0-n244733-ea31abc261f: Sat Apr 10 08:09:33 CEST 2021     root@erde.lan:/usr/obj/usr/src/amd64.amd64/sys/MYKERNEL  amd64

allgemeines

Das Ausrufungszeichen hinter println! bedeutet, dass es sich hier um ein MAKRO handelt:

> vi hallo_welt.rs
fn main() {
    println!("Hallo Welt!");
}

> rustc hallo_welt.rs
> ls -lha
total 606
drwxr-xr-x    2 fritz  fritz     4B Apr 17 13:03 .
drwx------  137 fritz  fritz   459B Apr 17 13:05 ..
-rwxr-xr-x    1 fritz  fritz   392K Apr 17 13:03 hallo_welt
-rw-r--r--    1 fritz  fritz    43B Apr 17 13:03 hallo_welt.rs

> ./hallo_welt
Hallo Welt!

hier wird die Variable a nicht überschrieben, sondern überdeckt, das nennt sich shadowing:

fn main() {
    let a = 12;
    println!("a = {}", a);
    
    let a = 33;
    println!("a = {}", a);
}

das geschied durch das vorangestellte Schlüsselwort let, denn eine „normale“ Variable (ist eine „unveränderbare Variable“) kann man in Rust aus Sicherheitsgründen nicht überschrieben werden. Will man eine Variable, die sich verändern läßt, dann benötigt man noch das Schlüsselwort mut:

fn main() {
    let mut a = 12;
    println!("a = {}", a);
    
    a = 33;
    println!("a = {}", a);
}

und dann gibt es noch die „Konstanten“. Nur worin besteht der Unterschied zwischen „unveränderbare Variablen“ und „Konstanten“? „Konstanten“ können nicht mit dem Schlüsselwort mut benutzt werden und im Gegensatz zu „Variablen“, muß man ihnen immer einen Typ zuweisen.

fn main() {
    const PI: f32 = 3.1416;
    println!("Pi = {}", PI);
}

„Konstanten“ können auch global bekannt gemacht werden:

const PI: f32 = 3.1416;

fn main() {
    println!("Pi = {}", PI);
}

Ausgabe

println! gibt auf stdout aus;

eprintln! gibt auf stderr aus;

siehe auch:

Ausgabe_nach_stderr.txt
use std::io::{self, Write};
 
fn main() -> io::Result<()> {
    let stderr = io::stderr();
    let mut handle = stderr.lock();
 
    handle.write_all(b"Hallo Werlt\n")?;
 
    Ok(())
}

stderr auf stdout umleiten

Wenn man „.output()“ statt „.status()“ verwendet, dann kann man die Daten aus stderr auslesen.

stderr2stdout.rs
fn main() {
    let ausgabe = std::process::Command::new("ffprobe")
        .arg("-i")
        .arg("Film.mp4")
        .output()
        .expect("ffprobe konnte nicht ausgeführt werden");
    println!("beendet mit {}", ausgabe.status);
    let s = String::from_utf8_lossy(&ausgabe.stderr);
    println!("{}", s);
}
ffprobe_Stream.rs
fn main() {
    let ausgabe = std::process::Command::new("ffprobe")
        .arg("-i")
        .arg("Film.mp4")
        .output()
        .expect("process failed to execute");
    println!("completed with {}", ausgabe.status);
    let s = String::from_utf8_lossy(&ausgabe.stderr);
    for line in s.lines() {
        if line.starts_with("    Stream") {
            println!("{}", &line);
        }
    }
}

Hier werden Daten verarbeitet, die „ffprobe“ auf stderr ausgibt:

> rustc ffprobe_Stream.rs
> ./ffprobe_Stream
completed with exit code: 0
    Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, smpte170m), 500x376 [SAR 376:375 DAR 4:3], 1223 kb/s, 15 fps, 15 tbr, 15360 tbn, 30 tbc (default)

Datentypen

Ganze Zahlen mit Vorzeichen:
  i8, i16, i32, i64, i128, isize

Ganze Zahlen ohne Vorzeichen:
  u8, u16, u32, u64, u128, usize

Dezimalzahlen (Kommazahlen):
  f32, f64

Unicode-Zeichen:
  char

Wahrheitswerte:

Der promitive Typ "Unit" -> Dieser Typ hat genau einen Wert, nämlich das leere Tupel "()".
                            Dieser Wert wird dann benutzt, wenn kein anderer Wert zurückgegeben werden kann.
                            In anderen Programmiersprachen würde man den Wert "()" als "nil" oder "null" wiederfinden.
isize = -9223372036854775808..9223372036854775807
usize =                    0..18446744073709551615

Will man zwei Zahlen miteinander verarbeiten (vergleichen oder addieren), dann geht das nur, wenn beide Zahlen vom gleichen Typ sind. Deshalb bietet es sich an immer den größten Typ zu verwenden (außer man will speicherplatz sparen oder ein kleinwenig mehr Geschwindigkeit erzielen), und diesen dann im ganzen Code für alle Zahlen, die miteinander in Beziehung stehen, zu verwenden. So kann man sich Typumwandlungen sparen.

Im Bereich der Zahlen, die im Alltag üblich sind, stehen dann diese Datentypen u128, i128 und f64 zur Auswahl. Allerdings sollte man die Unterschiede in ihrer Genauigkeit kennen. u128 und i128 können bis zu 38 Stellen darstellen. f64 kann nur 15 Stellen genau darstellen, ab der 16. Stelle wird gerundet.

Beispielsweise gibt dieses Programm:

fn main() {
    let a: u128 = 99999999999999999999999999999999999999;
    let b: i128 = -99999999999999999999999999999999999999;
    let c: f64  = 9999999999999999.9;
    let d: f64  = 999999999999999.9;
    let e: f64  = 99999999999999.9;
    println!("a = {};\nb = {};\nc = {};\nd = {};\ne = {}", a, b, c, d, e);
}

diese Ausgabe zurück:

a = 99999999999999999999999999999999999999;
b = -99999999999999999999999999999999999999;
c = 10000000000000000;
d = 999999999999999.9;
e = 99999999999999.9

Weiterhin ist anzumerken, dass auf der Doku-Seite von Rust Module std:: steht, dass für die Module i8-i128 und u8-u128 eine Abkündigung geplant ist (Deprecation planned), so dass man als Standard-Zahlen-Typ im Alltag lieber f64 verwenden sollte. 15 Stellen Genauigkeit sollte für die meisten Fälle im Alltag auch ausreichen, das reicht sogar für viele Wissenschaftliche Berechnungen aus.

Arrays und Tupel

Arrays benötigen einen speziellen Platzhalter ({:?} für eine Ausgabe in einer Zeile oder {:#?} für eine Ausgabe in einer Spalte):

fn main() {
    let a = [5, 7, 12, 3241];
    println!("a = {:?}", a);
    println!("a = {:#?}", a);
}
fn main() {
    let a: [i32; 4] = [5, 7, 12, 3241];
    println!("Länge von a = {}", a.len());
    println!("1. Wert = {}", a[0]);
    println!("4. Wert = {}", a[3]);
}
fn main() {
    let a = [5, 7, 12, 3241];
    let [eins, zwei, drei, vier] = a;
    println!("{}, {}, {}, {}", eins, zwei, drei, vier);
    println!("eins={}, zwei{}, drei={}, vier={}", eins, zwei, drei, vier);
}

Tupel können, im Gegensatz zu Arrays, Elemente verschiedenen Datentyps enthalten:

fn main() {
    let t = (5, 7, 12, 3241);
    println!("t = {:?}", t);
    println!("4. Wert = {}", t.3);
}
fn main() {
    let t = (5, false, "Hallo");
    println!("t = {:?}", t);
    println!("3. Wert = {}", t.2);
}
fn main() {
    let t = (5, false, "Hallo");
    let (eins, zwei, drei) = t;
    println!("{}, {}, {}", eins, zwei, drei);
}

Slice

Der Slice-Typ

Ein anderer Datentyp, der keinen Besitz hat, ist das Slice. Mit Slice können Sie auf eine zusammenhängende Folge von Elementen in einer Sammlung verweisen, nicht auf die gesamte Sammlung.

test1.rs
fn main() {
    let vector = vec![1, 2, 3, 4, 5, 6, 7, 8];
    let slice = &vector[3..6];
    println!("length of slice: {}", slice.len()); // 3
    println!("slice: {:?}", slice); // [4, 5, 6]
}
test2.rs
fn string_slice(arg: &str) {
    println!("{}", arg);
}
 
fn string(arg: String) {
    println!("{}", arg);
}
 
fn main() {
    //string("blue");
    string_slice("blue");
 
    string("red".to_string());
    //string_slice("red".to_string());
 
    string(String::from("hi"));
    //string_slice(String::from("hi"));
 
    string("rust is fun!".to_owned());
    //string_slice("rust is fun!".to_owned());
 
    string("nice weather".into());
    string_slice("nice weather".into());
 
    string(format!("Interpolation {}", "Station"));
    //string_slice(format!("Interpolation {}", "Station"));
 
    //string(&String::from("abc")[0..1]);
    string_slice(&String::from("abc")[0..1]);
 
    //string("  hello there ".trim());
    string_slice("  hello there ".trim());
 
    string("Happy Monday!".to_string().replace("Mon", "Tues"));
    //string_slice("Happy Monday!".to_string().replace("Mon", "Tues"));
 
    string("mY sHiFt KeY iS sTiCkY".to_lowercase());
    //string_slice("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

Dateien anlegen, lesen, schreiben, überschreiben und löschen

Dateien öffnen und schließen in Rust

Rusts Kernfeature ist das sogenannte Ownership. Es beschreibt die Idee, dass jeder Datentyp gleichzeitig eine Resource darstellt. Rust löst das darüber, dass es jedem Stück Daten einen sogenannten Owner zuweist. Erstelle ich mit File::create eine Datei, bekomme ich im Erfolgsfall nicht nur ein File-Objekt zurück, sondern auch die Verantwortung des Managements. Ich besitze die Datei und bin der einzige Besitzer. Gebe ich den Besitz auf irgendeine Art auf, wird die Datei geschlossen. Daten in Rust haben also einen modellierten Lebenszyklus. Im Gegensatz zu vielen anderen Sprachen ist dieses Konzept keine Konvention, sondern einer der Grundpfeiler des Typsystems. Deswegen begegnet es einem auch sofort und muss gelernt werden - es führt kein Weg daran vorbei.

test_golem_1.rs
use std::fs::File;
use std::io::Write;
use std::io;
 
fn main() -> Result<(), io::Error> {
    let open_file = File::create("hello_golem.txt");
    match open_file {
        Ok(mut file) => {
            file.write_all(b"Hallo Golem!\n")
            // hier wird die Datei aufgegeben und damit geschlossen
        }
        Err(e) => {
            eprintln!("Datei konnte nicht erstellt werden. Fehler: {}", e);
            Err(e)
        }
    }
}

vector + for

for-Schleife.rs
use std::env;
//let mut aaa: Vec<_>;
 
fn main() {
    let vector: Vec<String> = env::args().collect();
    println!("{:?}", vector);
 
    println!("-----------------------------------");
 
    let l = vector.len();
    println!("Es sind {} Elemente im Vektor.", l);
 
    println!("===================================");
 
    for i in 0..l {
        println!("Das {}. Vektorelement: {}", i, &vector[i]);
    }
}

externe Programme aufrufen

command_1.rs
fn main() {
    std::process::Command::new("ls")
        .arg("-l")
        .arg("-h")
        .arg("-a")
        .status()
        .expect("ls command failed to start");
}
command_2.rs
fn main() {
    use std::process::Command;
 
    Command::new("ls")
        .arg("-l")
        .arg("-h")
        .arg("-a")
        .spawn()
        .expect("ls command failed to start");
}
command_3.rs
fn main() {
    use std::process::Command;
 
    let mut child = Command::new("/bin/hostname")
        .arg("-s")
        .spawn()
        .expect("failed to execute child");
 
    let ecode = child.wait()
        .expect("failed to wait on child");
 
    assert!(ecode.success());
}
command_4.rs
fn main() {
    use std::process::Command;
 
    let mut list_dir = Command::new("ls");
 
    // Execute `ls` in the current directory of the program.
    list_dir.status().expect("process failed to execute");
 
    println!();
 
    // Change `ls` to execute in the root directory.
    list_dir.current_dir("/");
 
    // And then execute `ls` again but in the root directory.
    list_dir.status().expect("process failed to execute");
}

aus den FAQs

    • Ein wichtiges Detail ist, dass der Rückgabetyp einer Funktion, welche mit einem Semikolon endet, () ist. Dies deutet an, dass kein Wert zurückgegeben wird. Implizite Rückgaben funktionieren nur ohne abschließendes Semikolon, da sonst der Wert des Ausdrucks unterdrückt wird.
rust.txt · Zuletzt geändert: 2021/05/16 00:56 von manfred