diff --git a/docs/desktop_applications/01_Overview/Introduction.md b/docs/desktop_applications/01_Overview/Introduction.md new file mode 100644 index 0000000..0547dba --- /dev/null +++ b/docs/desktop_applications/01_Overview/Introduction.md @@ -0,0 +1,29 @@ + + +Welcome to **Desktop Applications with Rust & Slint**! +This week, you will learn to create **desktop applications**, using **Rust** for the business logic and **Slint** for the graphical interface. + +The course is structured to gradually take you from the fundamentals of Rust, to UI integration, and finally to completing a full project that you will present at the end of the camp. + +--- + +## Course Tutors +- **Precup Cristiana** +- **Robert Dincă** + +--- + +## Course Structure + +1. **Rust for Desktop Apps** – Learn Rust fundamentals, data modeling, and local data storage +2. **Building UIs with Slint** – Create interactive desktop interfaces and handle user events +3. **Networking & APIs** – Fetch and display external data in your applications +4. **Final Project** – Build and present a complete desktop application + +--- + +## What is this course about? + +This course is about **building real desktop applications** from start to finish. + +You will take an idea, turn it into a working app with an interface, and by the end, you’ll have a **complete desktop application** you can run and showcase. diff --git a/docs/desktop_applications/02_Rust/01_-Installing-and-configuring.md b/docs/desktop_applications/02_Rust/01_-Installing-and-configuring.md new file mode 100644 index 0000000..dc35529 --- /dev/null +++ b/docs/desktop_applications/02_Rust/01_-Installing-and-configuring.md @@ -0,0 +1,47 @@ +--- +title: Installing and configuring +--- + +### Setting Up the Rust Development Environment + +Alright, let's get your computer ready to write some Rust code\! The easiest and official way to install Rust is using `rustup`. + +1. **Open Your Terminal/Command Prompt:** + + - **Linux/macOS:** Open your regular terminal application. + - **Windows:** Open PowerShell or Command Prompt. (If you're using VS Code, its integrated terminal works great\!) + +2. **Install `rustup`:** + + - **Linux/macOS:** Copy and paste this command into your terminal and press Enter. Follow the on-screen prompts (usually just pressing Enter for default options). + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + - **Windows:** + - Go to the official `rustup` download page: [https://rustup.rs/](https://rustup.rs/) + - Download the appropriate installer for your system (e.g., `rustup-init.exe` for 64-bit Windows). + - Run the installer and follow the instructions. Choose the default installation options. + +3. **Configure Your Shell (Important for Linux/macOS):** + + - After `rustup` finishes on Linux/macOS, it will often tell you to run a command to add Rust to your system's `PATH`. This is usually: + ```bash + source "$HOME/.cargo/env" + ``` + - Run this command in your _current_ terminal session. To make it permanent, you might need to add it to your shell's configuration file (like `.bashrc`, `.zshrc`, or `.profile`). + +4. **Verify Installation:** + + - Close and reopen your terminal/command prompt (or run the `source` command). + - Type these commands to check if Rust and Cargo (Rust's build tool, which comes with `rustup`) are installed correctly: + ```bash + rustc --version + cargo --version + ``` + - You should see version numbers printed for both `rustc` (the Rust compiler) and `cargo`. If you do, congratulations, Rust is installed\! + +5. **Install VS Code (Recommended IDE):** + - If you don't have it already, download and install Visual Studio Code: [https://code.visualstudio.com/](https://code.visualstudio.com/) +6. **Install the `Rust Analyzer` Extension:** This is crucial for a great Rust development experience in VS Code (code completion, error checking, formatting, etc.). Open VS Code, go to the Extensions view (Ctrl+Shift+X or Cmd+Shift+X), search for "Rust Analyzer", and install it. + +7. **Install the `Slint` Extension:** This is recommanded for a better development experience, featuring auto-complition, go-to definition, refactoring, syntax coloration, and a live preview and editing of Slint GUIs. diff --git a/docs/desktop_applications/02_Rust/02_Project-structure.md b/docs/desktop_applications/02_Rust/02_Project-structure.md new file mode 100644 index 0000000..5772287 --- /dev/null +++ b/docs/desktop_applications/02_Rust/02_Project-structure.md @@ -0,0 +1,48 @@ +--- +title: Project Structure +--- + +**What is Cargo?** + +Cargo is Rust's **build system and package manager** which streamlines almost every aspect of your Rust workflow: + +- **Project Creation:** `cargo new` quickly sets up a new Rust project with the correct structure. +- **Building:** `cargo build` compiles your code into an executable program. +- **Running:** `cargo run` builds and then executes your program. +- **Testing:** `cargo test` runs your project's tests. + +Let's create our classic "Hello, world\!" program using Cargo. + +1. **Create a New Project:** + - In your terminal, navigate to a directory where you want to create your project (e.g., your Desktop or a `dev` folder). + - Run this command: + ```bash + cargo new hello_rust_app + ``` + - Cargo will create a new folder named `hello_rust_app` with a basic project structure inside. +2. **Explore the Project Structure:** + - Navigate into the new folder: `cd hello_rust_app` + - Look at the contents: + - `Cargo.toml`: This is the manifest file for your project. It contains metadata about your project (name, version) and lists its dependencies. + - `src/main.rs`: This is where your main Rust code lives. + - `target/`: (Created after you build) This is where compiled executable files go. +3. **Examine `src/main.rs`:** + - Open `src/main.rs` in your VS Code. You'll see: + ```rust + fn main() { + println!("Hello, world!"); + } + ``` + - `fn main()`: This is the main function, the entry point of every Rust executable program. + - `println!`: This is a **macro** (indicated by the `!`). It prints text to the console. +4. **Run Your Application:** + - In your terminal (make sure you're inside the `hello_rust_app` folder), run: + ```bash + cargo run + ``` + - **What happens?** + - Cargo first **compiles** your code (you'll see messages like "Compiling hello_rust_app v0.1.0..."). + - Then, it **executes** the compiled program. + - You should see: `Hello, world!` printed in your terminal. + +Congratulations\! You've just created and run your very first Rust application. diff --git a/docs/desktop_applications/02_Rust/03_Variables-and-data-types.md b/docs/desktop_applications/02_Rust/03_Variables-and-data-types.md new file mode 100644 index 0000000..0de842b --- /dev/null +++ b/docs/desktop_applications/02_Rust/03_Variables-and-data-types.md @@ -0,0 +1,85 @@ +--- +title: Variables and data types +--- + +Now let's dive into how Rust handles variables and data. This is where you'll see some key differences from languages like JavaScript or Python. + +#### **Immutability by Default:** +This is one of Rust's core principles. By default, variables in Rust are **immutable**, meaning once you give them a value, you cannot change that value. This helps prevent unexpected bugs. + + * **Declaring an Immutable Variable:** + ```rust + fn main() { + let x = 5; // 'x' is immutable. Its value is 5, and it cannot be changed. + println!("The value of x is: {}", x); + // x = 6; // This would cause a compile-time error! Try uncommenting it. + // println!("The value of x is: {}", x); + } + ``` + + * **Making a Variable Mutable:** + If you *do* want to change a variable's value, you must explicitly mark it as `mut` (short for mutable). + ```rust + fn main() { + let mut y = 10; // 'y' is mutable. We can change its value. + println!("The initial value of y is: {}", y); + y = 15; // This is allowed because 'y' is mutable. + println!("The new value of y is: {}", y); + } + ``` +#### **Type Inference vs. Explicit Types:** +Rust is a **statically typed** language, meaning it knows the type of every variable at compile time. However, it's also very smart and can often **infer** the type based on the value you assign. You don't always *have* to write the type. + * **Type Inference (Common):** + ```rust + fn main() { + let age = 30; // Rust infers 'age' is an integer (i32 by default) + let pi = 3.14; // Rust infers 'pi' is a floating-point number (f64 by default) + let is_active = true; // Rust infers 'is_active' is a boolean + let initial = 'A'; // Rust infers 'initial' is a character (single quotes) + let greeting = "Hello"; // Rust infers 'greeting' is a string slice (&str) + println!("Age: {}, Pi: {}, Active: {}, Initial: {}, Greeting: {}", age, pi, is_active, initial, greeting); + } + ``` + * **Explicit Type Annotation (When needed or for clarity):** + You can explicitly tell Rust the type of a variable. This is useful when inference is ambiguous or for better readability. + ```rust + fn main() { + let count: i64 = 100_000_000_000; // Explicitly a 64-bit integer + let temperature: f32 = 25.5; // Explicitly a 32-bit float + let message: &str = "Welcome!"; // Explicitly a string slice + println!("Count: {}, Temp: {}, Message: {}", count, temperature, message); + } + ``` +#### **Common Primitive Data Types:** +Rust has several built-in primitive types: + * **Integers:** `i8`, `i16`, `i32` (default), `i64`, `i128` (signed integers) and `u8`, `u16`, `u32`, `u64`, `u128` (unsigned integers). The number indicates the bits they use. `isize` and `usize` depend on the architecture (e.g., 32-bit or 64-bit). + * **Floating-Point Numbers:** `f32` (single-precision), `f64` (double-precision, default). + * **Booleans:** `bool` (`true` or `false`). + * **Characters:** `char` (single Unicode scalar value, uses single quotes, e.g., `'A'`, `'😊'`). + * **Strings:** We'll learn more about strings later, but for now, know that `&str` (string slice, immutable reference to text) and `String` (growable, owned string) are the main types. + +#### **Constants:** + +Constants are always immutable and must have their type explicitly annotated. They can be declared in any scope, including global. + +```rust +const MAX_POINTS: u32 = 100_000; // Constants are typically named in SCREAMING_SNAKE_CASE +const APP_VERSION: &str = "1.0.0"; +fn main() { + println!("Max points: {}", MAX_POINTS); + println!("App version: {}", APP_VERSION); +} +``` + +#### **Shadowing:** + +Rust allows you to declare a *new* variable with the same name as a previous variable. This "shadows" the previous variable, meaning the new variable takes precedence. This is different from `mut`, as you're creating a new variable, not changing an existing one. +```rust +fn main() { + let spaces = " "; // First 'spaces' variable (string slice) + println!("Spaces (initial): '{}'", spaces); + let spaces = spaces.len(); // 'spaces' is now a new variable, holding the length (an integer) + println!("Spaces (length): {}", spaces); // The old 'spaces' is no longer accessible +} +``` +Shadowing is useful when you want to transform a variable's value but keep the same name, without needing to make the original variable mutable. \ No newline at end of file diff --git a/docs/desktop_applications/02_Rust/04_Functions-and-control-flow.md b/docs/desktop_applications/02_Rust/04_Functions-and-control-flow.md new file mode 100644 index 0000000..e5f95d4 --- /dev/null +++ b/docs/desktop_applications/02_Rust/04_Functions-and-control-flow.md @@ -0,0 +1,187 @@ +--- +title: Control Flow and Functions +--- + +We'll learn how to make decisions based on conditions, repeat actions, and organize our code into reusable blocks. By the end, we'll even build a game\! + +--- + +### 1. Conditional Execution: `if` Statements + +Just like in other programming languages, `if` statements in Rust let your program execute different code blocks based on whether a condition is true or false. + +#### **Basic `if`, `else if`, `else`:** + +You'll find this structure very familiar: + +```rust +fn main() { + let number = 7; + + if number < 5 { // If this condition is true + println!("Condition was true: number is less than 5"); + } else if number == 5 { // Otherwise, if this condition is true + println!("Condition was true: number is exactly 5"); + } else { // If none of the above conditions are true + println!("Condition was false: number is greater than 5"); + } +} +``` + +- **Conditions Must Be `bool`:** In Rust, the condition inside an `if` statement _must_ evaluate to a **boolean** (`true` or `false`). You can't just use a number like in some other languages. + ```rust + // This would be an ERROR: `if number` is not allowed in Rust + // if number { + // println!("Number was something!"); + // } + ``` + +#### **`if` as an Expression:** + +A cool feature in Rust is that `if` statements are **expressions**, meaning they can return a value. This is super handy for assigning values conditionally. + +```rust +fn main() { + let condition = true; + let number = if condition { // 'if' expression returns a value + 5 // This value is returned if 'condition' is true + } else { + 6 // This value is returned if 'condition' is false + }; // Note the semicolon here, as it's a statement assigning a value + + println!("The value of number is: {}", number); // Output: The value of number is: 5 + + let message = if number > 5 { + "Number is greater than 5" + } else { + "Number is 5 or less" + }; // Both branches must return the SAME TYPE! + + println!("Message: {}", message); +} +``` + +- **Important:** All branches of an `if` expression **must return the same type**. If one branch returns an integer and another returns a string, Rust won't compile because it can't determine the final type of the variable. + +--- + +### 2. Iterative Control Structures + +Repeating actions is a fundamental part of programming. Rust provides several ways to create loops. + +#### **`loop` (Infinite Loop with `break`):** + +The `loop` keyword creates an infinite loop. You'll typically use `break` to exit it based on a condition, and `continue` to skip to the next iteration. + +```rust +fn main() { + let mut counter = 0; + + let result = loop { // 'loop' can also return a value! + counter += 1; + println!("Loop count: {}", counter); + + if counter == 10 { + break counter * 2; // Break the loop and return this value + } + }; // Semicolon here, as it's an expression + + println!("Loop finished. Result: {}", result); // Output: Loop finished. Result: 20 +} +``` + +#### **`while` Loop:** + +A `while` loop executes a block of code repeatedly as long as a specified condition remains true. + +```rust +fn main() { + let mut number = 3; + + while number != 0 { + println!("{}!", number); + number -= 1; // Decrement number + } + println!("LIFTOFF!!!"); +} +``` + +#### **`for` Loop (Iterating over Collections):** + +The `for` loop is the most common loop in Rust. It's used to iterate over elements in a collection (like arrays, vectors, or ranges). This is often safer and more concise than `while` loops for iterating. + +- **Iterating over a Range:** + + ```rust + fn main() { + // Iterate from 1 up to (but not including) 5 + for number in 1..5 { + println!("Number in range: {}", number); + } + // Iterate from 1 up to AND including 5 + for number in 1..=5 { + println!("Number in range (inclusive): {}", number); + } + } + ``` + +- **Iterating over an Array/Vector:** + + ```rust + fn main() { + let a = [10, 20, 30, 40, 50]; + + for element in a.iter() { // .iter() creates an iterator over the elements + println!("The value is: {}", element); + } + + // You can also iterate with an index if needed (less common in idiomatic Rust) + for (index, element) in a.iter().enumerate() { + println!("Element at index {}: {}", index, element); + } + } + ``` + +--- + +### 3. Defining and Utilizing Functions + +Functions are blocks of code that perform a specific task and can be reused. + +#### **Basic Function Syntax:** + +- Functions are declared using the `fn` keyword. +- Parameters are type-annotated. +- The return type is specified after an arrow `->`. +- The last expression in a function (without a semicolon) is implicitly returned. You can also use the `return` keyword explicitly. + + + +```rust +// A function that doesn't take parameters and doesn't return a value +fn greet() { + println!("Hello from the greet function!"); +} + +// A function that takes parameters and returns a value +fn add_numbers(x: i32, y: i32) -> i32 { // Takes two i32s, returns an i32 + x + y // This is an expression, implicitly returned +} + +// A function with an explicit return +fn subtract_numbers(a: i32, b: i32) -> i32 { + return a - b; // Explicit return +} + +fn main() { + greet(); // Call the greet function + + let sum = add_numbers(5, 7); // Call add_numbers and store the result + println!("The sum is: {}", sum); // Output: The sum is: 12 + + let difference = subtract_numbers(10, 3); + println!("The difference is: {}", difference); // Output: The difference is: 7 +} +``` + +Well, you have all the required knowladge to solve the first exercise. It is time to practice! diff --git a/docs/desktop_applications/02_Rust/05_Ownership-and-borrowing.md b/docs/desktop_applications/02_Rust/05_Ownership-and-borrowing.md new file mode 100644 index 0000000..698a039 --- /dev/null +++ b/docs/desktop_applications/02_Rust/05_Ownership-and-borrowing.md @@ -0,0 +1,266 @@ +--- +title: Ownership, Borrowing, and Slices in Rust +--- + +In this lesson, we’ll take a closer look at three of Rust’s most important concepts: **ownership**, **borrowing**, and **slices**. +These are the foundations of Rust’s **memory safety**, allowing it to manage memory **without a garbage collector** and to **prevent data races at compile time**. + + +### 1. Ownership + +Ownership is **Rust’s memory management system**. Instead of a garbage collector, Rust tracks **who owns each piece of data** and **frees it automatically** when no longer needed. + +Key ideas: +- No garbage collector. +- Strict rules, checked during compilation. +- Violating them causes compilation errors. + +**Stack** and **Heap** memory: + +- **Stack**: + - Stores data in **Last-In, First-Out (LIFO)** order. + - Extremely fast because the memory location is always at the “top of the stack.” + - Requires values to have a **known, fixed size** at compile time. + - Automatically freed when the variable goes out of scope. + +- **Heap**: + - Stores **dynamically sized or growable data** (like `String` or `Vec`). + - Memory must be **requested at runtime** from the allocator. + - Access is **slower** because you must **follow a pointer** from the stack to the heap. + - Memory must eventually be freed, which is where **ownership rules** come in. + +Rust’s **ownership system** exists to **safely manage heap memory**, automatically cleaning up resources and preventing data races. + +### **The 3 Ownership Rules** + +1. **Each value in Rust has a single owner** (a variable that “owns” the value). +2. **When the owner goes out of scope, the value is dropped** (memory freed). +3. **Ownership can be moved, but not copied by default** (unless the type is `Copy` or you explicitly `clone`). + + +#### **Example:** + +```rust +fn main() { + let s1 = String::from("hello"); // s1 owns the string + let s2 = s1; // ownership moves to s2 + + // println!("{}", s1); // ERROR: s1 no longer owns the value + println!("{}", s2); // Works +} +``` + +- After `s2 = s1`, **s1 is invalidated** to prevent **double free** errors. +- When `main` ends, `s2` is dropped, and Rust frees the memory automatically. + +--- + +### **Copy vs Clone** + +Rust **treats data differently** depending on **where it lives**: + +- **Stack-only data (simple types)** → **Copied automatically** +- **Heap-allocated data (complex types)** → **Moved by default** + + +#### **1. Copy Types (Stack-Only)** + +- Examples: integers (`i32`), booleans (`bool`), characters (`char`), and tuples of `Copy` types. +- These types are **small and fixed-size**, so Rust **copies them cheaply** instead of moving them. + +```rust +fn main() { + let x = 5; // i32 is a Copy type + let y = x; // A new copy of 5 is created on the stack + + println!("x = {}, y = {}", x, y); // Both valid +} +``` + +Stack values are **duplicated instantly**, so `x` still owns its 5 and `y` has its own 5. + + +#### **2. Move Semantics (Heap Data)** + +- Types like `String`, `Vec`, or any custom type **holding heap memory** are **moved by default**. +- Assigning them **transfers ownership** instead of copying the underlying heap memory (which could be expensive). + +```rust +fn main() { + let s1 = String::from("hello"); // s1 owns the heap data + let s2 = s1; // s1 is MOVED into s2 + + // println!("{}", s1); // ERROR: s1 is no longer valid + println!("{}", s2); // Only s2 can be used now +} +``` + +**Why move instead of copy?** +- Copying large heap data automatically could be **slow**. +- Move avoids extra work while still keeping memory safe. + + +#### **3. Clone (Deep Copy)** + +- If you **want a real copy of the heap data**, call `.clone()`. + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = s1.clone(); // Copies heap data as well + + println!("s1 = {}, s2 = {}", s1, s2); // Both valid +} +``` + +- **Move**: only the pointer and metadata are copied; old owner is invalid. (Cheap) +- **Clone**: heap data is copied too; both owners are valid. (More expensive) + + +Think of **ownership like house keys**: + +- **Copy** - Making a **duplicate key** for a small box (cheap and simple). +- **Move** - Handing your **only key** to someone else (you can’t access it anymore). +- **Clone** - **Building a whole new house** with its own key (expensive). + +--- + +### 2. Borrowing and References + +If we want to **use a value in multiple places** without transferring ownership. +Rust solves this with **borrowing**, which allows **references** to a value. + +- A **reference** is like a **pointer** that guarantees memory safety. +- Borrowing allows **access without taking ownership**, so the original variable stays valid. +- **No runtime overhead**: the compiler ensures safety rules. + + +### **Immutable References (`&T`)** + +An immutable reference lets you **read data without taking ownership**: + +```rust +fn main() { + let s = String::from("hello"); + + let len = calculate_length(&s); // Borrow immutably + println!("The length of '{}' is {}.", s, len); // s is still valid +} + +fn calculate_length(s: &String) -> usize { + s.len() // Can read, cannot modify +} +``` + +- `&s` is a **reference** (borrow). +- The original variable **keeps ownership**. +- You **cannot modify** through an immutable reference. + + +### **Mutable References (`&mut T`)** + +If we want to **modify** a value without transferring ownership, we use **mutable references**: + +```rust +fn main() { + let mut s = String::from("hello"); + + change(&mut s); // Borrow mutably + println!("{}", s); // Output: hello, world +} + +fn change(some_string: &mut String) { + some_string.push_str(", world"); +} +``` + +- Only **one mutable reference** is allowed at a time. +- This prevents **data races**, ensuring **safe concurrent access**. + + +### **Borrowing Rules** + +1. **You can have either:** + - Any number of **immutable references** + - **OR** one **mutable reference** +2. **References must always be valid** (no dangling pointers). + +These rules ensure Rust can **guarantee memory safety** at compile time. + +--- + +## 3. Slices + +A **slice** is a **reference to part of a collection**. +Slices let you **work with sub-sections of data without copying**. + + +### **String Slices (`&str`)** + +```rust +fn main() { + let s = String::from("hello world"); + + let hello = &s[0..5]; // Slice of "hello" + let world = &s[6..11]; // Slice of "world" + + println!("{} {}", hello, world); +} +``` + +- `&s[start..end]` creates a slice from `start` (inclusive) to `end` (exclusive). +- `&s[..]` creates a slice of the **entire string**. + +--- + +### **Slices in Functions** + +Slices are commonly used to **avoid copying data** when processing collections: + +```rust +fn first_word(s: &str) -> &str { // Accepts &String or string literal + let bytes = s.as_bytes(); + + for (i, &item) in bytes.iter().enumerate() { + if item == b' ' { + return &s[..i]; // Slice until first space + } + } + + &s[..] // If no space, return entire string +} + +fn main() { + let s = String::from("hello world"); + let word = first_word(&s); + println!("First word: {}", word); +} +``` + +- `&str` is already a **string slice**. +- Returning a slice is **efficient** and avoids extra allocations. + +--- + +### **Array Slices** + +Slices also work with arrays: + +```rust +fn main() { + let arr = [1, 2, 3, 4, 5]; + let slice = &arr[1..4]; // Elements 2, 3, 4 + + for val in slice { + println!("{}", val); + } +} +``` + +- Array slices are `&[T]`. +- They **borrow part of the array** without copying it. + +--- + +### Exercises +// to be added later \ No newline at end of file diff --git a/docs/desktop_applications/02_Rust/06_References-and-slices.md b/docs/desktop_applications/02_Rust/06_References-and-slices.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/desktop_applications/02_Rust/07_Pattern-matching.md b/docs/desktop_applications/02_Rust/07_Pattern-matching.md new file mode 100644 index 0000000..a44b851 --- /dev/null +++ b/docs/desktop_applications/02_Rust/07_Pattern-matching.md @@ -0,0 +1,39 @@ +--- +title: Pattern Matching +--- + +--- + +### Advanced Pattern Matching: The `match` Expression (10 min) + +The `match` expression is one of Rust's most powerful control flow constructs. It allows you to compare a value against a series of patterns and then execute code based on which pattern matches. It's often a more robust and readable alternative to long `if-else if` chains. + +- **Exhaustiveness:** A key feature of `match` is that it must be **exhaustive**. This means you have to cover _every possible value_ that the data could take. If you don't, Rust's compiler will give you an error, which helps prevent bugs\! + +Let's look at a simple example with numbers: + +```rust +fn main() { + let number = 3; + + match number { // Match the 'number' against these patterns + 1 => println!("One!"), // If number is 1, do this + 2 => println!("Two!"), // If number is 2, do this + 3 | 4 => println!("Three or Four!"), // If number is 3 OR 4, do this (multiple patterns) + 5..=10 => println!("Between 5 and 10, inclusive!"), // If number is in this range + _ => println!("Something else!"), // The underscore '_' is a catch-all pattern (like 'default' in switch) + } + + let result = match number { // 'match' can also be an expression, returning a value + 1 => "It's one", + _ => "It's not one", // All branches must return the same type! + }; + println!("Result: {}", result); +} +``` + +- **When to use `match` vs. `if`:** + - Use `if` for simple true/false conditions or a few distinct branches. + - Use `match` when you have many possible values or complex patterns to handle, especially when working with `enum`s (which we'll cover in Lesson 3) or `Result` types (which you saw in Lesson 1's I/O). + +--- diff --git a/docs/desktop_applications/02_Rust/08_Structs-and-enums.md b/docs/desktop_applications/02_Rust/08_Structs-and-enums.md new file mode 100644 index 0000000..370ee48 --- /dev/null +++ b/docs/desktop_applications/02_Rust/08_Structs-and-enums.md @@ -0,0 +1,231 @@ +--- +title: Structs and Enums +--- + +### Structuring Data with `struct`s + +`struct`s (short for "structures") allow you to create custom data types by grouping related data together. They are similar to classes in object-oriented languages or objects/dictionaries in JavaScript/Python, but without built-in methods initially. + +#### **Defining a `struct`:** + +You define a `struct` using the `struct` keyword, followed by its name (typically `PascalCase`), and then curly braces containing its fields (each with a name and a type). + +```rust +// Define a struct named 'User' +struct User { + active: bool, + username: String, + email: String, + sign_in_count: u64, +} + +fn main() { + // Creating an instance of a struct + let user1 = User { // Order of fields doesn't matter + active: true, + username: String::from("alice123"), + email: String::from("alice@example.com"), + sign_in_count: 1, + }; + + // Accessing values using dot notation + println!("User 1 Name: {}", user1.username); + println!("User 1 Email: {}", user1.email); + + // To modify a field, the struct instance itself must be mutable + let mut user2 = User { + active: false, + username: String::from("bob456"), + email: String::from("bob@example.com"), + sign_in_count: 5, + }; + + user2.email = String::from("new_bob@example.com"); // This is allowed + println!("User 2 New Email: {}", user2.email); + + // You can also create new instances from existing ones using the struct update syntax + let user3 = User { + email: String::from("charlie@example.com"), + username: String::from("charlie789"), + ..user1 // Fills remaining fields from user1 (active, sign_in_count) + }; + println!("User 3 Name: {}, Active: {}", user3.username, user3.active); +} +``` + +#### **Tuple Structs:** + +Tuple structs are like tuples but have a name. They are useful when you want to give a name to a tuple but don't need named fields. + +```rust +struct Color(i32, i32, i32); // RGB values +struct Point(i32, i32, i32); // X, Y, Z coordinates + +fn main() { + let black = Color(0, 0, 0); + let origin = Point(0, 0, 0); + + println!("Black RGB: ({}, {}, {})", black.0, black.1, black.2); + // Note: black.0 is the first element, black.1 the second, etc. +} +``` + +#### **Unit-Like Structs:** + +These are useful when you need to implement a trait on some type but don't have any data that you want to store inside the type itself. + +```rust +struct AlwaysEqual; // No fields + +fn main() { + let subject = AlwaysEqual; + // You can use it as a type, but it holds no data. +} +``` + +#### **Printing Structs with `Debug` Trait:** + +By default, `println!` cannot directly print structs in a readable format. You need to derive the `Debug` trait for your struct using `#[derive(Debug)]`. + +```rust +#[derive(Debug)] // Add this line above your struct definition +struct User { + active: bool, + username: String, + email: String, + sign_in_count: u64, +} + +fn main() { + let user1 = User { + active: true, + username: String::from("alice123"), + email: String::from("alice@example.com"), + sign_in_count: 1, + }; + + println!("User 1: {:?}", user1); // Use {:?} for debug printing + println!("User 1 (pretty print): {:#?}", user1); // Use {:#?} for pretty printing +} +``` + +--- + +### Modeling Data with `enum`s + +`enum`s (enumerations) allow you to define a type by enumerating its possible variants. In Rust, `enum`s are much more powerful than in many other languages; they are "sum types," meaning a value of an `enum` can be _one of_ a set of defined possibilities. + +#### **Simple `enum`s:** + +You've already seen `Ordering` in the guessing game, which is a simple enum. + +```rust +enum TrafficLight { + Red, + Yellow, + Green, +} + +fn main() { + let current_light = TrafficLight::Red; + + match current_light { // Often used with 'match' for exhaustive handling + TrafficLight::Red => println!("Stop!"), + TrafficLight::Yellow => println!("Prepare to stop!"), + TrafficLight::Green => println!("Go!"), + } +} +``` + +#### **`enum`s with Associated Data:** + +This is where Rust's enums become extremely powerful. Each variant of an enum can hold its own specific data. + +```rust +enum Message { + Quit, // No data + Move { x: i32, y: i32 }, // Anonymous struct-like data + Write(String), // Single String data + ChangeColor(i32, i32, i32), // Tuple-like data (RGB values) +} + +fn main() { + let m1 = Message::Quit; + let m2 = Message::Move { x: 10, y: 20 }; + let m3 = Message::Write(String::from("hello")); + let m4 = Message::ChangeColor(255, 0, 128); + + // Using match to destructure and handle different enum variants + match m2 { + Message::Quit => println!("The Quit message has no data."), + Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y), + Message::Write(text) => println!("Write message: {}", text), + Message::ChangeColor(r, g, b) => println!("Change color to R:{}, G:{}, B:{}", r, g, b), + } +} +``` + +#### **The `Option` Enum (Handling Absence of a Value):** + +`Option` is a standard library enum that represents a value that might or might not be present. It's Rust's way of handling null/nil without null pointer exceptions. + +```rust +enum Option { // Conceptual definition + None, // Represents no value + Some(T), // Represents a value of type T +} + +fn main() { + let some_number = Some(5); // A value is present + let no_number: Option = None; // No value is present + + // You MUST use match (or other Option methods) to safely get the value out + match some_number { + Some(value) => println!("We have a number: {}", value), + None => println!("No number here."), + } + + match no_number { + Some(value) => println!("We have a number: {}", value), + None => println!("No number here."), + } +} +``` + +#### **The `Result` Enum (Handling Recoverable Errors):** + +`Result` is another fundamental enum for handling operations that can succeed or fail. You saw it with `read_line()` in Lesson 1. + +```rust +enum Result { // Conceptual definition + Ok(T), // Represents success, holding a value of type T + Err(E), // Represents failure, holding an error of type E +} + +fn main() { + // Example: A function that might fail + fn divide(numerator: f64, denominator: f64) -> Result { + if denominator == 0.0 { + Err(String::from("Cannot divide by zero!")) + } else { + Ok(numerator / denominator) + } + } + + let division_result = divide(10.0, 2.0); + match division_result { + Ok(value) => println!("Division successful: {}", value), + Err(error) => println!("Division failed: {}", error), + } + + let division_by_zero = divide(10.0, 0.0); + match division_by_zero { + Ok(value) => println!("Division successful: {}", value), + Err(error) => println!("Division failed: {}", error), + } +} +``` + +- **Key takeaway for `Option` and `Result`:** Rust forces you to explicitly handle the possibility of a value being absent or an operation failing, leading to more robust code. + +--- diff --git a/docs/desktop_applications/02_Rust/09_Error-handling.md b/docs/desktop_applications/02_Rust/09_Error-handling.md new file mode 100644 index 0000000..4c0f992 --- /dev/null +++ b/docs/desktop_applications/02_Rust/09_Error-handling.md @@ -0,0 +1,149 @@ +--- +title: Error Handling +--- + +--- + +Welcome to a crucial topic in Rust: error handling. Unlike many other languages, Rust doesn't have exceptions. Instead, it uses a powerful type system to categorize errors and force you to handle them. This approach leads to more reliable, predictable code and prevents common bugs that can lead to unexpected program crashes. + +--- + +### 1\. Unrecoverable Errors with `panic!` + +Sometimes, a program can get into a state from which it's impossible to recover. This usually indicates a bug in your code. In these situations, **`panic!`** is the right tool. It immediately stops the program and unwinds the call stack, printing a message and a backtrace. + +Use `panic!` for: + +- **Logic Errors:** An index that should never be out of bounds, but is. +- **Failed Assumptions:** A value that you assumed would be `Some` but turned out to be `None`. + +The `unwapped()` and `expect()` methods are also common ways to cause a panic. They are shortcuts for error handling that should only be used when you are absolutely certain an operation will succeed. + +```rust +fn main() { + let numbers = vec![1, 2, 3]; + // This will panic because index 10 is out of bounds. + // The program stops here. + let number = numbers[10]; + + println!("{}", number); +} +``` + +In a production application, you should strive to avoid panics. They are for programmer errors, not for situations you can foresee and handle. + +--- + +### 2\. Recoverable Errors with `Result` + +For situations where an error is a possibility you can predict and recover from, Rust uses the **`Result`** enum. It represents an operation that can either succeed or fail. + +`Result` has two variants: + +- **`Ok(T)`**: The operation was successful, and it returned a value of type `T`. +- **`Err(E)`**: The operation failed, and it returned an error of type `E`. + +A classic example is a file operation, which can fail if the file doesn't exist or if you don't have permission to access it. + +```rust +use std::fs::File; +use std::io::ErrorKind; + +fn main() { + let file_result = File::open("hello.txt"); + + // We must handle both the Ok and Err cases using a `match` expression. + let file = match file_result { + Ok(file) => file, + Err(error) => match error.kind() { + ErrorKind::NotFound => match File::create("hello.txt") { + Ok(fc) => fc, + Err(e) => panic!("Problem creating the file: {:?}", e), + }, + other_error => panic!("Problem opening the file: {:?}", other_error), + }, + }; + println!("File opened successfully!"); +} +``` + +This approach forces you to consider every possible outcome, making your code more resilient. + +--- + +### 3\. The `?` Operator for Error Propagation + +Handling every `Result` with a `match` can get verbose. The **`?` operator** is a convenient shortcut for error handling that allows you to propagate an error up the call stack. + +When you use `?` on a `Result`, one of two things happens: + +- If the `Result` is `Ok(value)`, the `value` is extracted and the function continues. +- If the `Result` is `Err(error)`, the function immediately returns with that `error`. + +For `?` to work, the function it's used in must return a `Result` type that is compatible with the error being propagated. Let's rewrite the file example to be much cleaner. + +```rust +use std::fs::File; +use std::io::{self, Read}; + +// The function now returns a Result, allowing us to use `?`. +fn read_username_from_file() -> Result { + let mut file = File::open("hello.txt")?; // Open the file, or return the error. + let mut username = String::new(); + file.read_to_string(&mut username)?; // Read to a string, or return the error. + Ok(username) // Return the result wrapped in Ok. +} + +fn main() { + match read_username_from_file() { + Ok(username) => println!("Username: {}", username), + Err(e) => println!("Error: {}", e), + } +} +``` + +The `?` operator makes your code concise and clearly states that an error in one part of a function should be passed up to the caller. + +--- + +### 4\. Optional Data with `Option` + +While `Result` is for errors, **`Option`** is used to represent the absence of a value. It's a way of signaling that something might or might not exist. + +`Option` has two variants: + +- **`Some(T)`**: A value of type `T` is present. +- **`None`**: There is no value. + +This is often used in cases where `null` or `nil` would be used in other languages. `Option` prevents the "billion-dollar mistake" of null pointers by forcing you to handle the `None` case. + +```rust +fn find_item_by_id(id: u32, items: &Vec) -> Option<&String> { + // This returns an Option<&String> because the item might not exist. + items.get(id as usize) +} + +fn main() { + let grocery_list = vec!["milk".to_string(), "bread".to_string(), "eggs".to_string()]; + + // We must handle both cases when we get a value. + let item = find_item_by_id(1, &grocery_list); + match item { + Some(name) => println!("Found item: {}", name), + None => println!("Item not found!"), + } +} +``` + +You can also use the `?` operator on `Option` types, as long as the function's return type is also an `Option`. + +--- + +### Summary + +- Use **`panic!`** for unrecoverable situations that indicate a bug in your code. +- Use **`Result`** for recoverable errors that can be handled gracefully, such as file I/O or network failures. +- Use the **`?` operator** to simplify propagating `Result` errors up the call stack. +- Use **`Option`** to represent the possible absence of a value, preventing common null-related bugs. + +Mastering these tools is essential for writing safe, reliable, and production-quality Rust applications. diff --git a/docs/desktop_applications/02_Rust/10_Collections.md b/docs/desktop_applications/02_Rust/10_Collections.md new file mode 100644 index 0000000..768a5f6 --- /dev/null +++ b/docs/desktop_applications/02_Rust/10_Collections.md @@ -0,0 +1,158 @@ +--- +title: Collections +--- + +--- + +### An Introduction to Rust's Standard Collections + +Rust's standard library provides a rich set of data structures for managing data, all designed with the language's core principles of performance and safety in mind. These collections are generic, meaning they can hold any type of data, and they're highly optimized. + +--- + +### `Vec`: The Growable Array + +The `Vec` is a dynamic, growable array, a lot like `std::vector` in C++. It's one of the most widely used collections in Rust. It stores values on the heap, so its size can change at runtime. + +#### Key Features: + +- **Contiguous Storage:** Elements are stored next to each other in memory, making access very fast. + +- **Heap-allocated:** The data is stored on the heap, and the `Vec` itself is a small pointer on the stack, which points to the data. + +- **Growable:** You can add and remove elements. `push()` will handle reallocation if needed. + +#### Example: + +``` +// Create a new, empty Vec that will hold i32 values. +let mut numbers: Vec = Vec::new(); + +// Add elements to the Vec. +numbers.push(10); +numbers.push(20); +numbers.push(30); + +// You can also create a Vec with initial values using a macro. +let mut other_numbers = vec![1, 2, 3]; + +// Accessing elements by index. This will panic if the index is out of bounds. +let third = numbers[2]; +println!("The third element is: {}", third); + +// A safer way to get an element is with the `.get()` method, which returns an Option. +match numbers.get(10) { + Some(value) => println!("The value at index 10 is: {}", value), + None => println!("Index 10 is out of bounds."), +} + + + +``` + +--- + +### `String` & `&str`: The Text Types + +Working with text in Rust involves two main types, which can be a point of confusion for beginners. + +- **`String`**: This is a heap-allocated, growable, and mutable UTF-8 encoded string. It's the equivalent of `std::string` in C++. You use it when you need to own the data and potentially modify it. + +- **`&str` (string slice)**: This is an immutable view or "slice" into a `String` or a string literal. It's a pointer to the data and its length. You can think of it like `const char*` in C++, but safer and aware of its length. `&str` is often used for function arguments. + +#### Example: + +``` +// A mutable, heap-allocated String. +let mut s1 = String::from("hello"); + +// A string slice, which is a view into the string literal. +let s2: &str = "world"; + +// You can push characters or strings onto a String. +s1.push_str(", "); +s1.push_str(s2); +s1.push('!'); + +println!("{}", s1); // Prints "hello, world!" + +// String slices can be created from Strings. +let slice = &s1[0..5]; // This creates a slice "hello". +println!("A slice: {}", slice); + +// Note that you cannot use an index to access a character directly, +// as a single character might take up more than one byte in UTF-8. + + + +``` + +--- + +### `HashMap`: The Key-Value Store + +`HashMap` is a collection that stores data as key-value pairs, similar to `std::unordered_map` in C++. It provides an efficient way to look up a value based on its key. + +#### Key Features: + +- **Key-Value Pairs:** Stores data in a `(key, value)` format. + +- **Efficient Lookups:** Provides average $O(1)$ time complexity for insertion and retrieval. + +- **Hashing:** The key type `K` must implement the `Hash` trait so the `HashMap` can determine where to store the data. It also needs to implement the `Eq` trait for comparison. + +#### Example: + +``` +use std::collections::HashMap; + +// Create a new HashMap. The compiler will infer the types. +let mut scores = HashMap::new(); + +// Insert key-value pairs. +scores.insert(String::from("Blue"), 10); +scores.insert(String::from("Yellow"), 50); + +// Get a value from the HashMap. This returns an Option<&V>. +let team_name = String::from("Blue"); +let score = scores.get(&team_name); + +match score { + Some(value) => println!("The blue team's score is: {}", value), + None => println!("No score found for that team."), +} + +// Iterate over the HashMap's key-value pairs. +for (key, value) in &scores { + println!("{}: {}", key, value); +} + + + +``` + +--- + +### Other Useful Collections + +Beyond the core collections, Rust's standard library offers several others for more specific use cases. Here's a brief mention of them with links to their official documentation for further exploration. + +- **`HashSet`**: A collection of unique values, similar to `std::unordered_set` in C++. It offers fast, average $O(1)$ time complexity for insertion, deletion, and checking for the presence of an item. + + - [Official `HashSet` Docs](https://doc.rust-lang.org/std/collections/struct.HashSet.html) + +- **`BTreeMap`**: A map that stores key-value pairs in a sorted order, comparable to `std::map` in C++. It has a logarithmic $O(\\log n)$ time complexity for operations but guarantees a consistent, sorted iteration order. + + - [Official `BTreeMap` Docs](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) + +- **`BTreeSet`**: A collection of unique, sorted values, similar to `std::set` in C++. It also provides logarithmic $O(\\log n)$ time complexity and guarantees sorted iteration. + + - [Official `BTreeSet` Docs](https://doc.rust-lang.org/std/collections/struct.BTreeSet.html) + +- **`VecDeque`**: A double-ended queue, which is a growable array optimized for efficient pushes and pops from both the front and the back. + + - [Official `VecDeque` Docs](https://doc.rust-lang.org/std/collections/struct.VecDeque.html) + +- **`LinkedList`**: A classic doubly-linked list. While a `VecDeque` is often a better choice, a `LinkedList` can be more efficient for frequent insertions and deletions at arbitrary positions. + + - [Official `LinkedList` Docs](https://doc.rust-lang.org/std/collections/struct.LinkedList.html) diff --git a/docs/desktop_applications/02_Rust/11_Impl-and-methods.md b/docs/desktop_applications/02_Rust/11_Impl-and-methods.md new file mode 100644 index 0000000..346d459 --- /dev/null +++ b/docs/desktop_applications/02_Rust/11_Impl-and-methods.md @@ -0,0 +1,49 @@ +--- +title: Impl and Methods +--- + +#### **Associated Functions (Methods) to `structs` using `impl`:** + +You can define functions that belong to a `struct` using an `impl` (implementation) block. These are often called **methods** if their first parameter is `self` (a reference to the instance of the struct). + +```rust +struct Rectangle { + width: u32, + height: u32, +} + +// Implement methods for the Rectangle struct +impl Rectangle { + // A method that takes an immutable reference to self + fn area(&self) -> u32 { + self.width * self.height + } + + // A method that takes a mutable reference to self + fn scale(&mut self, factor: u32) { + self.width *= factor; + self.height *= factor; + } + + // An associated function (not a method, doesn't take self) + // Often used as constructors (like 'new' in other languages) + fn square(size: u32) -> Rectangle { + Rectangle { + width: size, + height: size, + } + } +} + +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + println!("The area of the rectangle is {} square pixels.", rect1.area()); // Call method + + let mut rect2 = Rectangle { width: 10, height: 20 }; + rect2.scale(2); // Call mutable method + println!("Scaled rectangle: width={}, height={}", rect2.width, rect2.height); + + let sq = Rectangle::square(25); // Call associated function using :: + println!("Square area: {}", sq.area()); +} +``` diff --git a/docs/desktop_applications/02_Rust/12_Module-organization.md b/docs/desktop_applications/02_Rust/12_Module-organization.md new file mode 100644 index 0000000..7cab98f --- /dev/null +++ b/docs/desktop_applications/02_Rust/12_Module-organization.md @@ -0,0 +1,107 @@ +--- +title: Module Organization +--- + +### Organizing Code with Modules + +As your Rust projects grow, you'll want to organize your code into logical units. Rust's module system helps you do this. + +- **Modules (`mod`):** Modules are like namespaces or containers for functions, structs, enums, and other modules. They help prevent naming conflicts and control visibility. +- **`use` Keyword:** The `use` keyword brings items from modules into your current scope, so you don't have to type the full path every time. +- **`pub` Keyword:** By default, everything in Rust is private. To make an item (function, struct, enum, module) visible outside its current module, you need to mark it with `pub` (public). + +#### **Example: Single File Module** + +```rust +// src/main.rs + +// Declare a module named 'greetings' +mod greetings { + // This function is private by default + fn english() { + println!("Hello!"); + } + + // This function is public, so it can be accessed from outside 'greetings' + pub fn spanish() { + println!("¡Hola!"); + } + + // Declare a nested module + pub mod formal { + pub fn english_formal() { + println!("Good day!"); + } + } +} + +fn main() { + // Call a public function from the greetings module + greetings::spanish(); + + // Call a public function from the nested formal module + greetings::formal::english_formal(); + + // greetings::english(); // ERROR! 'english' is private +} +``` + +#### **Example: Modules in Separate Files** + +For larger projects, you'll put modules in separate files. + +1. **Create a new Cargo project:** `cargo new my_app_modules` + +2. **Create module files:** + + - Inside `src/`, create a file named `greetings.rs`. + - Inside `src/greetings/`, create a file named `formal.rs`. (You'll need to create the `greetings` folder first). + +3. **Content of `src/greetings.rs`:** + + ```rust + // src/greetings.rs + pub fn spanish() { + println!("¡Hola desde el módulo de saludos!"); + } + + // Declare the nested module 'formal' + pub mod formal; + ``` + +4. **Content of `src/greetings/formal.rs`:** + + ```rust + // src/greetings/formal.rs + pub fn english_formal() { + println!("Good day from the formal submodule!"); + } + ``` + +5. **Content of `src/main.rs`:** + + ```rust + // src/main.rs + + mod greetings; // Declare the 'greetings' module (Rust looks for src/greetings.rs or src/greetings/mod.rs) + + // Bring specific items into scope using 'use' for easier access + use greetings::spanish; + use greetings::formal::english_formal; + + fn main() { + spanish(); // Now you can call it directly + english_formal(); // And this one too + + // You can still use the full path if you prefer + greetings::spanish(); + } + ``` + + + +- **`mod` declaration:** When you write `mod greetings;` in `main.rs`, Rust looks for `src/greetings.rs` or `src/greetings/mod.rs`. +- **`pub` for Visibility:** Remember to use `pub` on items you want to expose from a module. +- **`use` for Convenience:** `use` statements are like shortcuts; they don't change visibility but make names easier to type. + +--- diff --git a/docs/desktop_applications/02_Rust/13_Exercises.md b/docs/desktop_applications/02_Rust/13_Exercises.md new file mode 100644 index 0000000..c40ae31 --- /dev/null +++ b/docs/desktop_applications/02_Rust/13_Exercises.md @@ -0,0 +1,31 @@ +--- +title: Exercises +--- + +### 1. Guessing Game + +You have to create a simple guessing game which utilizes the knowledge acquired so far. + +Firstly, create a new project similar to the one in Lesson 1. + +**Game Logic:** + +1. Generate a random secret number. +2. Prompt the user to guess. +3. Read the user's input. +4. Compare the guess to the secret number. +5. Tell the user if they guessed too high, too low, or correctly. +6. Keep looping until the user guesses correctly. + +### 2. Custom Error-Handling System + +You will design a custom error-handling system to demonstrate your understanding of Rust's `Result`, `enum`, and trait implementation. + +**Your Task:** + +1. Create a new project with a library module (`src/lib.rs`) and a main application file (`src/main.rs`). +2. In the library module, define a public `enum` called **`MyErrorType`** with variants like `NotFound`, `UnexpectedError`, and `InvalidInput`. +3. Define a public **`CustomError`** `struct` that contains both a `String` message and a `MyErrorType` variant. +4. Implement the necessary traits (`std::fmt::Display` and `std::error::Error`) for your `CustomError` struct so it works with Rust's standard error handling. +5. Create a public function named **`handle_error`** that accepts a message and an `error_type` and returns an `Err` variant of `Result<(), CustomError>`. +6. In your `main.rs`, use the `handle_error` function to simulate different errors and demonstrate how to handle them gracefully using `Result` and the `?` operator. diff --git a/docs/desktop_applications/03_Slint/01_Introduction.md b/docs/desktop_applications/03_Slint/01_Introduction.md new file mode 100644 index 0000000..eaa2d5d --- /dev/null +++ b/docs/desktop_applications/03_Slint/01_Introduction.md @@ -0,0 +1,80 @@ +# Introduction to Slint + +## What is Slint? +Slint is a **declarative UI language** designed to make building modern, efficient, and portable user interfaces simple and intuitive. +It allows you to describe *what* your UI should look like and *how* it should behave, without dealing with low-level implementation details. + +Slint is designed to work seamlessly with languages like **Rust**, **C++**, and **JavaScript**, keeping your **business logic** separate from the UI code. + + +## Core Concepts + +### Elements and Properties +Slint uses **elements** (such as `Text`, `Rectangle`, `Button`) followed by curly braces `{}` to define UI components. +Inside the braces, you set **properties** that describe the appearance or behavior of that element. + +Example: +```slint +Text { + text: "Hello World!"; + font-size: 24px; + color: #0044ff; +} +``` + +### Nesting Elements +Elements can be placed inside one another to build hierarchical UIs. + +Example: +```slint +Rectangle { + width: 150px; + height: 60px; + background: white; + border-radius: 10px; + + Text { + text: "Hello World!"; + font-size: 24px; + color: black; + } +} +``` + +### Reactivity +Slint has **built-in reactivity**: when a property changes, all dependent UI elements automatically update. + +Example with a counter: +```slint +property counter: 0; + +Rectangle { + width: 150px; + height: 60px; + background: white; + border-radius: 10px; + + Text { + text: "Count: " + counter; + font-size: 24px; + color: black; + } + + TouchArea { + clicked => { + counter += 1; + } + } +} +``` + + +## Why Slint? + +- **Pure declarative language** built for UI from the ground up. +- **Easy to read and write** for both developers and designers. +- **Portable** works on desktop, embedded systems, and web. +- **Clear separation** of UI and business logic. +- **Reactivity** makes dynamic UIs effortless. + +Compared to traditional UI approaches (like HTML/JavaScript or XML-based layouts), Slint is more concise, readable, and easier to maintain. diff --git a/docs/desktop_applications/03_Slint/02_Reactivity.md b/docs/desktop_applications/03_Slint/02_Reactivity.md new file mode 100644 index 0000000..2fbe290 --- /dev/null +++ b/docs/desktop_applications/03_Slint/02_Reactivity.md @@ -0,0 +1,132 @@ +# Slint Reactivity + +## Introduction +**Reactivity** is a core concept in Slint. It allows you to create complex, dynamic user interfaces with far less code. +In Slint, UI elements automatically update when the properties they depend on change — without requiring manual refresh logic. + +## Example: Mouse Tracking and Color Change +```slint +export component MyComponent { + width: 400px; height: 400px; + + Rectangle { + background: #151515; + } + + ta := TouchArea {} + + myRect := Rectangle { + x: ta.mouse-x; + y: ta.mouse-y; + width: 40px; + height: 40px; + background: ta.pressed ? orange : white; + } + + Text { + x: 5px; y: 5px; + text: "x: " + myRect.x / 1px; + color: white; + } + + Text { + x: 5px; y: 15px; + text: "y: " + myRect.y / 1px; + color: white; + } +} +``` + +### How It Works +- The **rectangle follows the mouse** using `x: ta.mouse-x;` and `y: ta.mouse-y;`. +- **Color changes on click**: `background: ta.pressed ? orange : white;`. +- Text labels **automatically update** to show the rectangle's current position. + +This works because: +1. The `TouchArea` exposes `mouse-x`, `mouse-y`, and `pressed` properties. +2. When these properties change, all bound expressions are automatically re-evaluated. +3. The UI updates only where dependencies have changed. + + +## Performance and Dependency Tracking +Slint evaluates bindings lazily: +- Dependencies are registered when a property is accessed during evaluation. +- When a property changes, only dependent expressions are re-evaluated. +- This ensures high performance, even for complex UIs. + + +## Property Expressions +Property bindings can be simple or complex: + +```slint +// Tracks foo.x +x: foo.x; + +// Conditional expression +x: foo.x > 100px ? 0px : 400px; + +// Clamped value +x: clamp(foo.x, 0px, 400px); +``` + +You can also use **functions** for clarity: + +```slint +export component MyComponent { + width: 400px; height: 400px; + + pure function lengthToInt(n: length) -> int { + return (n / 1px); + } + + ta := TouchArea {} + + myRect := Rectangle { + x: ta.mouse-x; + y: ta.mouse-y; + width: 40px; + height: 40px; + background: ta.pressed ? orange : white; + } + + Text { + x: 5px; y: 5px; + text: "x: " + lengthToInt(myRect.x); + } + Text { + x: 5px; y: 15px; + text: "y: " + lengthToInt(myRect.y); + } +} +``` + +## Purity in Bindings +For reactivity to work correctly, bindings must be **pure**: +- Evaluating a property should not change any other observable state. +- The Slint compiler enforces purity in binding expressions, pure functions, and pure callbacks. + +Example: +```slint +export component Example { + pure callback foo() -> int; + public pure function bar(x: int) -> int { + return x + foo(); + } +} +``` + +## Two-Way Bindings +Two-way bindings keep two properties in sync using `<=>`: + +```slint +export component Example { + in property rect-color <=> r.background; + in property rect-color2 <=> r.background; + + r:= Rectangle { + width: parent.width; + height: parent.height; + background: blue; + } +} +``` diff --git a/docs/desktop_applications/03_Slint/03_Slint-file.md b/docs/desktop_applications/03_Slint/03_Slint-file.md new file mode 100644 index 0000000..8664540 --- /dev/null +++ b/docs/desktop_applications/03_Slint/03_Slint-file.md @@ -0,0 +1,179 @@ +# The `.slint` File + +## Introduction +In Slint, you define user interfaces in the **Slint language** and save them in files with the `.slint` extension. + +Each `.slint` file defines **one or more components**. Components declare a tree of elements and can be reused to build your own set of UI controls. You can use each declared component by its name in other components. + + +## Example: Components and Elements +```slint +component MyButton inherits Text { + color: black; + // ... +} + +export component MyApp inherits Window { + preferred-width: 200px; + preferred-height: 100px; + Rectangle { + width: 200px; + height: 100px; + background: green; + } + MyButton { + x: 0; y: 0; + text: "hello"; + } + MyButton { + y: 0; + x: 50px; + text: "world"; + } +} +``` + +- `MyButton` and `MyApp` are components. +- `Window` and `Rectangle` are **built-in elements**. +- Components can **reuse** other components. + + +## Naming Elements +You can assign names to elements using `:=`: + +```slint +export component MyApp inherits Window { + preferred-width: 200px; + preferred-height: 100px; + + hello := MyButton { + x: 0; y: 0; + text: "hello"; + } + world := MyButton { + y: 0; + text: "world"; + x: 50px; + } +} +``` + +### Reserved Names +- `root` → outermost element of a component. +- `self` → current element. +- `parent` → parent element. + + +## Comments +- **Single-line**: `// comment` +- **Multi-line**: `/* ... */` + +```slint +// Single line comment +/* + Multi-line comment +*/ +``` + +--- + +## Elements and Components +- **Elements** → basic building blocks (e.g., `Text`, `Rectangle`). +- **Components** → can be built from multiple elements. +- Declared as: `ElementName { ... }` + +Valid: +```slint +Text {} +Text { +} +``` + +Invalid: +```slint +Text {}; +``` + + +## The Root Element +The root element in a `.slint` file must be a **component**. + +```slint +component MyApp { + Text { + text: "Hello World"; + font-size: 24px; + } +} +``` + +## Properties +Set with `property-name: value;`. + +### Identifiers +- Letters, numbers, `_`, or `-`. +- Cannot start with number or `-`. +- `_` and `-` are considered equivalent (`foo_bar` = `foo-bar`). + + +## Conditional Elements +Use `if` to create elements conditionally. + +```slint +export component Example inherits Window { + preferred-width: 50px; + preferred-height: 50px; + if area.pressed : foo := Rectangle { background: blue; } + if !area.pressed : Rectangle { background: red; } + area := TouchArea {} +} +``` + +## Modules (Import/Export) +By default, components are **private**. Use `export` to make them available in other files. + +```slint +component ButtonHelper inherits Rectangle { } +component Button inherits Rectangle { + ButtonHelper { } +} +export { Button } +``` + +Rename on export: +```slint +component Button inherits Rectangle { } +export { Button as ColorButton } +``` + +Export directly: +```slint +export component Button inherits Rectangle { } +``` + +Import from other files: +```slint +import { Button } from "./button.slint"; +``` + +Rename on import: +```slint +import { Button as CoolButton } from "../theme/button.slint"; +``` + +## Module Syntax +### Import +```slint +import { MyButton } from "module.slint"; +import { MyButton, MySwitch } from "module.slint"; +import { MyButton as OtherButton } from "module.slint"; +``` + +### Export +```slint +export component MyButton inherits Rectangle { } +component MySwitch inherits Rectangle { } +export { MySwitch } +export { MySwitch as Alias1, MyButton as Alias2 } +export * from "other_module.slint"; +``` diff --git a/docs/desktop_applications/03_Slint/04_Properties.md b/docs/desktop_applications/03_Slint/04_Properties.md new file mode 100644 index 0000000..70463b2 --- /dev/null +++ b/docs/desktop_applications/03_Slint/04_Properties.md @@ -0,0 +1,115 @@ +# Properties in Slint + +## Introduction +All elements in Slint have **properties**. Built-in elements come with standard properties such as `color` or dimensional properties like `width` and `height`. +You can assign values directly or use expressions. + +Example: +```slint +export component Example inherits Window { + // Simple expression (ends with semicolon) + width: 42px; + // Code block (no semicolon) + height: { 42px } +} +``` + +The **default value** of a property is the default value of its type: +- `bool` → `false` +- `int` → `0` +- etc. + + +## Declaring Custom Properties +You can define extra properties by specifying the type, name, and optional default value: + +```slint +export component Example { + // Integer property named my-property + property my-property; + + // Integer property with a default value + property my-second-property: 42; +} +``` + +--- + +## Property Qualifiers +You can annotate properties with qualifiers that control how they can be accessed: + +- **private** *(default)*: Accessible only within the component. +- **in**: Input property — can be set by the user of the component, but not overwritten internally. +- **out**: Output property — can only be set by the component, read-only for the user. +- **in-out**: Readable and writable by both the component and the user. + +Example: +```slint +export component Button { + in property text; // Set by the user + out property pressed; // Read by the user + in-out property checked; // Changed by both + private property has-mouse; // Internal only +} +``` + +Properties declared at the top level that aren’t private are accessible externally when using the component as an element, or from business logic via language bindings. + + +## Change Callbacks +You can define a callback that is triggered when a property changes value. + +```slint +import { LineEdit } from "std-widgets.slint"; +export component Example inherits Window { + VerticalLayout { + LineEdit { + // Triggered when text changes + changed text => { t.text = self.text; } + } + t := Text {} + } +} +``` + +### Notes on Change Callbacks: +- They are **not invoked immediately** — instead, they are queued and executed in the next event loop iteration. +- Invoked **only if** the property’s value actually changes. +- If multiple changes occur in one event loop cycle, the callback runs only once. +- If the value reverts before execution, the callback won’t be called. + + +## Warning: Avoid Loops in Change Callbacks +Creating a callback loop can cause undefined behavior. + +Example of a potential loop: +```slint +export component Example { + in-out property foo; + property bar: foo + 1; + changed bar => { foo += 1; } // Potential infinite loop +} +``` + +Slint will break such loops after a few iterations, which can prevent other callbacks from running. + + +## Best Practices +- **Prefer declarative bindings** over change callbacks when possible. +- Avoid: +```slint +changed bar => { foo = bar + 1; } +``` +- Instead, use: +```slint +foo: bar + 1; +``` + +Declarative bindings: +- Automatically track dependencies. +- Are lazily evaluated. +- Maintain binding purity. +- Are easier to work with in graphical editors. + +Excessive use of `changed` callbacks can lead to bugs, complexity, and performance issues. + diff --git a/docs/desktop_applications/03_Slint/05_Expressions.md b/docs/desktop_applications/03_Slint/05_Expressions.md new file mode 100644 index 0000000..26f76aa --- /dev/null +++ b/docs/desktop_applications/03_Slint/05_Expressions.md @@ -0,0 +1,107 @@ +# Expressions in Slint + +## Introduction +Expressions allow you to **declare relationships** and **connect properties** in your user interface. +When properties used in an expression change, the expression is **automatically re-evaluated** and the property is updated. + +Example: +```slint +export component Example { + // Declare a property of type int + in-out property my-property; + + // Bind width to the property + width: root.my-property * 20px; +} +``` +When `my-property` changes, the `width` changes automatically. + + +## Arithmetic Operators +Arithmetic in expressions works like most programming languages, using `*`, `+`, `-`, `/`. + +```slint +export component Example { + in-out property p: 1 * 2 + 3 * 4; // same as (1 * 2) + (3 * 4) +} +``` + + +## String Concatenation +Concatenate strings using `+`. + + +## Logical and Comparison Operators +- Logical: `&&` (and), `||` (or) +- Comparison: `==`, `!=`, `>`, `<`, `>=`, `<=` + + +## Accessing Properties of Elements +Use `elementName.propertyName` syntax. + +```slint +export component Example { + foo := Rectangle { + x: 42px; + } + x: foo.x; +} +``` + + +## Ternary Operator +Slint supports the ternary operator `condition ? value1 : value2`. + +Example: +```slint +export component Example inherits Window { + preferred-width: 100px; + preferred-height: 100px; + + Rectangle { + touch := TouchArea {} + background: touch.pressed ? #111 : #eee; + border-width: 5px; + border-color: !touch.enabled ? #888 + : touch.pressed ? #aaa + : #555; + } +} +``` + + +## Statements in Expressions +### Assignment +```slint +clicked => { some-property = 42; } +``` + +### Self-assignment +```slint +clicked => { some-property += 42; } +``` + +### Calling a Callback +```slint +clicked => { root.some-callback(); } +``` + +### Conditional Statements +```slint +clicked => { + if (condition) { + foo = 42; + } else if (other-condition) { + bar = 28; + } else { + foo = 4; + } +} +``` + +### Empty Expression +```slint +clicked => { } +// or +clicked => { ; } +``` diff --git a/docs/desktop_applications/03_Slint/06_Layouts.md b/docs/desktop_applications/03_Slint/06_Layouts.md new file mode 100644 index 0000000..70d2906 --- /dev/null +++ b/docs/desktop_applications/03_Slint/06_Layouts.md @@ -0,0 +1,194 @@ +# Positioning and Layouts in Slint + +## Introduction +All visual elements in Slint are placed in a **window**. +- `x` and `y` → coordinates **relative** to their parent. +- `width` and `height` → element size. + +Slint calculates **absolute position** by adding parent positions recursively up to the top-level element. + + +## Placement Methods +You can position elements in two ways: +1. **Explicitly** — set `x`, `y`, `width`, and `height` directly. +2. **Automatically** — use **layout elements**. + +Explicit placement is ideal for static UIs; layouts are better for scalable and complex UIs. + + +## Explicit Placement Example +```slint +export component Example inherits Window { + width: 200px; + height: 200px; + Rectangle { + x: 100px; + y: 70px; + width: parent.width - self.x; + height: parent.height - self.y; + background: blue; + Rectangle { + x: 10px; + y: 5px; + width: 50px; + height: 30px; + background: green; + } + } +} +``` + +### Units +- `px` → logical pixels (scales with device pixel ratio). +- `phx` → physical pixels. +- `%` → percentage of parent’s size. + +**Defaults:** +- `x` and `y` center elements by default. +- `width`/`height` vary: + - Content elements (Text, Image, etc.) → size to content. + - Elements without content (Rectangle, TouchArea) → fill parent. + + +## Preferred Size +- Set with `preferred-width` / `preferred-height`. +- Defaults depend on children unless explicitly set. +- `100%` → match parent size. + +Example: +```slint +export component MyComponent { + preferred-width: 100%; + preferred-height: 100%; +} +``` + + +## Automatic Placement with Layouts +Slint layout elements: +- `VerticalLayout` +- `HorizontalLayout` +- `GridLayout` + +Constraints: +- **Size**: `min-width`, `min-height`, `max-width`, `max-height`, `preferred-width`, `preferred-height`. +- **Stretch**: `horizontal-stretch`, `vertical-stretch` (default `0` = no stretch). + +**Common layout properties:** +- `spacing` → space between children. +- `padding` → space inside layout borders (can be per-side). + + +## Vertical and Horizontal Layout Examples +**Stretch by default:** +```slint +export component Example inherits Window { + width: 200px; + height: 200px; + HorizontalLayout { + Rectangle { background: blue; min-width: 20px; } + Rectangle { background: yellow; min-width: 30px; } + } +} +``` + +**Alignment:** +```slint +HorizontalLayout { + alignment: start; + Rectangle { background: blue; min-width: 20px; } + Rectangle { background: yellow; min-width: 30px; } +} +``` + +**Nested Layouts:** +```slint +HorizontalLayout { + Rectangle { background: green; width: 10px; } + VerticalLayout { + padding: 0px; + Rectangle { background: blue; height: 7px; } + Rectangle { + border-color: red; border-width: 2px; + HorizontalLayout { + Rectangle { border-color: blue; border-width: 2px; } + Rectangle { border-color: green; border-width: 2px; } + } + } + } +} +``` + + +## Relative Lengths +Use percentages: +```slint +width: parent.width * 50%; +height: parent.height * 50%; +``` +Shorthand: +```slint +width: 50%; +height: 50%; +``` + +--- + +## Alignment Options +`alignment` values: +- `stretch` (default) +- `start` +- `end` +- `center` +- `space-between` +- `space-around` + +--- + +## Stretch Algorithm +1. Elements sized to **min size**. +2. Extra space shared by **stretch factor**. +3. Does not exceed **max size**. + + +## Looping in Layouts +Layouts can use `for` or `if` to dynamically insert elements: +```slint +HorizontalLayout { + Rectangle { background: green; } + for t in [ "Hello", "World" ] : Text { text: t; } +} +``` + + +## GridLayout +Arrange elements in a grid. + +With `Row`: +```slint +GridLayout { + Row { + Rectangle { background: red; } + Rectangle { background: blue; } + } +} +``` + +With `row` / `col`: +```slint +GridLayout { + Rectangle { background: red; } + Rectangle { background: blue; col: 1; } +} +``` + + +## Container Components with `@children` +Control where child elements go: +```slint +component BoxWithLabel inherits GridLayout { + Row { Text { text: "label text here"; } } + Row { @children } +} +``` + diff --git a/docs/desktop_applications/03_Slint/07_Globals.md b/docs/desktop_applications/03_Slint/07_Globals.md new file mode 100644 index 0000000..965a672 --- /dev/null +++ b/docs/desktop_applications/03_Slint/07_Globals.md @@ -0,0 +1,98 @@ +# Globals in Slint + +## Introduction +**Globals** are singletons that hold properties or callbacks accessible throughout the entire project. +Declare them using: +```slint +global Name { + /* properties or callbacks */ +} +``` + +Access global properties or callbacks via: +``` +Name.property +Name.callback() +``` + + +## Example: Global Color Palette +```slint +global Palette { + in-out property primary: blue; + in-out property secondary: green; +} + +export component Example inherits Rectangle { + background: Palette.primary; + border-color: Palette.secondary; + border-width: 2px; +} +``` + + +## Exporting Globals +- Use `export global` to make a global available to other `.slint` files. +- To make a global visible to **native code** (Rust, C++, NodeJS, Python), re-export it from the file that exports your main application component. + +Example: +```slint +export global Logic { + in-out property the-value; + pure callback magic-operation(int) -> int; +} + +// Main application component +export component App inherits Window { + // ... +} +``` + + +## Rust Example: Accessing a Global +```rust +slint::slint!{ +export global Logic { + in-out property the-value; + pure callback magic-operation(int) -> int; +} + +export component App inherits Window { + // ... +} +} + +fn main() { + let app = App::new(); + app.global::().on_magic_operation(|value| { + eprintln!("magic operation input: {}", value); + value * 2 + }); + app.global::().set_the_value(42); + // ... +} +``` + + +## Re-Exposing Globals in Components +You can re-expose properties or callbacks from a global using **two-way bindings** (`<=>`). + +```slint +global Logic { + in-out property the-value; + pure callback magic-operation(int) -> int; +} + +component SomeComponent inherits Text { + text: "The magic value is:" + Logic.magic-operation(42); +} + +export component MainWindow inherits Window { + in-out property the-value <=> Logic.the-value; + pure callback magic-operation <=> Logic.magic-operation; + + SomeComponent {} +} +``` + +This allows **native code** to access or modify global values through your component. diff --git a/docs/desktop_applications/03_Slint/08_Repetition.md b/docs/desktop_applications/03_Slint/08_Repetition.md new file mode 100644 index 0000000..2fac730 --- /dev/null +++ b/docs/desktop_applications/03_Slint/08_Repetition.md @@ -0,0 +1,101 @@ +# Repetition in Slint + +## Introduction +The **for-in** syntax in Slint is used to create multiple instances of an element dynamically. + +**Syntax:** +```slint +for name[index] in model : id := Element { ... } +``` +- **name** → variable holding the model's value in each iteration. +- **index** *(optional)* → index of the current element. +- **id** *(optional)* → identifier for the element. +- **model** → integer, array, or natively declared model. + +--- + +## Supported Model Types +1. **Integer** → repeats the element that many times. +2. **Array** → repeats the element for each array entry. +3. **Native Model** → repeats for each item in a model declared in Slint or provided from native code. + +The loop variable (`name`) is available within the repeated element as a pseudo-property. + + +## Examples + +### Integer/Array Literal Model +```slint +export component Example inherits Window { + preferred-width: 300px; + preferred-height: 100px; + + for my-color[index] in [ #e11, #1a2, #23d ]: Rectangle { + height: 100px; + width: 60px; + x: self.width * index; + background: my-color; + } +} +``` + +### Array of Structs as a Model +```slint +export component Example inherits Window { + preferred-width: 50px; + preferred-height: 50px; + + in property <[{foo: string, col: color}]> model: [ + {foo: "abc", col: #f00 }, + {foo: "def", col: #00f }, + ]; + + VerticalLayout { + for data in root.model: my-repeated-text := Text { + color: data.col; + text: data.foo; + } + } +} +``` + + +## Arrays and Models in Slint +Arrays are declared by enclosing the element type in square brackets: +```slint +export component Example { + in-out property<[int]> list-of-int: [1, 2, 3]; + in-out property<[{a: int, b: string}]> list-of-structs: [ + { a: 1, b: "hello" }, + { a: 2, b: "world" } + ]; +} +``` + +**Array literals** and **array properties** can both be used as models in `for` expressions. + + +## Array Operations +Arrays and models support: + +### Length +```slint +list-of-int.length +``` +Returns the number of items. + +### Index Access +```slint +list-of-int[0] // first element +``` +If the index is out of bounds, Slint returns a default-constructed value. + +**Example:** +```slint +export component Example { + in-out property<[int]> list-of-int: [1, 2, 3]; + + out property list-len: list-of-int.length; + out property first-int: list-of-int[0]; +} +``` diff --git a/docs/desktop_applications/03_Slint/09_Callbacks.md b/docs/desktop_applications/03_Slint/09_Callbacks.md new file mode 100644 index 0000000..e2109b5 --- /dev/null +++ b/docs/desktop_applications/03_Slint/09_Callbacks.md @@ -0,0 +1,130 @@ +# Functions and Callbacks in Slint + +## Introduction +Functions in Slint allow you to **name, organize, and reuse** pieces of logic. +They can be defined inside **components** or **elements within components** — but not globally, inside structs/enums, or nested within other functions. + + +## Declaring Functions +Functions are declared with the `function` keyword: + +```slint +export component Example { + function my-function(parameter: int) -> string { + return "result"; + } +} +``` +- **Parameters**: `(name: type)` format, passed by value. +- **Return type**: after `->`. +- If no explicit `return`, the last statement's value is returned. +- Use `pure` for side-effect-free functions. + + +## Calling Functions +Functions can be called: +- Without an element name (like a normal function call). +- With an element name (like a method). + +```slint +import { Button } from "std-widgets.slint"; + +export component Example { + property my-property: my-function(); + property my-other-property: my_button.my-other-function(); + + pure function my-function() -> string { + return "result"; + } + + Text { + text: root.my-function(); + } + + my_button := Button { + pure function my-other-function() -> int { + return 42; + } + } +} +``` + + +## Function Visibility +- **private** (default): accessible only within the component. +- **public**: accessible from other components via a target (child instance). +- **protected**: accessible only by components that inherit from it. + +Example: +```slint +export component HasFunction { + public pure function double(x: int) -> int { + return x * 2; + } +} + +export component CallsFunction { + property test: my-friend.double(1); + my-friend := HasFunction {} +} +``` + +Functions in **child elements** are not accessible externally, even if `public`. + +Public functions in **exported components** can also be called from **backend code** (Rust, C++, JS). + + +## Functions vs Callbacks +**Similarities**: +- Callable blocks of code. +- Have parameters and return values. +- Can be `pure`. + +**Differences**: +- **Callbacks** can be implemented from backend code. +- **Functions** must be fully implemented in Slint. +- Callback syntax differs and supports aliases with `<=>`. + + +## Declaring Callbacks +Callbacks are declared with the `callback` keyword. + +Example: +```slint +export component Example inherits Rectangle { + callback hello; + + area := TouchArea { + clicked => { + root.hello() + } + } +} +``` + +### With Parameters: +```slint +callback hello(int, string); +hello(aa, bb) => { /* handler code */ } +``` + +### With Return Value: +```slint +callback hello(int, int) -> int; +hello(aa, bb) => { aa + bb } +``` + +### With Named Arguments: +```slint +callback hello(foo: int, bar: string); +hello(aa, bb) => { /* code */ } +``` + + +## Callback Aliases +You can alias callbacks using `<=>`: +```slint +callback clicked <=> area.clicked; +area := TouchArea {} +``` + diff --git a/docs/desktop_applications/03_Slint/10_Structs-and-Enums b/docs/desktop_applications/03_Slint/10_Structs-and-Enums new file mode 100644 index 0000000..cddd148 --- /dev/null +++ b/docs/desktop_applications/03_Slint/10_Structs-and-Enums @@ -0,0 +1,57 @@ +# Structs and Enums in Slint + +## Structs +Use the `struct` keyword to define named data structures. + +Example: +```slint +export struct Player { + name: string, + score: int, +} + +export component Example { + in-out property player: { name: "Foo", score: 100 }; +} +``` +- Default values: all fields are set to their type’s default value. + + +## Anonymous Structures +You can declare anonymous structs inline using: +```slint +{ field1: type1, field2: type2 } +``` +Initialize them with: +```slint +{ field1: value1, field2: value2 } +``` + +Example: +```slint +export component Example { + in-out property<{name: string, score: int}> player: { name: "Foo", score: 100 }; + in-out property<{a: int, }> foo: { a: 3 }; +} +``` +- Trailing commas are allowed in type declarations and initializers. + + +## Enums +Use the `enum` keyword to define enumerations. + +Example: +```slint +export enum CardSuit { clubs, diamonds, hearts, spade } + +export component Example { + in-out property card: spade; + out property is-clubs: card == CardSuit.clubs; +} +``` +- Enum values are accessed as `EnumName.value` (e.g., `CardSuit.spade`). +- The enum name can be omitted if: + - The property is of that enum type. + - The return value of a callback is of that enum type. +- Default value of an enum type is always its **first** value. + diff --git a/docs/desktop_applications/04_Data/01_File-io.md b/docs/desktop_applications/04_Data/01_File-io.md new file mode 100644 index 0000000..0bda565 --- /dev/null +++ b/docs/desktop_applications/04_Data/01_File-io.md @@ -0,0 +1,112 @@ +--- +title: File I/O in Rust +--- + +File Input/Output (I/O) is how your application reads and writes data to the computer's file system. In Rust, these operations are handled by the `std::fs` module and a set of I/O traits, all designed to be safe and to force you to handle potential errors. + +--- + +### Opening a File + +The main type for file operations is `std::fs::File`. To get an instance of this type, you use `File::open`. This function returns a **`Result`**, not the `File` itself. This is because a file might not exist or be accessible, and Rust requires you to handle these possibilities. + +```rust +use std::fs::File; +use std::io::ErrorKind; + +fn main() { + let file_result = File::open("config.txt"); + + // The 'match' expression handles both possible outcomes of the Result. + let file = match file_result { + Ok(file) => file, // The file was opened successfully, we can now use it. + Err(error) => match error.kind() { + // A common, recoverable error: file not found. + ErrorKind::NotFound => { + println!("File not found. Creating a new one..."); + File::create("config.txt").unwrap() + }, + // Any other error is unrecoverable, so we panic. + other_error => { + panic!("Problem opening the file: {:?}", other_error); + } + }, + }; +} +``` + +**Why it can throw errors**: Any interaction with the operating system is prone to failure. The disk could be full, the file could be deleted by another process, or you may not have the correct permissions. Rust's `Result` type prevents these failures from causing a program crash by making you explicitly write code to handle them. + +--- + +### Reading and Writing + +Once a file is open, you can read from or write to it. The traits `std::io::Read` and `std::io::Write` provide the necessary methods. + +#### Reading a File + +To read a text file's entire contents into a `String`, you can use the `read_to_string` method. This method also returns a `Result` because the read operation can fail. + +```rust +use std::fs::File; +use std::io::{self, Read}; + +// Returns a Result containing the file contents on success, or an error on failure. +fn read_config(path: &str) -> Result { + let mut file = File::open(path)?; // The '?' operator propagates an error if the file can't be opened. + let mut contents = String::new(); + file.read_to_string(&mut contents)?; // '?' propagates an error if reading fails. + Ok(contents) +} +``` + +Here, we use the **`?` operator** which is a clean and concise way to handle `Result` types. If the `Result` is `Ok`, the value is unwrapped. If it's `Err`, the function immediately returns with that error. This keeps the code from being cluttered with `match` statements. + +#### Writing to a File + +To write to a file, you first create it with `File::create`, and then use methods like `write_all`. + +```rust +use std::fs::File; +use std::io::{self, Write}; + +fn save_data(path: &str, data: &str) -> Result<(), io::Error> { + let mut file = File::create(path)?; // '?' propagates an error if the file can't be created. + file.write_all(data.as_bytes())?; // '?' propagates an error if writing fails. + Ok(()) +} +``` + +This function demonstrates a common pattern where `Ok(())` is returned to signal a successful operation that doesn't produce a value. + +--- + +### Putting It All Together + +For a desktop application, you'll typically have functions that handle I/O and a top-level `main` function that manages the overall flow and displays any final errors to the user. + +```rust +use std::io::{self, Write}; +use std::fs::File; +use std::error::Error; + +// main() can return a Result to handle errors gracefully. +fn main() -> Result<(), Box> { + // Attempt to save data. + let save_result = save_data("log.txt", "Application started."); + + // Handle the result at the top level. + if let Err(e) = save_result { + eprintln!("Error saving log file: {}", e); + // We can choose to continue or exit. + } + + // This will work because the ? operator in save_data() ensures we get an error if saving fails. + save_data("log.txt", "Another line of text.")?; + + println!("File operations successful!"); + Ok(()) // All went well. +} +``` + +In this example, the `main` function itself returns a `Result`, allowing the program to cleanly exit with an error code if any of the file operations fail. This is a robust pattern for real-world desktop applications. diff --git a/docs/desktop_applications/04_Data/02_Json-and-serde.md b/docs/desktop_applications/04_Data/02_Json-and-serde.md new file mode 100644 index 0000000..40f9342 --- /dev/null +++ b/docs/desktop_applications/04_Data/02_Json-and-serde.md @@ -0,0 +1,191 @@ +--- +title: JSON and the Serde Crate +--- + +--- + +JSON (JavaScript Object Notation) is a lightweight, human-readable data format used to exchange data between a server and a web application, or as a configuration file format. It is language-independent, making it the de-facto standard for APIs and many other data-sharing scenarios. A typical JSON object looks like a key-value map, with nested structures and arrays. + +--- + +### Introducing Serde + +**Serde** is Rust's premier framework for **ser**ializing and **de**serializing data. Serialization is the process of converting a Rust data structure into a format like JSON, while deserialization is the reverse—converting JSON data into a Rust data structure. Serde is so widely used because it's fast, robust, and provides a simple way to convert complex data structures without writing boilerplate code. We'll use the `serde_json` crate, which provides the tools for handling JSON specifically. + +--- + +### Setting Up Serde + +To use Serde in your project, you must add it and `serde_json` to your `Cargo.toml` file. The `derive` feature for `serde` is essential, as it allows Rust to automatically generate the code for serialization and deserialization for your structs and enums. + +```toml +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +``` + +--- + +### Deserialization: JSON to Rust Struct + +The most common use case is reading a JSON file or API response and converting it into a usable Rust data structure. You do this by defining a `struct` that mirrors the structure of your JSON data and then using the `serde_json::from_str` or `serde_json::from_reader` functions. + +Let's assume we have a JSON file named `user.json`: + +```json +{ + "user_id": 42, + "username": "alice", + "is_active": true, + "roles": ["admin", "member"] +} +``` + +Now, let's create a Rust struct that corresponds to this JSON data. + +```rust +use serde::Deserialize; +use std::fs::File; +use std::io::Read; + +// Use the Deserialize trait to allow Serde to parse JSON into this struct. +#[derive(Deserialize, Debug)] +struct User { + user_id: u32, + username: String, + is_active: bool, + roles: Vec, +} + +fn main() -> Result<(), Box> { + // Open the JSON file. + let mut file = File::open("user.json")?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + // Use serde_json::from_str to deserialize the JSON into our User struct. + // This function returns a Result, which we handle with the '?' operator. + let user: User = serde_json::from_str(&contents)?; + + println!("Deserialized user: {:?}", user); + + Ok(()) +} +``` + +The `#[derive(Deserialize, Debug)]` attribute is a procedural macro that automatically generates the code needed to convert a JSON object into a `User` struct. This is the core of Serde's power. + +--- + +### Serialization: Rust Struct to JSON + +The reverse process, serialization, is just as easy. You create an instance of your Rust struct and use `serde_json::to_string` to convert it into a JSON string. + +```rust +use serde::Serialize; +use std::fs::File; +use std::io::Write; + +// We derive Serialize to convert the struct into JSON. +#[derive(Serialize, Debug)] +struct Product { + id: u32, + name: String, + price: f64, +} + +fn main() -> Result<(), Box> { + let product = Product { + id: 101, + name: "Laptop".to_string(), + price: 1200.50, + }; + + // Serialize the Product struct into a JSON string. + let serialized_product = serde_json::to_string(&product)?; + + println!("Serialized JSON: {}", serialized_product); + + // Save the serialized JSON to a file. + let mut file = File::create("product.json")?; + file.write_all(serialized_product.as_bytes())?; + + println!("Product saved to product.json"); + + Ok(()) +} +``` + +The `to_string` function returns a `Result`, which is crucial for handling potential errors during serialization. + +--- + +### Advanced Serde Attributes + +Serde provides many attributes to customize the serialization and deserialization process, which are essential for handling real-world JSON data. + +- `#[serde(rename = "...")]`: This attribute is used when the key in your JSON file has a different naming convention than your Rust struct field. For example, a common convention is to use snake_case in Rust and kebab-case or camelCase in JSON. + + ```rust + #[derive(Deserialize, Debug)] + struct UserProfile { + #[serde(rename = "first-name")] // Handles "first-name" from JSON + first_name: String, + #[serde(rename = "last_name")] // Handles "last_name" from JSON + last_name: String, + } + ``` + +- `#[serde(default)]`: If a field is optional in the JSON data, you can mark it with `default`. Serde will use the default value for the field's type if the key is missing from the JSON. + + ```rust + #[derive(Deserialize, Debug)] + struct Settings { + theme: String, + #[serde(default)] // This field will be 'false' if not present in the JSON. + dark_mode: bool, + } + ``` + +### Naming Convention Differences with Serde + +When the naming conventions in your JSON data don't match Rust's standard **snake_case**, you can use the **`#[serde(rename = "...")]`** attribute to map the JSON key to your Rust struct field. This is very common for handling JSON from APIs that use **camelCase** or **kebab-case**. + +--- + +For example, if your JSON data uses `userName` (camelCase) and `isVerified` (camelCase), but you want to use `user_name` and `is_verified` in your Rust struct, you would do this: + +```rust +use serde::{Deserialize, Serialize}; + +// The JSON data has keys 'userName' and 'isVerified' +// but our Rust struct uses snake_case as per Rust conventions. +#[derive(Debug, Serialize, Deserialize)] +struct User { + #[serde(rename = "userName")] // Maps 'userName' from JSON to user_name in Rust + user_name: String, + + #[serde(rename = "isVerified")] // Maps 'isVerified' from JSON to is_verified + is_verified: bool, +} + +fn main() { + let json_data = r#" + { + "userName": "rustacean_user", + "isVerified": true + } + "#; + + let user: User = serde_json::from_str(json_data).unwrap(); + + println!("Deserialized User: {:?}", user); + // Output: Deserialized User: User { user_name: "rustacean_user", is_verified: true } +} +``` + +The `#[serde(rename = "…")]` attribute tells Serde to look for the key specified in the JSON data and use its value to populate the corresponding Rust field. This allows you to maintain idiomatic Rust code while working with different external data formats. + +--- + +Serde's power lies in these attributes, which allow you to effortlessly map complex JSON data to clean, idiomatic Rust code. diff --git a/docs/desktop_applications/05_Networking/01_Async-await.md b/docs/desktop_applications/05_Networking/01_Async-await.md new file mode 100644 index 0000000..63b941a --- /dev/null +++ b/docs/desktop_applications/05_Networking/01_Async-await.md @@ -0,0 +1,128 @@ +--- +title: Async and Await +--- + +### Asynchronous Programming with `async` and `await` + +Asynchronous programming is a way for your program to perform tasks without waiting for a blocking operation (like a slow network request or reading a large file) to finish. Instead of blocking, your program can start another task while it waits, maximizing efficiency. In Rust, this is achieved using the **`async`** and **`await`** keywords, which work together with an asynchronous runtime to manage these non-blocking operations. + +--- + +### The `async` Keyword: Creating a `Future` + +When you mark a function with the `async` keyword, you're telling the compiler that this function contains an asynchronous operation. Critically, an `async` function does not run its code immediately when called. Instead, it returns a special type called a **`Future`**. + +Think of a `Future` as a "promise" or a "recipe" for an upcoming value. It's an inert object that describes what work needs to be done. The work described in the `async` function won't start until this `Future` is actively executed by a runtime. + +```rust +async fn my_async_task() -> String { + // This code will only run when the Future is awaited. + "Task finished".to_string() +} + +fn main() { + // Calling the async function returns a Future, but nothing runs. + let future = my_async_task(); + + // The program would end here without executing my_async_task(). +} +``` + +This example shows that just calling `my_async_task()` by itself does nothing. The `Future` is created and immediately dropped because it's never told to run. + +--- + +### The `await` Keyword and the Runtime + +The **`await`** keyword is the key to running a `Future`. It tells the program to "execute this `Future` and give me its value when it's ready." When an `await` call is made, the current task pauses, yielding control to an asynchronous runtime. The runtime then looks for other tasks to run that are ready to make progress. + +For this system to work, your application needs an **asynchronous runtime**. This is the scheduler that manages all the `Future`s, decides when they should run, and wakes them up when a blocking operation (like a network request) completes. **Tokio** is the most popular runtime for Rust. + +To use Tokio, you must first add it to your `Cargo.toml`: + +```toml +[dependencies] +tokio = { version = "1", features = ["full"] } +``` + +Then, you can use the `#[tokio::main]` macro to set up the runtime and make your `main` function an async entry point. + +```rust +use tokio::time::{sleep, Duration}; + +#[tokio::main] +async fn main() { + println!("Start of program."); + + // The program pauses here for 2 seconds without blocking the thread. + sleep(Duration::from_secs(2)).await; + + println!("End of program after waiting."); +} +``` + +--- + +### Concurrency with `await`: `join!` and `select!` + +The real power of `async` and `await` is the ability to run multiple tasks concurrently. By starting several `Future`s and then awaiting them, your program's runtime will manage them all efficiently. The **`tokio::join!`** macro is perfect for this, as it waits for all given `Future`s to complete. + +```rust +use tokio::time::{sleep, Duration}; +use tokio::join; + +async fn fetch_user_data() -> String { + sleep(Duration::from_secs(3)).await; + println!("User data fetched after 3s."); + "User".to_string() +} + +async fn fetch_product_data() -> String { + sleep(Duration::from_secs(1)).await; + println!("Product data fetched after 1s."); + "Product".to_string() +} + +#[tokio::main] +async fn main() { + println!("Fetching data concurrently..."); + + // These two futures are started at the same time and run together. + let (user_data, product_data) = join!(fetch_user_data(), fetch_product_data()); + + println!("Total execution time is ~3s, not 4s."); + println!("Result 1: {}", user_data); + println!("Result 2: {}", product_data); +} +``` + +In this example, the total execution time is approximately 3 seconds (the duration of the longest task), not the sum of both tasks, because they run concurrently. The `join!` macro waits for both futures to complete before moving on. The `tokio::select!` macro is similar, but it completes as soon as one of the futures finishes. + +--- + +### The "No Await" Pitfall: Dropping a `Future` + +As mentioned earlier, simply calling an `async` function and not using `await` means the `Future` is never executed. It's created and immediately dropped at the end of the line. + +```rust +use tokio::time::{sleep, Duration}; + +async fn my_task() { + sleep(Duration::from_secs(1)).await; + println!("This line will never be printed."); +} + +#[tokio::main] +async fn main() { + println!("Starting program."); + + // We call the async function, but we don't await its Future. + // The Future is created but immediately dropped. + my_task(); + + println!("Finished program."); + // The program ends immediately, and 'my_task' never runs. +} +``` + +This is a common mistake for newcomers to async Rust. You must always `await` a `Future` or pass it to a runtime scheduler (e.g., with a function like `tokio::spawn`) for its code to execute. diff --git a/docs/desktop_applications/05_Networking/02_Http-requests-with-reqwest.md b/docs/desktop_applications/05_Networking/02_Http-requests-with-reqwest.md new file mode 100644 index 0000000..449b21e --- /dev/null +++ b/docs/desktop_applications/05_Networking/02_Http-requests-with-reqwest.md @@ -0,0 +1,219 @@ +--- +title: Http Requests with reqwest +--- + +--- + +Up until now, our apps have been self-contained. But real-world applications often need to fetch data from the internet (like weather updates, news, or country statistics) or send data to a server (like saving user preferences or posting messages). Today, we'll learn the fundamental principles of how computers communicate over the web + +--- + +### Introduction to Inter-Computer Communication + +Why do our applications need to talk to other computers? + +Imagine a world where every app is an island. Your weather app would only know the weather you manually typed in. Your social media app couldn't show you friends' updates. That's not very useful, right? + +- **Accessing Remote Data:** The internet is a vast repository of information (APIs\!). Our apps need to fetch this data. +- **Sharing Information:** Multiple users need to access and modify the same data (e.g., a shared to-do list, a chat application). This data lives on a central server. +- **Leveraging Services:** Apps can use specialized services running on other computers (e.g., payment processing, machine learning models, cloud storage). + +This communication is typically done over networks, and for web-based services, the **HTTP protocol** is the universal language. + +--- + +### HTTP Protocol: Structure and Fundamentals + +**HTTP** stands for **Hypertext Transfer Protocol**. It's the foundation of data communication for the World Wide Web. Think of it as a set of rules for how clients (like your browser or your Rust desktop app) and servers (where data lives) exchange messages. + +#### **Client-Server Model:** + +- **Client:** Your Rust desktop application (or a web browser) that _requests_ data or services. +- **Server:** A remote computer that _provides_ data or services in response to client requests. + +#### **Request-Response Cycle:** + +The core of HTTP is a simple cycle: + +1. **Client sends a Request:** Your app sends a message to the server, asking for something. +2. **Server sends a Response:** The server processes the request and sends a message back to your app. + +#### **Key Parts of an HTTP Request:** + +- **Method (Verb):** Tells the server what action you want to perform. + - **`GET`**: Retrieve data (e.g., get a list of countries). + - **`POST`**: Send new data to create a resource (e.g., create a new user). + - **`PUT`**: Send data to update an existing resource (e.g., update a user's profile). + - **`DELETE`**: Remove a resource (e.g., delete a user). + - _(These map directly to the **CRUD** operations: Create, Read, Update, Delete)_ +- **URL (Uniform Resource Locator):** The address of the resource you're interacting with (e.g., `https://restcountries.com/v3.1/all`). +- **Headers:** Key-value pairs providing metadata about the request (e.g., `Content-Type: application/json`, `Authorization: Bearer `). +- **Body (Optional):** The actual data you're sending to the server, typically for `POST` or `PUT` requests (e.g., a JSON object representing a new country). + +#### **Key Parts of an HTTP Response:** + +- **Status Code:** A three-digit number indicating the outcome of the request. + - `200 OK`: Request successful. + - `201 Created`: Resource successfully created (for `POST`). + - `204 No Content`: Request successful, but no content to return (for `DELETE`). + - `400 Bad Request`: Client sent an invalid request. + - `404 Not Found`: Resource not found on the server. + - `500 Internal Server Error`: Server encountered an error. +- **Headers:** Metadata about the response (e.g., `Content-Type: application/json`). +- **Body (Optional):** The data returned by the server (e.g., a JSON array of countries). + +--- + +### 6.4 Making External API Calls with `reqwest` and `tokio` (25 min) + +`reqwest` is the most popular and robust HTTP client library for Rust. It integrates seamlessly with Tokio for asynchronous operations. + +#### **Setting Up `reqwest`:** + +1. **Add `reqwest` to `Cargo.toml`:** + ```toml + # Cargo.toml + [dependencies] + # ... other dependencies ... + tokio = { version = "1", features = ["full"] } + reqwest = { version = "0.11", features = ["json"] } # "json" feature for easy JSON handling + ``` + - `features = ["json"]` is important as it enables `reqwest`'s convenient `.json()` method for responses and `.json(&data)` for requests. + +#### **Example: Making a `GET` Request to the Rest Countries API** + +Let's fetch data for a specific country (e.g., Japan) using `reqwest`. + +```rust +// src/main.rs +use reqwest; // The HTTP client library +use tokio; // The async runtime + +#[tokio::main] +async fn main() -> Result<(), Box> { // main now returns a Result + println!("Fetching country data..."); + + let country_code = "JPN"; // Japan's code + let url = format!("https://restcountries.com/v3.1/alpha/{}", country_code); + + // Make the GET request. .await? handles the Future and propagates errors. + let response = reqwest::get(&url).await?; + + // Check if the request was successful (2xx status code) + if response.status().is_success() { + // Get the response body as text + let body = response.text().await?; + println!("Response for {}:\n{}", country_code, body); + } else { + println!("Failed to fetch data for {}. Status: {}", country_code, response.status()); + } + + Ok(()) // Indicate success +} +``` + +- **`Result<(), Box>`:** This return type for `main` is common for async Rust examples. It means the function either succeeds (`Ok(())`) or returns an error that can be boxed (useful for diverse error types). +- **`.await?`:** This is a powerful shorthand. It's equivalent to: + ```rust + let result = some_async_operation().await; + match result { + Ok(val) => val, + Err(err) => return Err(err.into()), // Convert error and return from function + } + ``` + It allows you to write async code that looks sequential but handles errors gracefully. + +--- + +### Processing API Responses with `serde` + +APIs almost always communicate using **JSON** (JavaScript Object Notation). Rust needs a way to convert this JSON text into Rust `struct`s (Deserialization) and convert Rust `struct`s into JSON text (Serialization). This is where the `serde` ecosystem comes in. + +- **`serde`**: The core serialization/deserialization framework. +- **`serde_json`**: A crate that implements `serde` for JSON data. + +#### **Setting Up `serde`:** + +1. **Add `serde` and `serde_json` to `Cargo.toml`:** + ```toml + # Cargo.toml + [dependencies] + # ... other dependencies ... + tokio = { version = "1", features = ["full"] } + reqwest = { version = "0.11", features = ["json"] } + serde = { version = "1.0", features = ["derive"] } # "derive" feature for automatic trait implementation + serde_json = "1.0" + ``` + - `features = ["derive"]` for `serde` allows you to automatically implement `Serialize` and `Deserialize` traits on your structs using `#[derive()]`. + +#### **Example: Deserializing Country Data (JSON to Rust Struct)** + +Let's define a Rust `struct` that matches the structure of the JSON response we expect from the Rest Countries API for a single country. + +```rust +// src/main.rs +use reqwest; +use tokio; +use serde::Deserialize; // Import Deserialize trait + +// Use #[derive(Debug, Deserialize)] to automatically implement traits +// Debug allows us to print the struct with {:?} +// Deserialize allows serde to convert JSON into this struct +#[derive(Debug, Deserialize)] +struct Country { + name: CountryName, + population: u64, + region: String, + languages: Option>, // Languages can be optional + capital: Option>, // Capital can be optional and an array +} + +#[derive(Debug, Deserialize)] +struct CountryName { + common: String, + official: String, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Fetching country data..."); + + let country_code = "JPN"; + let url = format!("https://restcountries.com/v3.1/alpha/{}", country_code); + + let client = reqwest::Client::new(); // It's good practice to reuse a reqwest client + let response = client.get(&url).send().await?; + + if response.status().is_success() { + // The Rest Countries API for /alpha/:code returns an array of countries, + // even if it's just one. So we expect a Vec. + let countries_data: Vec = response.json().await?; // Deserialize JSON directly to Vec + + if let Some(country) = countries_data.into_iter().next() { // Take the first country from the array + println!("Fetched Country: {:#?}", country); + println!("Common Name: {}", country.name.common); + println!("Population: {}", country.population); + println!("Region: {}", country.region); + + if let Some(langs) = country.languages { + println!("Languages: {:?}", langs.values().collect::>()); + } + if let Some(caps) = country.capital { + println!("Capital: {}", caps.join(", ")); + } + } else { + println!("No country data found in response."); + } + } else { + println!("Failed to fetch data for {}. Status: {}", country_code, response.status()); + } + + Ok(()) +} +``` + +- **`#[derive(Debug, Deserialize)]`**: These are procedural macros that automatically generate the necessary code for your `Country` and `CountryName` structs to be printable for debugging and deserializable from JSON. +- **`response.json().await?`**: This is a convenient `reqwest` method (enabled by the `json` feature) that attempts to parse the response body directly into the specified Rust type (here, `Vec`). It returns a `Result`. +- **`Option` and `Vec` for missing/array data:** Notice how `languages` and `capital` are wrapped in `Option` and `Vec` to correctly match the API's JSON structure, which might have missing fields or arrays. + +--- diff --git a/docs/desktop_applications/05_Networking/03_Fetching-public-apis.md b/docs/desktop_applications/05_Networking/03_Fetching-public-apis.md new file mode 100644 index 0000000..70e3113 --- /dev/null +++ b/docs/desktop_applications/05_Networking/03_Fetching-public-apis.md @@ -0,0 +1,141 @@ +# Fetching Public APIs in Rust + +This guide shows how to fetch public APIs in Rust using `reqwest` and `tokio`. +We’ll walk through small examples and explain what’s happening. + +--- + +## 1. Setup + +In `Cargo.toml`: + +```toml +[dependencies] +tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +``` + +**Explanation:** +- **tokio**: Async runtime that allows us to write asynchronous code (`async`/`await`). +- **reqwest**: HTTP client library for making GET/POST requests. +- **serde** and **serde_json**: For converting JSON data into Rust structs automatically. + +--- + +## 2. Fetch a Joke + +```rust +use reqwest::Client; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct Joke { setup: String, punchline: String } + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + let url = "https://official-joke-api.appspot.com/random_joke"; + + let joke: Joke = client.get(url).send().await?.json().await?; + + println!("{} — {}", joke.setup, joke.punchline); + Ok(()) +} +``` + +**Explanation:** +- We create a `Client` to make HTTP requests. +- `client.get(url)` creates a GET request. +- `.send().await?` sends the request and waits for the response. +- `.json().await?` converts the JSON into our `Joke` struct automatically using `serde`. +- We then print the joke’s setup and punchline. + +--- + +## 3. Weather API Example (OpenWeatherMap) + +You can sign up for a free API key at [https://openweathermap.org/](https://openweathermap.org/). + +```rust +use reqwest::Client; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct WeatherResponse { + name: String, + main: Main, + weather: Vec, +} + +#[derive(Debug, Deserialize)] +struct Main { temp: f64 } + +#[derive(Debug, Deserialize)] +struct Weather { description: String } + +#[tokio::main] +async fn main() -> Result<(), Box> { + let api_key = "YOUR_API_KEY"; // from OpenWeather + let city = "London"; + let url = format!( + "https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric", + city, api_key + ); + + let client = Client::new(); + let resp: WeatherResponse = client.get(&url).send().await?.json().await?; + + println!("Weather in {}: {}°C, {}", + resp.name, + resp.main.temp, + resp.weather.first().map(|w| &w.description).unwrap_or(&"No data".to_string()) + ); + + Ok(()) +} +``` + +**Explanation:** +- The `WeatherResponse` struct matches the JSON structure returned by OpenWeather. +- We use `format!` to insert the city name and API key into the URL. +- The `units=metric` parameter tells the API to return Celsius temperatures. +- After parsing the JSON, we display the city name, temperature, and weather description. +- Always store your API key in an environment variable in real apps for security. + +--- + +## 4. Fetch a Random Cat Fact + +```rust +use reqwest::Client; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct CatFact { fact: String } + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + let url = "https://catfact.ninja/fact"; + + let fact: CatFact = client.get(url).send().await?.json().await?; + println!("Cat fact: {}", fact.fact); + Ok(()) +} +``` + +**Explanation:** +- This example is almost identical to the joke example but uses another API. +- The API returns JSON with a single field, `fact`. +- This is perfect for learning because you can easily change the URL and struct to fetch other simple APIs. + +--- + +## General Notes + +- **Error handling**: In production, always check if the request succeeded before parsing. Use `.error_for_status()` to catch HTTP errors. +- **Performance**: Reuse the same `Client` across requests instead of creating a new one each time. +- **Security**: Never hardcode API keys in your source code. Use environment variables or secure config files. +- **Data mapping**: Struct field names must match the JSON keys or use `#[serde(rename = "json_key_name")]`. diff --git a/docs/desktop_applications/05_Networking/04_Network-error-handling.md b/docs/desktop_applications/05_Networking/04_Network-error-handling.md new file mode 100644 index 0000000..1b05273 --- /dev/null +++ b/docs/desktop_applications/05_Networking/04_Network-error-handling.md @@ -0,0 +1,100 @@ +--- +title: Network Error Handling +--- + +--- + +Network operations are inherently unreliable; connections can fail, servers can be offline, and data can be malformed. In Rust, the **`reqwest`** crate handles these possibilities by returning a `Result` for every operation, allowing you to build robust applications that can gracefully recover from failures. This lesson will show you how to handle these errors, from simple propagation to detailed analysis. + +--- + +### 1\. The `reqwest::Error` Type + +All errors from `reqwest` are consolidated into a single, comprehensive **`reqwest::Error`** enum. This error type provides detailed information about what went wrong, including connection issues, HTTP status codes, and serialization failures. You don't need to import `reqwest::Error` explicitly; it's the type returned by `reqwest`'s functions, and the `?` operator works on it automatically. + +--- + +### 2\. Simple Error Handling with `?` + +The simplest and most common way to handle a `reqwest` error is with the **`?`** operator. This works because most `reqwest` functions return a `Result`. The `?` operator will propagate any error back to the caller, making your code concise. + +```rust +use reqwest; +use tokio; + +#[tokio::main] +async fn main() -> Result<(), reqwest::Error> { + // This will work because main() returns a Result<(), reqwest::Error> + // The '?' operator will propagate any error from the get() call. + let response = reqwest::get("https://httpbin.org/get").await?; + + // The '?' will also propagate an error if the response body cannot be read. + let body = response.text().await?; + + println!("Response body:\n{}", body); + + Ok(()) +} +``` + +This is perfect for small applications or for when you simply want to log an error and exit. + +--- + +### 3\. Granular Error Handling with `match` + +For more advanced applications, you may want to handle different types of errors differently. For example, a network timeout might be retried, while a "404 Not Found" error might be displayed to the user. You can use a `match` expression or methods on the `reqwest::Error` type to get this level of detail. + +The `reqwest::Error` has several useful methods to check for specific error categories: + +- **`is_connect()`**: Returns true if the error was a connection issue (e.g., DNS failure, connection refused). +- **`is_timeout()`**: Returns true if the operation timed out. +- **`status()`**: Returns the `StatusCode` for non-successful HTTP responses. This is a `Option`, so you must handle the `Some` and `None` cases. +- **`is_client_error()`**: Returns true if the status code is a `4xx`. +- **`is_server_error()`**: Returns true if the status code is a `5xx`. +- **`is_decode()`**: Returns true if deserializing the response body (e.g., JSON) failed. + + + +```rust +use reqwest::{self, StatusCode}; +use tokio; + +#[tokio::main] +async fn main() { + let response = reqwest::get("https://httpbin.org/status/404").await; + + match response { + Ok(res) => { + // Check if the status code indicates a success (200-299) + if res.status().is_success() { + println!("Request was successful!"); + } else { + // If the status is not a success, you can handle it here. + println!("HTTP Error: {}", res.status()); + + // You can also match on the status code itself. + match res.status() { + StatusCode::NOT_FOUND => println!("The resource was not found (404)"), + StatusCode::UNAUTHORIZED => println!("Authentication failed (401)"), + _ => println!("An unexpected HTTP error occurred."), + } + } + }, + Err(e) => { + // Check for different kinds of network errors. + if e.is_connect() { + println!("A connection error occurred. Is the server running?"); + } else if e.is_timeout() { + println!("The request timed out. Please check your network."); + } else if e.is_decode() { + println!("Failed to decode the response body. Is the format correct?"); + } else { + println!("An unexpected request error occurred: {:?}", e); + } + }, + } +} +``` + +This granular approach gives you full control, allowing you to implement specific logic for each potential failure point. diff --git a/docs/desktop_applications/06_Project/Ideeas.md b/docs/desktop_applications/06_Project/Ideeas.md new file mode 100644 index 0000000..7d64d57 --- /dev/null +++ b/docs/desktop_applications/06_Project/Ideeas.md @@ -0,0 +1,39 @@ +### Project Ideas + +Here are a few project ideas, ranging from desktop utilities to educational tools, that leverage various programming concepts. Each project is designed to help you build practical skills and create a portfolio-ready application. + +--- + +#### 🌍 Country Guesser + +Create a game that challenges users to identify a country based on data retrieved from an API, such as `REST Countries`. This project will focus on **HTTP requests**, **JSON parsing**, and handling **asynchronous data**. You can present information like the country's flag, capital city, or population, prompting the user to make a guess. + +--- + +#### 🔐 Cryptography Application + +Build a simple desktop application for encrypting and decrypting messages. Implement classic ciphers like the **Caesar Cipher** or the **XOR Cipher**. This project will strengthen your understanding of **string manipulation**, **character encoding**, and basic **cryptographic principles**. + +--- + +#### 📝 Text Editor + +Develop a basic text editor with fundamental features. You can start with simple file **I/O** (opening, saving), and then add features like creating new files and displaying file content. This project provides a solid foundation in **file system interactions** and **GUI development**. + +--- + +#### 🛒 Inventory Management System + +Create a system to manage the inventory of a store or warehouse. This application would involve defining **data structures** for items, quantities, and prices, and then implementing functions to add, remove, and update products. This project is excellent for practicing **data persistence** and **structuring a complex application**. + +--- + +#### 📊 Sorting Algorithm Visualizer + +Build an interactive tool that visually demonstrates how different sorting algorithms work (e.g., Bubble Sort, Merge Sort). This project requires a strong grasp of **algorithms**, and it is a great way to practice **graphics programming** or **GUI updates** to animate the sorting process. + +--- + +#### 📑 Markdown Previewer + +Design a lightweight application that renders Markdown text in real time. The user can type Markdown on one side of the screen, and the formatted output will be displayed on the other. This project is a great way to learn about **text parsing**, **string rendering**, and **dynamic user interface updates**. diff --git a/docs/desktop_applications/06_Project/Requirements.md b/docs/desktop_applications/06_Project/Requirements.md new file mode 100644 index 0000000..e69de29