Rust Tutorial #26: Macros — Writing Code That Writes Code

In the previous tutorial, we learned file I/O. Now we learn macros — one of Rust’s most powerful features for code generation. Macros let you write code that writes code. They run at compile time and expand into regular Rust code. You have already used macros like println!(), vec![], and format!(). Now you will write your own. What Are Macros? A macro is a pattern that expands into code at compile time. When you write println!("hello"), the compiler replaces it with the actual printing code before compilation. ...

March 26, 2026 · 8 min

Rust Tutorial #25: File I/O and Path Handling

In the previous tutorial, we built CLI tools with Clap. Now we learn File I/O — reading files, writing files, working with paths, and walking directories. File I/O is something every program needs. Rust makes it safe and explicit. Every file operation returns a Result, so you always handle errors. No silent failures. No corrupted data. Path and PathBuf Before reading or writing files, you need to understand paths. Rust has two path types: ...

March 26, 2026 · 8 min

Rust Tutorial #24: CLI Tools with Clap

In the previous tutorial, we built a database-backed API with SQLx. Now we switch gears and learn Clap — the most popular library for building command-line tools in Rust. CLI tools are one of Rust’s sweet spots. Fast startup, small binaries, no runtime needed. Tools like ripgrep, bat, fd, and exa are all written in Rust. Clap handles the argument parsing so you can focus on the logic. By the end of this tutorial, you will build a complete CLI tool with subcommands, flags, and validated arguments. ...

March 26, 2026 · 8 min

Rust Tutorial #23: Database with SQLx

In the previous tutorial, we built a REST API with Axum using in-memory storage. Now we add a real database with SQLx. SQLx is an async database library for Rust. It supports PostgreSQL, MySQL, and SQLite. Unlike ORMs, SQLx lets you write plain SQL while still being type-safe. It can even check your queries at compile time against a real database. In this tutorial, we use SQLite because it needs no server setup. Everything you learn applies to PostgreSQL and MySQL too — just change the connection string and SQL dialect. ...

March 26, 2026 · 10 min

Jetpack Compose Tutorial #24: Navigation, Animations, and Polish

The task manager works. You can add tasks, complete them, delete them, search, and filter. But it doesn’t feel polished. Screens change instantly. Deleting a task is jarring. There’s no feedback when you complete something. This tutorial adds the polish that makes the difference between a homework project and a real app. What We Add Feature What It Does Navigation transitions Screens slide in/out smoothly Swipe to delete Swipe a task left to delete it Animated task completion Checkbox animates, strikethrough fades in Animated list changes Tasks slide in/out when added or removed Empty state animations Gentle fade-in when list is empty Dark mode Follows system theme Snackbar with undo “Task deleted” with undo option Navigation Transitions By default, screens appear instantly. Add slide transitions: ...

March 26, 2026 · 5 min

Rust Tutorial #22: Web API with Axum

In the previous tutorial, we learned to make HTTP requests. Now we build the other side — a REST API server with Axum. Axum is a web framework built on top of Tokio and Tower. It is fast, type-safe, and ergonomic. Unlike some other frameworks, Axum uses standard Rust types and traits. There are no macros on your handler functions. Everything is just regular async functions. Setting Up Add these dependencies to your Cargo.toml: ...

March 26, 2026 · 8 min

Rust HTTP Requests with Reqwest: GET, POST, JSON, and Error Handling

In the previous tutorial, we learned Serde for serialization. Now we use those skills to make HTTP requests with Reqwest — the most popular HTTP client in Rust. Most real applications talk to APIs. Whether you fetch data, send forms, or call microservices, you need an HTTP client. Reqwest makes this easy while staying fully async. Setting Up Add these dependencies to your Cargo.toml: [dependencies] tokio = { version = "1", features = ["full"] } reqwest = { version = "0.12", features = ["json"] } serde = { version = "1", features = ["derive"] } serde_json = "1" The "json" feature on reqwest enables built-in JSON parsing with Serde. ...

March 26, 2026 · 7 min

Rust Serde and JSON: Serialize and Deserialize Data in Rust

In the previous tutorial, we learned advanced error handling. Now we learn Serde — the serialization framework that powers most data handling in Rust. Serde converts Rust structs and enums to and from formats like JSON, TOML, YAML, and more. It is not just a JSON library. Serde separates “what to serialize” from “what format to use.” You write #[derive(Serialize, Deserialize)] once, and your type works with every supported format. Setting Up Add Serde and the formats you need to Cargo.toml: ...

