Dies ist eine alte Version des Dokuments!
Inhaltsverzeichnis
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 - Rust is a systems programming language that runs blazingly fast, prevents almost all crashes*, and eliminates data races.
-
- https://users.rust-lang.org/ - User-Forum (englisch)
-
- https://chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust-de - Deutscher Chat-Kanal
-
- Module und Bibliotheken
- Programmiersprache: Rust erreicht "Hunderte Millionen" Endnutzer - Nur rund zwei Jahre nach der ersten stabilen Version erreicht die Sprache Rust dank Dropbox, Mozilla und Gnome bereits "Hunderte Millionen" Endnutzer. Trotz dieser überzeugenden Zahlen soll die Sprache leichter genutzt werden können.
- Weniger Frust mit Rust - 25. Februar 2021, 7:00 Uhr - Die Programmiersprache Rust macht nicht nur weniger Fehler, sie findet sie auch früher.
-
-
-
- Seit der initialen Schöpfung hat das Projekt mehrfach das Team und die Ausrichtung gewechselt, aber inzwischen seine Nische gefunden. War die Sprache zuerst zur verteilten Programmierung gedacht, hat sie sich inzwischen weit davon entfernt und tritt eher als moderner C/C++-Ersatz an. Der Fokus auf parallele Programmierung und einem dazu geeigneten, auf "unique pointers" basierenden Typsystem ist jedoch geblieben. Die Sprache versucht den Spagat zwischen maschinennaher Programmierung und leistungsfähigen Abstraktionen. Der Name Rust stammt interessanterweise nicht etwa von Rost, sondern von einer besonderen Pilzsorte.
-
- Warum eine MIT-ASL2 Doppellizenz?
- Die Apache-Lizenz enthält wichtigen Schutz gegen Patentaggressoren, aber ist mit der GPLv2 inkompatibel. Um Probleme bei der Verwendung von Rust mit der GPLv2-Lizenz zu vermeiden, ist es alternativ MIT-lizenziert.
- Einführung
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
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
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.
