Weil ich dem Sirenengesang Performance – Reliability – Productivity
gegenüber sehr aufgeschlossen bin, die kurze Haskell-Affäre vor vielen Jahren wenig erfüllend war und die Beschränkung auf eine einzige (wenn auch populäre) Programmiersprache die (durchaus berechtigten) Zweifel an meiner technischen Satisfaktionsfähigkeit kräftig schürt, befasse ich mich seit einigen Tagen mit Rust. Die Referenz-Einführung (the book
) bietet nicht nur ein passendes Lerntempo, sondern auch die richtige Mischung von Theorie und Praxis, und so kann ich am Ende des zweiten Kapitels drei Beispielaufgaben lösen.
Weltweit bestehen nur noch sehr wenige Länder darauf, Temperaturen ausschließlich in Fahrenheit anzugeben, und keines davon ist das Vereinigte Königreich. Trotzdem bleibt die Umrechnung zwischen Fahrenheit zu Celsius eine beliebte Übung. Mein Skript erwartet die Eingabe eines Zahlenwertes und der Maßeinheit:
use std::io; fn to_celsius(fahrenheit_value: f32) { let celsius_value = (fahrenheit_value - 32.0) * 5.0 / 9.0; println!("{fahrenheit_value} Grad Fahrenheit entspricht {celsius_value} Grad Celsius."); } fn to_fahrenheit(celsius_value: f32) { let fahrenheit_value = (celsius_value * 9.0 / 5.0) + 32.0; println!("{celsius_value} Grad Celsius entspricht {fahrenheit_value} Grad Fahrenheit."); } fn main() { loop { let mut input = String::new(); println!("Bitte Temperaturwert und Ausgangseinheit – [F]ahrenheit oder [C]elsius – eingeben!"); io::stdin().read_line(&mut input).expect("Lesefehler!"); let mut splitted_input = input.split_whitespace(); let temperature_value = match splitted_input.next() { Some(value) => value, None => continue, }; let temperature_value: f32 = match temperature_value.parse() { Ok(num) => num, Err(_) => continue, }; let unit = splitted_input.next(); match unit { Some("F") => to_celsius(temperature_value), Some("C") => to_fahrenheit(temperature_value), Some(&_) => { println!("Ungültige Ausgangseinheit!"); } None => { println!("Fehlende Ausgangseinheit!"); } } break; } }
Die Eingabe wird als Zeichenkette (vom Typ String
) gespeichert und in zwei Elemente aufgeteilt. Das erste Element wird mit Hilfe von zwei match
-Ausdrücken daraufhin geprüft, ob es nicht leer ist und in einen Fließkommawert umgewandelt werden kann. Falls einer der beiden Schritte nicht zum Erfolg führt, wird die Schleife erneut ausgeführt – und die Nutzerin wieder zur Eingabe von Temperatur und Maßeinheit aufgefordert. Fehlt dagegen die Maßeinheit, oder besteht sie nicht aus den Zeichenketten C bzw. F, gibt das Skript eine Fehlermeldung aus.
Bei der Berechnung der n-ten Fibonacci-Zahl verzichte ich auf Nutzereingaben und lasse einfach die 150. Zahl (rund 16,1 Quintillionen, genau: 16.130.531.424.904.581.415.797.907.386.349) berechnen:
fn main() { let mut left = 1; let mut right = 1; let mut result: u128 = 0; let target = 150; let mut counter = 1; while target > counter { result = left + right; left = right; right = result; counter += 1; } println!("The {target}th Fibonacci number is {result}"); }
Mit diesem simplen Skript lässt sich das Leistungsversprechen von Rust (im Vergleich mit Python) testen, indem man den obigen Code 10 Millionen mal ausführen lässt. Python benötigt 165,45 Sekunden – und Rust 0,62 (bei sehr viel geringerem Einsatz von Speicherkapazität und Rechenzyklen).
Die dritte Aufgabe besteht darin, den Text eines sehr repetitiven Weihnachtsliedes vollständig auszuschreiben.
fn main() { let material = [ ("first", "and a partridge in the pear tree."), ("second", "two turtle doves"), ("third", "three French hens"), ("fourth", "four calling birds"), ("fifth", "five gold rings"), ("sixth", "six geese a-laying"), ("seventh", "seven swans a-swimming"), ("eight", "eight maids a-milking"), ("ninth", "nine ladies dancing"), ("tenth", "ten lords a-leaping"), ("eleventh", "eleven pipers piping"), ("twelfth", "twelve drummers drumming") ]; let mut storage = String::new(); for (day, gift) in material { println!("On the {day} day of christmas, my true love sent to me\n{gifted}", gifted=gift.strip_prefix("and ").unwrap_or(gift)); println!("{storage}"); storage = gift.to_owned() + "\n" + &storage; } }
Abgesehen von der Methode strip_prefix()
, mit der das initiale and
bei der ersten Verwendung der Zeile and a partridge in the pear tree
entfernt wird, ist nur die letzte Zeile etwas erklärungsbedürftig, denn:
When using strings, Rust beginners puzzle over
String
vs.&str
, and Haskell beginners puzzle overString
vs.Text
vs.ByteString
.
Das kann ich sowohl für Haskell als auch für Rust bestätigen. Was geht in der Codezeile vor? Die Add
-Methode verwendet den Buffer des ersten Arguments vom Typ String
(owned), kopiert den Inhalt des zweiten Arguments vom Typ &str
(borrowed) in diesen Buffer und liefert den erweiterten String
zurück. Entsprechend muss die Variable gift
(Typ &str
) in einen String
umgewandelt werden, während storage
als &storage
(Typ &str
) den umgekehrten Weg geht. Um Ownership, References und Borrowing für einen Moment auszublenden, lässt sich alternativ insert_str()
zweimal aufrufen:
storage.insert_str(0, "\n"); storage.insert_str(0, gift);