March 26, 2026 · 7 min

Rust Tutorial #19: Advanced Error Handling (thiserror, anyhow)

In the previous tutorial, we learned testing in Rust. Now we take error handling to the next level with thiserror and anyhow — the two crates that every production Rust project uses. In Tutorial #8, we learned the basics: Result, Option, the ? operator, and custom error types. That works fine for small programs. But as your project grows, writing Display, From, and Error implementations by hand gets tedious. That is where thiserror and anyhow come in. ...

March 26, 2026 · 10 min

Jetpack Compose Tutorial #23: Building the UI Layer — Screens and ViewModels

In the previous tutorial, we built the data layer — Room entities, DAO, repository, and use cases. Now we connect it to the UI. This tutorial builds the three main screens of our task manager app: the task list, the add task form, and the settings screen. What We Build UI Layer: ├── TaskListScreen + TaskListViewModel (MVI) │ ├── Search bar │ ├── Filter chips (All, Active, Completed) │ ├── Task cards with priority badge + category │ └── Swipe to delete ├── AddTaskScreen + AddTaskViewModel │ ├── Title + description fields │ ├── Category chips │ ├── Priority chips │ └── Save button with validation └── SettingsScreen + SettingsViewModel ├── Dark mode toggle └── Delete completed tasks Task List Screen — State and Intents State // ui/screens/tasklist/TaskListState.kt // Everything the task list screen needs to display data class TaskListState( val tasks: List<Task> = emptyList(), val filter: TaskFilter = TaskFilter.ALL, val searchQuery: String = "", val isLoading: Boolean = true, val error: String? = null, val taskCount: Int = 0, val completedCount: Int = 0 ) Intents // ui/screens/tasklist/TaskListIntent.kt // Every action the user can take on the task list sealed interface TaskListIntent { data class Search(val query: String) : TaskListIntent data class ChangeFilter(val filter: TaskFilter) : TaskListIntent data class ToggleTask(val taskId: Long) : TaskListIntent data class DeleteTask(val taskId: Long) : TaskListIntent data object DeleteCompleted : TaskListIntent data object Retry : TaskListIntent } ViewModel // ui/screens/tasklist/TaskListViewModel.kt @HiltViewModel class TaskListViewModel @Inject constructor( private val getTasks: GetTasksUseCase, private val toggleTask: ToggleTaskUseCase, private val deleteTask: DeleteTaskUseCase, private val repository: TaskRepository ) : ViewModel() { private val _state = MutableStateFlow(TaskListState()) val state: StateFlow<TaskListState> = _state.asStateFlow() private var searchJob: Job? = null init { observeTasks() observeCounts() } fun onIntent(intent: TaskListIntent) { when (intent) { is TaskListIntent.Search -> { _state.update { it.copy(searchQuery = intent.query) } observeTasks() } is TaskListIntent.ChangeFilter -> { _state.update { it.copy(filter = intent.filter) } observeTasks() } is TaskListIntent.ToggleTask -> { viewModelScope.launch { toggleTask(intent.taskId) } } is TaskListIntent.DeleteTask -> { viewModelScope.launch { deleteTask(intent.taskId) } } is TaskListIntent.DeleteCompleted -> { viewModelScope.launch { repository.deleteCompletedTasks() } } is TaskListIntent.Retry -> { _state.update { it.copy(error = null) } observeTasks() } } } private fun observeTasks() { searchJob?.cancel() searchJob = viewModelScope.launch { _state.update { it.copy(isLoading = true) } try { getTasks( filter = _state.value.filter, searchQuery = _state.value.searchQuery ).collect { tasks -> _state.update { it.copy(tasks = tasks, isLoading = false, error = null) } } } catch (e: Exception) { _state.update { it.copy(error = e.message, isLoading = false) } } } } private fun observeCounts() { viewModelScope.launch { repository.getTaskCount().collect { count -> _state.update { it.copy(taskCount = count) } } } viewModelScope.launch { repository.getCompletedCount().collect { count -> _state.update { it.copy(completedCount = count) } } } } } The ViewModel pattern: ...

March 26, 2026 · 9 min