Programování
v Rustu
Kompletní přehled jazyka Rust — od základů po pokročilé koncepty. Český výklad, anglický kód.
Instalace
Rust se instaluje pomocí nástroje rustup, který spravuje verze kompilátoru a nástrojů.
| Příkaz | Popis |
|---|---|
| rustc | Kompilátor Rustu |
| rustup | Správce verzí Rustu |
| cargo | Správce balíčků a build tool |
Cargo
Cargo je build systém a správce závislostí. Každý Rust projekt je řízen souborem Cargo.toml.
Cargo.toml — ukázka
# Cargo.toml — konfigurační soubor projektu [package] name = "my_project" version = "0.1.0" edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1", features = ["full"] } rand = "0.8"
Datové typy
Rust je staticky typovaný jazyk — každá hodnota má přesně daný typ, který kompilátor zná v době překladu.
Skalární typy
| Typ | Popis | Rozsah / příklad |
|---|---|---|
| i8, i16, i32, i64, i128 | Celé číslo se znaménkem | i32: −2³¹ až 2³¹−1 |
| u8, u16, u32, u64, u128 | Celé číslo bez znaménka | u8: 0–255 |
| f32, f64 | Desetinné číslo | 3.14, 2.718 |
| bool | Logická hodnota | true / false |
| char | Unicode znak (4 bajty) | 'a', '€', '🦀' |
| isize, usize | Velikost závisí na architektuře | 64bit: 8 bajtů |
Složené typy
// Tuple — fixní počet prvků různých typů let tup: (i32, f64, bool) = (42, 3.14, true); let x = tup.0; // přístup přes index let (a, b, c) = tup; // destructuring // Array — fixní délka, stejný typ let arr: [i32; 5] = [1, 2, 3, 4, 5]; let zeros = [0; 10]; // 10 nul let first = arr[0]; // Slice — pohled do části pole let slice: &[i32] = &arr[1..3]; // prvky 1 a 2
String typy
// &str — reference na string slice (neměnné, statické) let hello: &str = "Ahoj světe"; // String — vlastněný, měnitelný, heap-allocated let mut s = String::from("Hello"); s.push_str(" World"); s.push('!'); // Konverze let owned: String = hello.to_string(); let borrowed: &str = &s;
Proměnné
V Rustu jsou proměnné ve výchozím stavu neměnné (immutable). Klíčové slovo mut umožňuje měnit hodnotu.
// Neměnná proměnná (immutable) — výchozí let x = 5; // x = 6; ← chyba kompilace! // Měnitelná proměnná (mutable) let mut y = 5; y = 10; // OK // Explicitní typová anotace let z: u32 = 42; // Shadowing — nová proměnná se stejným názvem let x = 5; let x = x * 2; // x je teď 10 let x = "text"; // může změnit i typ! // Konstanty — vždy neměnné, typ povinný, platné celý program const MAX_POINTS: u32 = 100_000; // Statické proměnné static GREETING: &str = "Hello";
Funkce
Funkce se definují klíčovým slovem fn. Návratový typ se uvádí za šipkou ->. Poslední výraz bez středníku je automaticky vrácen.
// Základní funkce fn greet(name: &str) { println!("Ahoj, {}!", name); } // Funkce s návratovou hodnotou fn add(a: i32, b: i32) -> i32 { a + b // bez středníku = return } // Explicitní return fn abs_value(n: i32) -> i32 { if n < 0 { return -n; } n } // Více návratových hodnot (tuple) fn min_max(arr: &[i32]) -> (i32, i32) { let min = arr.iter().min().copied().unwrap(); let max = arr.iter().max().copied().unwrap(); (min, max) }
Řízení toku
if / else
let n = 42; if n < 0 { println!("záporné"); } else if n == 0 { println!("nula"); } else { println!("kladné"); } // if jako výraz let label = if n % 2 == 0 { "sudé" } else { "liché" };
Smyčky
// loop — nekonečná smyčka let result = loop { let x = get_value(); if x > 10 { break x * 2; } }; // while let mut i = 0; while i < 10 { i += 1; } // for — iterace přes rozsah for i in 0..10 { // 0 až 9 print!("{} ", i); } for i in 0..=10 { // 0 až 10 včetně print!("{} ", i); } // for — iterace přes kolekci let v = vec![1, 2, 3]; for x in &v { println!("{}", x); }
match
let n = 3; match n { 1 => println!("jedna"), 2 | 3 => println!("dvě nebo tři"), 4..=10 => println!("čtyři až deset"), _ => println!("cokoliv jiného"), } // match jako výraz let text = match n { 1 => "one", 2 => "two", _ => "other", };
Ownership
Ownership je nejdůležitější a nejunikátnější koncept Rustu. Zajišťuje bezpečnost paměti bez garbage collectoru.
2. Může existovat pouze jeden vlastník najednou.
3. Když vlastník opustí scope, hodnota je uvolněna (drop).
// Move — přenesení vlastnictví let s1 = String::from("hello"); let s2 = s1; // s1 je přesunuto do s2 // println!("{}", s1); ← chyba! s1 již neplatí println!("{}", s2); // OK // Clone — hluboká kopie let s3 = s2.clone(); println!("{} {}", s2, s3); // obě platí // Copy typy (jednoduché skalární hodnoty) se kopírují automaticky let x = 5; let y = x; // x i y jsou platné (i32 implementuje Copy) // Ownership a funkce fn takes_ownership(s: String) { println!("{}", s); } // s je uvolněno (drop) let my_str = String::from("ahoj"); takes_ownership(my_str); // my_str zde již nelze použít
Borrowing & Reference
Reference umožňují předat hodnotu funkci bez přenosu vlastnictví — vypůjčení (borrow).
// Neměnná reference (&T) fn calculate_length(s: &String) -> usize { s.len() } let s = String::from("hello"); let len = calculate_length(&s); println!("{} má {} znaků", s, len); // s stále platí // Měnitelná reference (&mut T) fn append(s: &mut String) { s.push_str(" world"); } let mut s = String::from("hello"); append(&mut s);
• NEBO právě jedna měnitelná reference (&mut T).
• Nikdy obě zároveň! Toto kompilátor vynucuje.
Lifetimes
Lifetimes říkají kompilátoru, jak dlouho jsou reference platné. Zabraňují dangling references.
// Explicitní lifetime anotace fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // Lifetime ve structu struct Important<'a> { part: &'a str, } // 'static — platné po celou dobu běhu programu let s: &'static str = "Jsem vždy platný";
Struct
Struktury jsou základní způsob vytváření vlastních datových typů v Rustu.
// Definice structu struct User { username: String, email: String, age: u32, active: bool, } // Vytvoření instance let user = User { username: String::from("alice"), email: String::from("[email protected]"), age: 30, active: true, }; // Struct update syntax let user2 = User { email: String::from("[email protected]"), username: String::from("bob"), ..user // zbytek zkopíruj z user }; // Implementace metod impl User { // associated function (konstruktor) fn new(username: String, email: String) -> Self { User { username, email, age: 0, active: true } } // metoda (&self = neměnná reference na sebe) fn display(&self) { println!("{} ({})", self.username, self.email); } // měnitelná metoda fn deactivate(&mut self) { self.active = false; } } // Tuple struct struct Point(f64, f64); let p = Point(3.0, 4.0); println!("x={}, y={}", p.0, p.1);
Enum
Enumerace v Rustu jsou velmi výkonné — každá varianta může nést vlastní data.
// Jednoduchý enum enum Direction { North, South, East, West, } // Enum s daty enum Shape { Circle(f64), // poloměr Rectangle(f64, f64), // šířka, výška Triangle { base: f64, height: f64 }, } impl Shape { fn area(&self) -> f64 { match self { Shape::Circle(r) => 3.14159 * r * r, Shape::Rectangle(w, h) => w * h, Shape::Triangle { base, height } => base * height / 2.0, } } } // Option enum — absence hodnoty (nahrazuje null) let some: Option<i32> = Some(42); let none: Option<i32> = None; if let Some(val) = some { println!("Hodnota: {}", val); } // unwrap_or pro výchozí hodnotu let x = none.unwrap_or(0); // 0
Kolekce
Vec<T> — dynamické pole
let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); // Makro let v2 = vec![1, 2, 3, 4, 5]; // Přístup k prvkům let third = &v2[2]; // panic při out-of-bounds let third = v2.get(2); // vrací Option<&i32> // Iterace for x in &v2 { print!("{} ", x); } for x in &mut v { *x *= 2; } // měnitelná iterace
HashMap<K, V>
use std::collections::HashMap; let mut map = HashMap::new(); map.insert("name", "Alice"); map.insert("city", "Praha"); // Čtení let name = map.get("name"); // Option<&&str> // Vložit jen pokud klíč neexistuje map.entry("age").or_insert("25"); // Iterace for (key, value) in &map { println!("{}: {}", key, value); }
Traits
Traits definují sdílené chování — podobné rozhraním (interface) v jiných jazycích.
// Definice traitu trait Animal { fn name(&self) -> &str; fn sound(&self) -> &str; // Defaultní implementace fn describe(&self) { println!("{} říká: {}", self.name(), self.sound()); } } struct Dog; struct Cat; impl Animal for Dog { fn name(&self) -> &str { "Pes" } fn sound(&self) -> &str { "Haf" } } impl Animal for Cat { fn name(&self) -> &str { "Kočka" } fn sound(&self) -> &str { "Mňau" } } // Trait jako parametr funkce fn make_sound(animal: &impl Animal) { animal.describe(); } // Trait object (dynamická dispatch) let animals: Vec<Box<dyn Animal>> = vec![ Box::new(Dog), Box::new(Cat), ];
Generics
Generiky umožňují psát kód, který funguje pro různé typy bez duplikace.
// Generická funkce fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest } // Generický struct struct Pair<T> { first: T, second: T, } impl<T: std::fmt::Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.first >= self.second { println!("Větší: {}", self.first); } } }
Error handling
Rust nemá výjimky. Chyby se zpracovávají přes typ Result<T, E>.
use std::fs::File; use std::io::{self, Read}; // Základní match na Result let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(e) => { panic!("Nelze otevřít soubor: {}", e); } }; // Operátor ? — propagace chyby fn read_username() -> Result<String, io::Error> { let mut s = String::new(); File::open("user.txt")?.read_to_string(&mut s)?; Ok(s) } // Vlastní typ chyby use std::fmt; enum MyError { NotFound, InvalidInput(String), } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { MyError::NotFound => write!(f, "Nenalezeno"), MyError::InvalidInput(msg) => write!(f, "Chybný vstup: {}", msg), } } }
Closures
Closure je anonymní funkce, která může zachytit proměnné z okolního scope.
// Základní closure let add = |a, b| a + b; println!("{}", add(2, 3)); // Zachytávání prostředí let multiplier = 3; let triple = |x| x * multiplier; // borrowuje multiplier // move closure — přebírá ownership let s = String::from("ahoj"); let greet = move || println!("{}", s); // Closure jako parametr fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) } let result = apply(|x| x * x, 5); // 25
Iterátory
Iterátory jsou líné (lazy) — výpočet probíhá až při konzumaci. Jsou velmi výkonné a idiomatické v Rustu.
let v = vec![1, 2, 3, 4, 5]; // map, filter, collect let result: Vec<i32> = v.iter() .filter(|&&x| x % 2 == 0) // sudá čísla .map(|&x| x * 10) // vynásob 10 .collect(); // [20, 40] // sum, product let total: i32 = v.iter().sum(); let product: i32 = v.iter().product(); // fold — obecná redukce let sum = v.iter().fold(0, |acc, &x| acc + x); // enumerate, zip for (i, val) in v.iter().enumerate() { println!("[{}] = {}", i, val); } // any, all, count, find let has_even = v.iter().any(|&x| x % 2 == 0); let all_pos = v.iter().all(|&x| x > 0);
Moduly
Moduly organizují kód do logických celků a řídí viditelnost (public/private).
// src/main.rs mod math { pub fn add(a: i32, b: i32) -> i32 { a + b } pub mod advanced { pub fn sqrt(x: f64) -> f64 { x.sqrt() } } } // Použití use math::advanced::sqrt; let r = math::add(2, 3); let s = sqrt(16.0); // Soubor jako modul: src/utils.rs mod utils; // Rust hledá src/utils.rs nebo src/utils/mod.rs use utils::helper;
Concurrency
Rust garantuje bezpečné souběžné programování díky systému ownership. Kompilátor zabraňuje data races.
use std::thread; use std::sync::{Arc, Mutex}; // Spuštění vlákna let handle = thread::spawn(|| { println!("Jsem v novém vlákně!"); }); handle.join().unwrap(); // čekej na dokončení // Sdílená data — Arc + Mutex let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let c = Arc::clone(&counter); handles.push(thread::spawn(move || { let mut num = c.lock().unwrap(); *num += 1; }))); } for h in handles { h.join().unwrap(); } println!("Výsledek: {}", *counter.lock().unwrap()); // 10 // Kanály (channels) — komunikace mezi vlákny use std::sync::mpsc; let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send("Zpráva z vlákna").unwrap(); }); let msg = rx.recv().unwrap(); println!("{}", msg);
tokio = { version = "1", features = ["full"] }
use tokio; #[tokio::main] async fn main() { let result = fetch_data().await; println!("{:?}", result); } async fn fetch_data() -> String { // async operace</