Die Programmiersprache Rust ist im Vergleich zu anderen Sprachen einzigartig. Außerdem ist es äußerst einfach, mit nur wenigen Codezeilen leistungsstarke Anwendungen zu erstellen. Hier zeige ich Ihnen, wie einfach es ist, eine Anwendung in Rust zu erstellen, indem ich Ihnen zeige, wie Sie einen einfachen Rezeptmanager erstellen.
Rust einrichten
Bevor wir Rust nutzen können, müssen wir es auf unserem System installieren. Zum Glück können wir das in nur wenigen Schritten erledigen:
- Laden Sie Rustup herunter und installieren Sie es auf unserem Computer, wodurch wir Zugriff auf Fracht (den Rust-Paketmanager) erhalten.
- Richten Sie Visual Studio Code einmit Rust laufen lassen.
AlsIch habe festgestellt, dass die Verwendung von VS Code für Rust weitaus effizienter ist. Der Rust-Interpreter für VS Code wird weitaus häufiger aktualisiert. Der eigentliche Einrichtungsprozess ist viel detaillierter, aber ich werde hier nicht zu viel Zeit darauf verwenden, da die erwähnte Anleitung gute Arbeit leistet und Ihnen Schritt für Schritt zeigt, wie Sie Rust auf Ihrem System installieren.
Definieren unserer Programmanforderungen
Im Einklang mit den Standardstandards für den Programmentwicklungszyklus sollten wir definieren, was unser Programm tun muss, bevor wir mit der Codierung beginnen. Dies gibt uns einen Überblick darüber, was wir darin aufnehmen müssen. Daher sind hier die grundlegenden Funktionalitäten, die wir von unserem Programm benötigen:
- Fügen Sie neue Rezepte hinzu
- Alle Rezepte ansehen
- Sehen Sie sich ein bestimmtes Rezept an
- Bearbeiten Sie vorhandene Rezepte
- Rezepte löschen
- Speichern Sie Rezepte in einer Datei
- Laden Sie Rezepte aus einer Datei
Dies scheint ein recht einfaches Setup für dieses Programm zu sein, und es sollte nicht zu kompliziert sein, es zu schreiben. Lasst uns gleich eintauchen!
Einrichten unserer Ordner und Dateien
Als Erstes müssen wir einen Ordner für unser Projekt einrichten und ihn in einem Terminalfenster öffnen. Dies kann je nach Betriebssystem unterschiedlich sein, aber bei den meisten können wir mit der rechten Maustaste auf einen Ordner klicken und „Im Terminal öffnen“ wählen. Sobald wir das getan haben, sollten wir den Befehl eingeben:
cargo new recipe_managerDadurch wird ein neues Paket mit allen Abhängigkeiten erstellt, die wir zum Codieren in Rust benötigen. Sobald wir den Cargo-Befehl abgeschlossen haben, sollte das Terminalfenster wie folgt aussehen:

Als nächstes müssen wir unseren Ordner in VS Code öffnen. Dies ist so einfach wie das Öffnen von VS Code, das Klicken auf den Befehl „Datei“ und das Navigieren zu „Ordner öffnen“:

Dadurch erhalten wir ein Projektfenster, das in etwa so aussieht:

Sobald wir unseren Ordner eingerichtet haben, müssen wir ein paar Dateien erstellen, die die Hauptarbeit für unser Projekt erledigen. Da wir die Benutzeroberfläche, den App-Manager und die Rezeptdefinition in verschiedenen Dateien haben möchten, erstellen wir drei Dateien, die den Code für diese Elemente separat enthalten. Wir können dies tun, indem wir erneut auf das Menü „Datei“ klicken und „Neue Datei“ auswählen. Wir werden diese Dateien in „recipe.rs“, „manager.rs“ und „ui.rs“ umbenennen. Wenn wir fertig sind, sollte unser Projektfenster etwa so aussehen:

Wir sind mit der Einrichtung noch nicht fertig. Als Nächstes müssen wir die Datei „cargo.toml“ in unserem Ordner bearbeiten, die automatisch erstellt wurde, als Cargo den Ordner ursprünglich erstellte. Wir werden ein einfaches UI-System für unsere Anwendung implementieren (um sie optisch etwas ansprechender zu gestalten). Dazu müssen wir einige Abhängigkeiten ändern. Unsere Datei „cargo.toml“ sollte nun wie folgt aussehen:
[package]
name = "recipe_manager"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
iced = "0.8"In unserem Projekt gibt es drei Abhängigkeiten, aber zwei davon hängen zusammen. Serde ist eine Methode zur Serialisierung von Daten. Da es sich bei unseren Rezepten um einen eindeutigen Datentyp handelt, benötigen wir Serde, um sie zu speichern und zu laden. Iced ist ein einfacher GUI-Manager, der auf mehreren Betriebssystemen funktioniert. Wir verwenden es, da es einfach zu implementieren ist und mit fast jedem Betriebssystem funktioniert. Nachdem wir mit der Einrichtung fertig sind, können wir uns dem eigentlichen Spaß widmen: dem Codieren unserer Anwendung.
Erstellen des Rezeptskripts
Als Erstes müssen wir das Rezeptskript erstellen, das unsere Daten speichert. Navigieren Sie in VS Code zur Datei „recipe.rs“ und öffnen Sie sie. Die Datei sollte im linken Bereich leer angezeigt werden. Von dort aus schreiben wir diesen Code:
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Recipe {
pub id: u32,
pub name: String,
pub ingredients: Vec<String>,
pub instructions: Vec<String>,
pub servings: u32,
}
impl Recipe {
pub fn new(id: u32, name: String, ingredients: Vec<String>, instructions: Vec<String>, servings: u32) -> Self {
Recipe {
id,
name,
ingredients,
instructions,
servings,
}
}
}Mit diesem Code können wir eine öffentliche Struktur für unsere Rezepte erstellen, die Variablen zum Speichern der Portionsmenge, Anweisungen, Zutaten, Namen und einer eindeutigen ID als Referenz enthält. Wir haben auch eine Konstruktorfunktion, die uns hilft, neue Instanzen von Rezepten zu erstellen, sobald wir die Informationen erhalten, die wir in die Variablen einfügen können. Nachdem wir nun eine Rezeptstruktur haben, können wir mit der Erstellung des Anwendungsmanagers beginnen.
Erstellen des Anwendungsmanagers
Die Rezeptstruktur ermöglicht es uns, unsere Rezepte zu speichern, aber wir brauchen etwas, das sich um das eigentliche Speichern, Laden und Aktualisieren von Rezepten kümmert. Wenn wirWir haben das Speichern und Laden implementiert, aber in Rust ist dieser Prozess etwas anders. So sollte unser Anwendungsmanager (gespeichert in manager.rs in unserem Projektordner) aussehen:
use crate::recipe::Recipe;
use std::fs;
pub struct RecipeManager {
recipes: Vec<Recipe>,
next_id: u32,
}
impl RecipeManager {
pub fn new() -> Self {
RecipeManager {
recipes: Vec::new(),
next_id: 1,
}
}
pub fn add_recipe(&mut self, name: String, ingredients: Vec<String>, instructions: Vec<String>, servings: u32) -> u32 {
let id = self.next_id;
self.recipes.push(Recipe::new(id, name, ingredients, instructions, servings));
self.next_id += 1;
id
}
pub fn get_all_recipes(&self) -> &Vec<Recipe> {
&self.recipes
}
pub fn get_recipe(&self, id: u32) -> Option<&Recipe> {
self.recipes.iter().find(|r| r.id == id)
}
pub fn update_recipe(&mut self, id: u32, name: String, ingredients: Vec<String>, instructions: Vec<String>, servings: u32) -> bool {
if let Some(recipe) = self.recipes.iter_mut().find(|r| r.id == id) {
recipe.name = name;
recipe.ingredients = ingredients;
recipe.instructions = instructions;
recipe.servings = servings;
true
} else {
false
}
}
pub fn delete_recipe(&mut self, id: u32) -> bool {
let initial_len = self.recipes.len();
self.recipes.retain(|r| r.id != id);
self.recipes.len() < initial_len
}
pub fn save_to_file(&self, filename: &str) -> std::io::Result<()> {
let json = serde_json::to_string(&self.recipes)?;
fs::write(filename, json)
}
pub fn load_from_file(&mut self, filename: &str) -> std::io::Result<()> {
let json = fs::read_to_string(filename)?;
self.recipes = serde_json::from_str(&json)?;
self.next_id = self.recipes.iter().map(|r| r.id).max().unwrap_or(0) + 1;
Ok(())
}
}Wir beginnen zunächst mit dem Importieren unserer Rezeptstruktur, die wir zuvor codiert haben, und der fs-Bibliothek, um die Dateieingabe und -ausgabe (Lesen und Speichern von Dateien) zu verwalten. Direkt darunter definieren wir unseren Rezeptmanager und die Instanz. Wir können uns den Rezeptmanager als einen Behälter vorstellen, der alle unsere Rezepte enthält und organisiert hält.
Danach ist dieadd_recipeFunktion, die:
- Übernimmt Rezeptdetails als Parameter.
- Erstellt ein neues
Recipemit dem Stromnext_id. - Fügt das Rezept zum hinzu
recipesVektor. - Inkremente
next_idfür das nächste Rezept. - Gibt die ID des neu hinzugefügten Rezepts zurück.
Derget_all_recipesDie Funktion gibt einen Verweis auf die gesamte Rezeptliste zurück. Derget_recipeDie Funktion durchsucht die Liste der Rezepte und gibt eines basierend auf der ID zurück, die sie als Eingabe verwendet. Derupdate_recipeDie Funktion sucht nach einem Rezept und ermöglicht es uns, die Details dieses Rezepts zu aktualisieren und die neue Kopie zu speichern. Derdelete_recipeDie Funktion löscht das Rezept aus der Liste.
Zusätzlich zu diesen Funktionen verfügen wir über Dateioperationsfunktionen zum Speichern und Laden der Rezeptliste. Damit haben wir eine gute Grundlage für unser Programm, aber machen wir es noch etwas hübscher.
Implementierung der GUI
Eine einfache Befehlszeilen-Benutzeroberfläche könnte für dieses Programm funktionieren, aber es wird eine sehr langweilige Oberfläche sein. Lassen Sie es uns aufpeppen, indem wir etwas Code für unsere ui.rs-Datei schreiben:
use iced::widget::{Button, Column, Container, Row, Scrollable, Text, TextInput};
use iced::{Element, Length, Sandbox, Settings};
use iced::theme;
use iced::Color;
use crate::manager::RecipeManager;
use crate::recipe::Recipe;
pub struct RecipeManagerGUI {
recipe_manager: RecipeManager,
recipe_name: String,
recipe_ingredients: String,
recipe_instructions: String,
recipe_servings: String,
selected_recipe: Option<Recipe>,
error_message: Option<String>,
editing: bool,
}
#[derive(Debug, Clone)]
pub enum Message {
AddRecipe,
EditRecipe(u32),
UpdateRecipe,
CancelEdit,
RecipeNameChanged(String),
RecipeIngredientsChanged(String),
RecipeInstructionsChanged(String),
RecipeServingsChanged(String),
RecipeSelected(Recipe),
DeleteRecipe(u32),
SaveRecipes,
LoadRecipes,
}
impl Sandbox for RecipeManagerGUI {
type Message = Message;
fn new() -> Self {
Self {
recipe_manager: RecipeManager::new(),
recipe_name: String::new(),
recipe_ingredients: String::new(),
recipe_instructions: String::new(),
recipe_servings: String::new(),
selected_recipe: None,
error_message: None,
editing: false,
}
}
fn title(&self) -> String {
String::from("Recipe Manager")
}
fn update(&mut self, message: Message) {
match message {
Message::AddRecipe => {
if !self.recipe_name.is_empty() {
let servings = self.recipe_servings.parse().unwrap_or(1);
self.recipe_manager.add_recipe(
self.recipe_name.clone(),
self.recipe_ingredients.split(',').map(String::from).collect(),
self.recipe_instructions.split('\n').map(String::from).collect(),
servings,
);
self.recipe_name.clear();
self.recipe_ingredients.clear();
self.recipe_instructions.clear();
self.recipe_servings.clear();
}
}
Message::EditRecipe(id) => {
if let Some(recipe) = self.recipe_manager.get_recipe(id) {
self.recipe_name = recipe.name.clone();
self.recipe_ingredients = recipe.ingredients.join(", ");
self.recipe_instructions = recipe.instructions.join("\n");
self.recipe_servings = recipe.servings.to_string();
self.selected_recipe = Some(recipe.clone());
self.editing = true;
}
}
Message::UpdateRecipe => {
if let Some(recipe) = &self.selected_recipe {
let servings = self.recipe_servings.parse().unwrap_or(recipe.servings);
self.recipe_manager.update_recipe(
recipe.id,
self.recipe_name.clone(),
self.recipe_ingredients.split(',').map(String::from).collect(),
self.recipe_instructions.split('\n').map(String::from).collect(),
servings,
);
self.editing = false;
self.selected_recipe = None;
}
}
Message::CancelEdit => {
self.editing = false;
self.recipe_name.clear();
self.recipe_ingredients.clear();
self.recipe_instructions.clear();
self.recipe_servings.clear();
}
Message::RecipeNameChanged(name) => {
self.recipe_name = name;
}
Message::RecipeIngredientsChanged(ingredients) => {
self.recipe_ingredients = ingredients;
}
Message::RecipeInstructionsChanged(instructions) => {
self.recipe_instructions = instructions;
}
Message::RecipeServingsChanged(servings) => {
self.recipe_servings = servings;
}
Message::RecipeSelected(recipe) => {
self.selected_recipe = Some(recipe);
self.editing = false;
}
Message::DeleteRecipe(id) => {
if self.recipe_manager.delete_recipe(id) {
self.selected_recipe = None;
}
}
Message::SaveRecipes => {
if let Err(e) = self.recipe_manager.save_to_file("recipes.json") {
self.error_message = Some(format!("Failed to save recipes: {}", e));
}
}
Message::LoadRecipes => {
match self.recipe_manager.load_from_file("recipes.json") {
Ok(_) => self.selected_recipe = None,
Err(e) => self.error_message = Some(format!("Failed to load recipes: {}", e)),
}
}
}
}
fn view(&self) -> Element<Message> {
let recipe_form = Column::new()
.push(TextInput::new(
"Enter recipe name...",
&self.recipe_name,
Message::RecipeNameChanged,
))
.push(TextInput::new(
"Enter ingredients (comma-separated)...",
&self.recipe_ingredients,
Message::RecipeIngredientsChanged,
))
.push(TextInput::new(
"Enter instructions (line-separated)...",
&self.recipe_instructions,
Message::RecipeInstructionsChanged,
))
.push(TextInput::new(
"Enter number of servings...",
&self.recipe_servings,
Message::RecipeServingsChanged,
))
.push(
if self.editing {
Button::new(Text::new("Update Recipe")).on_press(Message::UpdateRecipe)
} else {
Button::new(Text::new("Add Recipe")).on_press(Message::AddRecipe)
}
)
.push(
if self.editing {
Button::new(Text::new("Cancel")).on_press(Message::CancelEdit)
} else {
Button::new(Text::new(""))
}
);
let recipes: Element<_> = self
.recipe_manager
.get_all_recipes()
.iter()
.fold(Column::new().spacing(10), |column, recipe| {
column.push(
Row::new()
.push(
Button::new(Text::new(&recipe.name))
.on_press(Message::RecipeSelected(recipe.clone()))
)
.push(
Button::new(Text::new("Edit"))
.on_press(Message::EditRecipe(recipe.id))
)
.push(
Button::new(Text::new("Delete"))
.on_press(Message::DeleteRecipe(recipe.id))
)
)
})
.into();
let recipes_list = Scrollable::new(recipes)
.height(Length::Fill);
let recipe_detail = self.selected_recipe.as_ref().map_or(
Column::new().push(Text::new("No recipe selected")),
|recipe| {
Column::new()
.push(Text::new(&recipe.name).size(24))
.push(Text::new(format!("Servings: {}", recipe.servings)))
.push(Text::new("Ingredients:"))
.push(Text::new(recipe.ingredients.join(", ")))
.push(Text::new("Instructions:"))
.push(Text::new(recipe.instructions.join("\n")))
},
);
let control_buttons = Row::new()
.push(Button::new(Text::new("Save Recipes")).on_press(Message::SaveRecipes))
.push(Button::new(Text::new("Load Recipes")).on_press(Message::LoadRecipes));
let error_display = if let Some(error) = &self.error_message {
Text::new(error).style(theme::Text::Color(Color::from_rgb(1.0, 0.0, 0.0)))
} else {
Text::new("")
};
let content = Column::new()
.push(recipe_form)
.push(recipes_list)
.push(recipe_detail)
.push(control_buttons)
.push(error_display);
Container::new(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.into()
}
}
pub fn run() -> iced::Result {
RecipeManagerGUI::run(Settings::default())
}Sobald wir Iced in unsere Datei importiert haben, können wir die Benutzeroberfläche dafür entwerfen. Die Benutzeroberfläche führt zu einer anderen Benutzererfahrung und, sie hängen zusammen. Der Code in unserer UI-Datei wird unseren Benutzern ein angenehmes Erlebnis bieten und sie dazu ermutigen, unser Programm für die Speicherung ihrer Rezepte zu verwenden. Es erleichtert uns auch das Leben, da die Navigation in einer Benutzeroberfläche hundertmal einfacher ist als in einem Terminalfenster. Jede Anwendung ist anders, daher sollten Sie mit dem UI-Layout experimentieren, bis Sie eine finden, die Ihnen gefällt. Dieses Layout funktioniert für mich, also schauen wir nun, ob unser Code ausgeführt wird.
Einrichten von main.rs und Ausführen unseres Codes
Im Ordner „src“ unseres Hauptordners „recipe_manager“ befindet sich die Datei „main.rs“. Wir werden den vorhandenen Code damit überschreiben:
mod recipe;
mod manager;
mod ui;
fn main() -> iced::Result {
ui::run()
}Unser Dolmetscher wird sich sofort darüber beschweren, dass er Rezept, Manager oder Benutzeroberfläche nicht finden kann, aber dafür gibt es eine einfache Lösung. Wählen Sie einfach alle diese Dateien aus dem Hauptordner aus und ziehen Sie sie per Drag & Drop in den src-Ordner. Die Datei „main.rs“ erkennt keine Dateien, die sich außerhalb ihres eigenen Ordners befinden. Dies ist also eine einfache Lösung für dieses Problem. Sobald das Problem gelöst ist, können wir versuchen, unseren Code auszuführen und zu sehen, ob er funktioniert.
Öffnen Sie Ihren Projektordner in einem Terminal und geben Sie Folgendes ein:
cargo run
Sie sollten sehen, wie Rust Ihr Projekt erstellt. Wenn Sie auf Fehler stoßen, stellen Sie sicher, dass Ihr Rust auf dem neuesten Stand ist, indem Sie ein Terminalfenster öffnen und Folgendes eingeben:
rustup update
Warten Sie dann, bis der Download des Updates abgeschlossen ist, und führen Sie das Programm erneut aus. Unser finaler Rezeptmanager sieht in etwa so aus:

Den vollständigen Code für dieses Projekt finden Sie wie immer auf meiner SeiteGitHub. Dieses Projekt ist ziemlich einfach, aber Sie können einige Dinge selbst tun, damit es besser aussieht. Daraus lässt sich möglicherweise ein robuster Rezeptmanager entwickeln. Am Ende der Erstellung dieses Rezeptmanagers sollten Sie eine gute Vorstellung davon haben, wozu Rust fähig ist.
