Hlavní navigace

Programovací jazyk Rust: jednoduchý server a klient komunikující přes TCP

Pavel Tišnovský

Jednou ze zajímavých domén použití jazyka Rust mohou být serverové aplikace. Proto si v dnešní části seriálu ukážeme základní použití knihovních funkcí pro práci s TCP sockety, a to jak při implementaci serveru, tak i klienta.

Obsah

1. Programovací jazyk Rust: jednoduchý server a klient komunikující přes TCP

2. První verze serveru se sekvenčním zpracováním požadavků

3. Otestování serveru Telnetem

4. Vylepšení zdrojového kódu serveru použitím pattern matchingu

5. Obsluha odpovědi ve zvláštním vláknu

6. Zpracování příchozích dat (jednoduchý echo server)

7. Zobrazení přijatých i odeslaných dat

8. Zdrojový kód pátého demonstračního příkladu

9. Vytvoření TCP klienta

10. Funkce pro poslání zprávy a přijmutí zprávy

11. Zdrojový kód šestého demonstračního příkladu

12. Otestování komunikace mezi serverem a klientem

13. Repositář s demonstračními příklady

14. Odkazy na Internetu

1. Programovací jazyk Rust: jednoduchý server a klient komunikující přes TCP

Již v předchozím článku jsme se krátce zmínili o jednoduchém serveru, který naslouchá na zvoleném portu a na každý požadavek odpoví řetězcem „Server response…\r\n“ a následně ukončí spojení. Tento server je vytvořen podobným postupem, jaký by byl použit například v céčku. Nejprve vytvoříme listener, který naslouchá na určitém portu. V tomto příkladu je zvolen port 1234, jehož hodnota je větší než 1024, takže ho může otevřít i aplikace, která nemá rootovská práva (nicméně si dejte pozor na to, ať neotvíráte již použitý port, typicky 8080 či 8000 když běží Jetty/Tomcat atd.). Povšimněte si, jak se zadává adresa a port, existuje totiž několik různých způsobů a každý je vhodné použít v jiné situaci:

// adresa i port jsou reprezentovány řetězcem
let listener = TcpListener::bind("127.0.0.1:1234").unwrap();
 
// můžeme použít i localhost a samozřejmě i odlišný port
let listener = TcpListener::bind("localhost:9999").unwrap();
 
// závorky jsou zde nutné, protože předáváme n-tici (tuple)
let listener = TcpListener::bind( ("localhost",9999) ).unwrap();
 
// vytvoření adresy jiným způsobem
let ip = Ipv4Addr::new(127, 0, 0, 1);
let port = 1234;
let listener = TcpListener::bind(SocketAddrV4::new(ip, port)).unwrap();
 
// IPv6 adresa by se vytvořila následovně
let ip6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff);

Poznámka: návratová hodnota TcpListener::bind() je Result<TcpListener> a nikoli přímo TcpListener, tj. mělo by se kontrolovat, zda se nevrátí chybová hodnota. Pokud to nastane, dojde při pokusu o zavolání unwrap() k pádu programu:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { repr: Custom(Custom { kind: InvalidInput, error: StringError("invalid port value") }) }', ../src/libcore/result.rs:788

Ve druhém kroku se získá nekonečný iterátor, který při každém požadavku o spojení od klienta vrátí strukturu typu Option<Result<TcpStream>>, která již reprezentuje obousměrný komunikační kanál. Vzhledem k tomu, že je iterátor nekonečný, bude nutné server ukončit jinak, například přes klávesovou zkratku Ctrl+C z terminálu, posláním signálu STOP příkazem kill atd.:

let tcp_stream_iter = listener.incoming();

Nejjednodušší možný způsob realizace na požadavek o spojení může vypadat takto. Nejprve otestujeme, zda iterátor vrátil korektní TCP stream (opět se totiž pracuje s typem Result) a následně se zavolá uživatelsky definovaný handler:

for tcp_stream in tcp_stream_iter {
    if tcp_stream.is_ok() {
        handler(tcp_stream.unwrap());
    } else {
        println!("connection failed");
    }
}

Handler je prozatím velmi primitivní, protože pouze klientovi pošle textovou odpověď a následně spojení ukončí:

fn handler(mut stream:TcpStream) {
    println!("Accepted connection");
    stream.write(b"Server response...\r\n").unwrap();
}

Dvě důležité poznámky k handleru:

  1. Povšimněte si, že se nikde nevolá žádná funkce typuclose() pro otevřený TCP stream. To není nutné (ostatně tato funkce ani neexistuje), protože se stream zavře automaticky sám ve chvíli, kdy skončí jeho životnost (nebo viditelnost). V našem případě je tímto okamžikem opuštění funkce handler().
  2. Dále je nutné si uvědomit, že řetězce v Rustu jsou vždy reprezentovány v UTF-8, což jsme si již řekli v úvodním článku. Takže pokud si skutečně chceme být jistí, že posleme klientovi ASCII znaky (co znak to jeden bajt) zadané v řetězci, je nutné použít prefix b". Ten zajistí, že omylem nezapíšeme odlišné znaky, které nelze reprezentovat jediným bajtem (kontrolu provádí překladač).

Pokus o použití znaků, které nelze reprezentovat v ASCII, vede k tomuto chybovému hlášení:

error: byte constant must be ASCII. Use a \xHH escape for a non-ASCII byte
 --> test.rs:7:31
  |
7 |     stream.write(b"Server respřčšěonse...\r\n").unwrap();
  |                               ^

2. První verze serveru se sekvenčním zpracováním požadavků

Úplný zdrojový kód dnešního prvního demonstračního příkladu vypadá následovně a naleznete ho i v GIT repositáři na adrese https://github.com/tisnik/pre­sentations/blob/master/rus­t/292_tcp_listener1.rs:

use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
 
fn handler(mut stream:TcpStream) {
    println!("Accepted connection");
    stream.write(b"Server response...\r\n").unwrap();
}
 
fn main() {
    let listener = TcpListener::bind("127.0.0.1:1234").unwrap();
 
    let tcp_stream_iter = listener.incoming();
 
    for tcp_stream in tcp_stream_iter {
        if tcp_stream.is_ok() {
            handler(tcp_stream.unwrap());
        } else {
            println!("connection failed");
        }
    }
}

3. Otestování serveru Telnetem

Otestování serveru může být velmi jednoduché, protože postačuje použít program Telnet či podobný nástroj. Nejdříve si server přeložte a spusťte v jednom terminálu:

$ rustc 292_tcp_listener_1.rs
 
$ ./292_tcp_listener_1

Následně se z jiného terminálu pokusíme k serveru připojit. Měli bychom přijmout zprávu „Server response…“ a spojení by se mělo ihned poté automaticky ukončit, protože server na své straně uzavře TCP stream:

$ telnet localhost 1234
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Server response...
Connection closed by foreign host.

Obrázek 1: Komunikace s první verzí serveru přes Telnet.

4. Vylepšení zdrojového kódu serveru použitím pattern matchingu

První verze serveru popsaná v předchozích třech kapitolách nebyla naprogramována stylem, který je pro zdrojové kódy Rustu doporučován. Zejména kontrola, zda hodnota typu Result<typ> obsahuje skutečný objekt typu „typ“ nebo jen chybu, by se neměla psát otrocky s použitím if-then:

if tcp_stream.is_ok() {
    handler(tcp_stream.unwrap());
} else {
    println!("connection failed");
}

Mnohem lepší je použití pattern matchingu, který nám umožní přepsat tělo smyčky čitelnějším způsobem. Navíc můžeme snadno přistoupit k objektu představujícímu chybu:

match tcp_stream {
    Ok(tcp_stream) => {
        handler(tcp_stream);
    }
    Err(e) => {
        println!("connection failed: {}", e);
    }
}

Úplný zdrojový kód druhé verze našeho jednoduchého serveru, který naleznete na adrese https://github.com/tisnik/pre­sentations/blob/master/rus­t/293_tcp_listener2.rs, bude vypadat následovně:

use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
 
fn handler(mut stream:TcpStream) {
    println!("Accepted connection");
    stream.write(b"Server response...\r\n").unwrap();
}
 
fn main() {
    let listener = TcpListener::bind("127.0.0.1:1234").unwrap();
 
    for tcp_stream in listener.incoming() {
        match tcp_stream {
            Ok(tcp_stream) => {
                handler(tcp_stream);
            }
            Err(e) => {
                println!("connection failed: {}", e);
            }
        }
    }
}

5. Obsluha odpovědi ve zvláštním vláknu

Obě předchozí verze serveru nedokázaly současně reagovat na požadavky většího množství připojení, protože se jednotlivé požadavky zpracovávaly postupně (sekvenčně):

for tcp_stream in listener.incoming() {
    match tcp_stream {
        Ok(tcp_stream) => {
            handler(tcp_stream);
        }
        Err(e) => {
            println!("connection failed: {}", e);
        }
    }
}

To sice (alespoň prozatím) nemuselo vadit, protože server odešle jen několik bajtů a ihned poté spojení ukončí, což bude relativně krátký okamžik. Ale v reálné aplikaci by toto chování již nemuselo být akceptovatelné. Představme si například situaci, kdy handler pracuje s databází, serializuje nějaké výsledky (do JSONu), zpracovává vstupní data atd. Jedno z nejjednodušších řešení v Rustu představuje použití vláken, kdy se každý handler spustí v samostatném vláknu. Problematiku vytváření vláken již známe, takže přepis smyčky, v níž se pro každou žádost o připojení vytvoří handler v samostatném vláknu, bude jednoduchý:

for tcp_stream in listener.incoming() {
    match tcp_stream {
        Ok(tcp_stream) => {
            thread::spawn(|| {
                handler(tcp_stream);
            });
        }
        Err(e) => {
            println!("connection failed: {}", e);
        }
    }
}

I v tomto případě platí, že viditelnost/životnost TCP streamu končí ve chvíli, kdy se opustí funkce handler(). Přesně v tomto okamžiku se stream a tím pádem i připojení uzavře.

Úplný zdrojový kód třetí verze našeho serveru bude vypadat následovně (naleznete ho na adrese https://github.com/tisnik/pre­sentations/blob/master/rus­t/294_tcp_listener3.rs):

use std::thread;
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
 
fn handler(mut stream:TcpStream) {
    println!("Accepted connection");
    stream.write(b"Server response...\r\n").unwrap();
}
 
fn main() {
    let listener = TcpListener::bind("127.0.0.1:1234").unwrap();
 
    for tcp_stream in listener.incoming() {
        match tcp_stream {
            Ok(tcp_stream) => {
                thread::spawn(|| {
                    handler(tcp_stream);
                });
            }
            Err(e) => {
                println!("connection failed: {}", e);
            }
        }
    }
}

6. Zpracování příchozích dat (jednoduchý echo server)

Zkusme si vytvořit složitější server, konkrétně server, který po připojení klienta odešle řetězec „Entering echo mode…“ a následně pouze vrací zpět všechna přečtená data. Jedná se vlastně o zjednodušenou podobu služby echo. Jediné, co od této služby požadujeme, je skutečně vracet data poslaná klientem zpět. Existuje přitom několik způsobů implementace. Jeden z nich je založený na práci s celými řádky (viz například https://rosettacode.org/wi­ki/Echo_server#Rust), my si však ukážeme poněkud odlišný způsob, kde se pracuje s bufferem o pevné délce a tím pádem lze snadno predikovat, jaké budou paměťové nároky. Do tohoto bufferu se načítají data od klienta a postupně se posílají zpět. Buffer vytvoříme snadno – bude se jednat o pole celých čísel:

let mut buffer = [0; 16];

Alternativně lze explicitně určit typ prvků, to ovšem není nutné, protože typ prvků si dokáže překladač odvodit sám:

let mut buffer: [u8;100] = [0u8; 100];

Poznámka: musí se skutečně jednat o pole bajtů, jinak překladač nahlásí chybu při použití funkce TcpStream::read().

Tento buffer je použit v programové smyčce, kde se postupně snažíme načíst data poslaná od klienta, informovat o nich na standardním výstupu a poslat je zpět. Taktéž si ve smyčce zkontrolujeme, zda mezitím nedošlo k odpojení klienta nebo zda ještě zbývají nějaká data od klienta. V této smyčce důsledně využíváme pattern matching (dokonce dvakrát):

loop {
    match stream.read(&mut buffer) {
        Ok(size) => {
            println!("read: {} bytes", size);
            if size == 0 {
                println!("no data to read?");
                break;
            }
            match stream.write(&buffer[0..size]) {
                Ok(_)  => {}
                Err(_) => {
                    println!("write error");
                    break;
                }
            }
        }
        Err(_) => {
            println!("read error");
            break;
        }
    }
}

Úplný zdrojový kód echo serveru může vypadat následovně:

use std::thread;
use std::io::Read;
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
 
fn handler(mut stream:TcpStream) {
    println!("Accepted connection");
    stream.write(b"Entering echo mode...\r\n").unwrap();
 
    let mut buffer = [0; 16];
 
    loop {
        match stream.read(&mut buffer) {
            Ok(size) => {
                println!("read: {} bytes", size);
                if size == 0 {
                    println!("no data to read?");
                    break;
                }
                match stream.write(&buffer[0..size]) {
                    Ok(_)  => {}
                    Err(_) => {
                        println!("write error");
                        break;
                    }
                }
            }
            Err(_) => {
                println!("read error");
                break;
            }
        }
    }
    println!("disconnected")
}
 
fn main() {
    let listener = TcpListener::bind("127.0.0.1:1234").unwrap();
 
    for tcp_stream in listener.incoming() {
        match tcp_stream {
            Ok(tcp_stream) => {
                thread::spawn(|| {
                    handler(tcp_stream);
                });
            }
            Err(e) => {
                println!("connection failed: {}", e);
            }
        }
    }
}

7. Zobrazení přijatých i odeslaných dat

Pokud budeme chtít zobrazit data, která server přijal od klienta, musíme převést přijatou sekvenci bajtů na řetězec a naopak. K převodu řetězce na pole bajtů lze použít následující sekvenci příkazů (schválně ukazuji příklad s řetězcem s Unicode znaky):

fn main() {
    let s = String::from_str("fň bž λ ζ ж").unwrap();
    let bytes = s.into_bytes();
    println!("{:?}", bytes);
}

Výsledkem bude tento výstup (obsah pole bytes):

[102, 197, 136, 32, 98, 197, 190, 32, 206, 187, 32, 206, 182, 32, 208, 182]

Pro opačný převod, tedy pro převod pole bajtů na řetězec, se používá taktéž dvojice funkcí, první pro převod pole libovolného typu na vektor s prvky téhož typu:

fn from(s: &'a [T]) -> Vec<T>

Dále se vektor obsahující bajty (prvky typu u8) může převést na řetězec funkcí from_utf8:

fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>

Poznámka: tato funkce skutečně může vrátit chybu, a to v tom případě, kdy sekvenci bajtů nelze interpretovat jako řetězec UTF-8 znaků.

Můžeme si tedy připravit pomocnou funkci nazvanou utf8_to_string(), která za nás převod provede:

fn utf8_to_string(bytes: &[u8]) -> String {
    let vector: Vec<u8> = Vec::from(bytes);
    String::from_utf8(vector).unwrap()
}

A ihned si ji můžeme otestovat:

fn utf8_to_string(bytes: &[u8]) -> String {
    let vector: Vec<u8> = Vec::from(bytes);
    String::from_utf8(vector).unwrap()
}
 
fn main() {
    let chars : [u8; 8] = [64, 65, 66, 32, 97, 98, 32, 33];
    println!("{}", utf8_to_string(&chars));
}

Výsledkem spuštění tohoto testu by měla být tato zpráva:

@AB ab !

V případě bajtů reprezentujících UTF-8 znaky:

fn main() {
    let chars = [102, 197, 136, 32, 98, 197, 190, 32, 206, 187, 32, 206, 182, 32, 208, 182];
    println!("{}", utf8_to_string(&chars));
}

Dostaneme tento – korektní – výstup:

fň bž λ ζ ж

8. Zdrojový kód pátého demonstračního příkladu

Předchozí server můžeme snadno upravit tak, aby vypisoval všechna přijatá data a zobrazil je jak ve formě řetězce, tak i jako sekvenci bajtů. Přidané řádky jsou zvýrazněny tučně:

use std::thread;
use std::io::Read;
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
 
fn utf8_to_string(bytes: &[u8]) -> String {
    let vector: Vec<u8> = Vec::from(bytes);
    String::from_utf8(vector).unwrap()
}
 
fn handler(mut stream:TcpStream) {
    println!("Accepted connection");
    stream.write(b"Entering echo mode...\r\n").unwrap();
 
    let mut buffer = [0; 16];
 
    loop {
        match stream.read(&mut buffer) {
            Ok(size) => {
                println!("read: {} bytes", size);
                if size == 0 {
                    println!("no data to read?");
                    break;
                } else {
                    let response = utf8_to_string(&buffer[0..size]);
                    println!("read: {:?}: '{}'", &buffer[0..size], response);
                    match stream.write(&buffer[0..size]) {
                        Ok(_)  => {}
                        Err(error) => {
                            println!("write error {:?}", error);
                            break;
                        }
                    }
                }
            }
            Err(error) => {
                println!("read error {:?}", error);
                break;
            }
        }
    }
    println!("disconnected")
}
 
fn main() {
    let listener = TcpListener::bind("127.0.0.1:1234").unwrap();
 
    for tcp_stream in listener.incoming() {
        match tcp_stream {
            Ok(tcp_stream) => {
                thread::spawn(|| {
                    handler(tcp_stream);
                });
            }
            Err(error) => {
                println!("connection failed: {}", error);
            }
        }
    }
}

9. Vytvoření TCP klienta

V posledním demonstračním příkladu, který si dnes ukážeme, bude implementován jednoduchý TCP klient. Ten vlastně bude pracovat velmi podobně jako server, ovšem namísto struktury nazvané TcpListener bude pracovat přímo se strukturou TcpStream, protože klient se (velmi zjednodušeně řečeno) musí aktivně připojovat k serveru, zatímco server jen pasivně vyčkává na žádosti o připojení.

10. Funkce pro poslání zprávy a přijmutí zprávy

V klientu využijeme dvě funkce, první pro příjem zprávy a druhou pro odeslání zprávy. Funkce pro příjem zprávy může vypadat takto:

fn receive_message(mut stream:&TcpStream) {
    let mut buffer = [0; 40];
    match stream.read(&mut buffer) {
        Ok(size) => {
            let response = utf8_to_string(&buffer[0..size]);
            println!("read: {} bytes: {:?}\n'{}'", size, &buffer[0..size], response);
            if size == 0 {
                println!("no data to read?");
            }
        }
        Err(_) => {
            println!("read error");
        }
     }
}

Pro jednoduchost počítáme s tím, že zpráva bude mít maximální délku 40 bajtů.

Funkce pro odeslání zprávy je velmi jednoduchá, protože pouze získá interní reprezentaci řetězce a odešle ji na server:

fn send_message(mut stream:&TcpStream, message: &str) {
    let message_as_bytes = message.as_bytes();
 
    match stream.write_all(&message_as_bytes) {
        Ok(_) => {
            println!("write ok");
        }
        Err(error) => {
            println!("write error: {}", error);
        }
    }
 
}

Samotné připojení klienta k serveru se již neprovádí přes listenera, ale funkcí connect():

let tcp_stream = TcpStream::connect("127.0.0.1:1234").unwrap();
 
receive_message(&tcp_stream);
send_message(&tcp_stream, "Hello\r\n");
receive_message(&tcp_stream);
 
println!("closing stream");

Poznámka: TCP stream je opět automaticky uzavřen, zde konkrétně ve chvíli, kdy přestane být viditelná proměnná tcp_stream.

11. Zdrojový kód šestého demonstračního příkladu

Úplný zdrojový kód jednoduchého TCP klienta naleznete na adrese https://github.com/tisnik/pre­sentations/blob/master/rus­t/297_tcp_client1.rs:

use std::io::Read;
use std::io::Write;
use std::net::TcpStream;
 
fn utf8_to_string(bytes: &[u8]) -> String {
    let vector: Vec<u8> = Vec::from(bytes);
    String::from_utf8(vector).unwrap()
}
 
fn send_message(mut stream:&TcpStream, message: &str) {
    let message_as_bytes = message.as_bytes();
 
    match stream.write_all(&message_as_bytes) {
        Ok(_) => {
            println!("write ok");
        }
        Err(error) => {
            println!("write error: {}", error);
        }
    }
 
}
 
fn receive_message(mut stream:&TcpStream) {
    let mut buffer = [0; 40];
    match stream.read(&mut buffer) {
        Ok(size) => {
            let response = utf8_to_string(&buffer[0..size]);
            println!("read: {} bytes: {:?}\n'{}'", size, &buffer[0..size], response);
            if size == 0 {
                println!("no data to read?");
            }
        }
        Err(_) => {
            println!("read error");
        }
     }
}
 
fn main() {
    let tcp_stream = TcpStream::connect("127.0.0.1:1234").unwrap();
    println!("{:?}", tcp_stream);
 
    receive_message(&tcp_stream);
    send_message(&tcp_stream, "Hello\r\n");
    receive_message(&tcp_stream);
 
    println!("closing stream");
}

12. Otestování komunikace mezi serverem a klientem

Pro otestování komunikace mezi serverem a klientem nyní již nemusíme použít Telnet, ale stačí nám si přeložit poslední dva příklady a každý spustit ve vlastním terminálu. Ostatně se podívejme, jak to vypadá v praxi (povšimněte si, že zprávy obsahují i znaky pro odřádkování):

Obrázek 2: Komunikace s poslední verzí serveru a klientu.

13. Repositář s demonstračními příklady

Všechny dnes popisované demonstrační příklady byly, podobně jako ve všech předchozích částech tohoto seriálu, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/pre­sentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář:

14. Odkazy na Internetu

  1. std::net::IpAddr
    https://doc.rust-lang.org/std/net/enum.IpAddr.html
  2. std::net::Ipv4Addr
    https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html
  3. std::net::Ipv6Addr
    https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html
  4. TcpListener
    https://doc.rust-lang.org/std/net/struct.TcpLis­tener.html
  5. TcpStream
    https://doc.rust-lang.org/std/net/struct.TcpStream.html
  6. Binary heap (Wikipedia)
    https://en.wikipedia.org/wi­ki/Binary_heap
  7. Binární halda (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Bin%C3%A1rn%C3%AD_halda
  8. Halda (datová struktura)
    https://cs.wikipedia.org/wi­ki/Halda_%28datov%C3%A1_struk­tura%29
  9. Struct std::collections::HashSet
    https://doc.rust-lang.org/std/collections/struc­t.HashSet.html
  10. Struct std::collections::BTreeSet
    https://doc.rust-lang.org/std/collections/struc­t.BTreeSet.html
  11. Struct std::collections::BinaryHeap
    https://doc.rust-lang.org/std/collections/struc­t.BinaryHeap.html
  12. Set (abstract data type)
    https://en.wikipedia.org/wi­ki/Set_%28abstract_data_ty­pe%29#Language_support
  13. Associative array
    https://en.wikipedia.org/wi­ki/Associative_array
  14. Hash Table
    https://en.wikipedia.org/wi­ki/Hash_table
  15. B-tree
    https://en.wikipedia.org/wiki/B-tree
  16. Pedro Celis: Robin Hood Hashing (naskenované PDF!)
    https://cs.uwaterloo.ca/re­search/tr/1986/CS-86–14.pdf
  17. Robin Hood hashing
    http://codecapsule.com/2013/11/11/ro­bin-hood-hashing/
  18. Robin Hood hashing: backward shift deletion
    http://codecapsule.com/2013/11/17/ro­bin-hood-hashing-backward-shift-deletion/
  19. Module std::collections
    https://doc.rust-lang.org/std/collections/
  20. Module std::vec
    https://doc.rust-lang.org/nightly/std/vec/index.html
  21. Struct std::collections::VecDeque
    https://doc.rust-lang.org/std/collections/struc­t.VecDeque.html
  22. Struct std::collections::LinkedList
    https://doc.rust-lang.org/std/collections/struc­t.LinkedList.html
  23. Module std::fmt
    https://doc.rust-lang.org/std/fmt/
  24. Macro std::println
    https://doc.rust-lang.org/std/macro.println.html
  25. Enum std::result::Result
    https://doc.rust-lang.org/std/result/enum.Result.html
  26. Module std::result
    https://doc.rust-lang.org/std/result/
  27. Result
    http://rustbyexample.com/std/re­sult.html
  28. Rust stdlib: Option
    https://doc.rust-lang.org/std/option/enum.Option.html
  29. Module std::option
    https://doc.rust-lang.org/std/option/index.html
  30. Rust by example: option
    http://rustbyexample.com/std/op­tion.html
  31. Rust by example: if-let
    http://rustbyexample.com/flow_con­trol/if_let.html
  32. Rust by example: while let
    http://rustbyexample.com/flow_con­trol/while_let.html
  33. Rust by example: Option<i32>
    http://rustbyexample.com/std/op­tion.html
  34. An Overview of Macros in Rust
    http://words.steveklabnik.com/an-overview-of-macros-in-rust
  35. A Practical Intro to Macros in Rust 1.0
    https://danielkeep.github.io/practical-intro-to-macros.html
  36. The Rust Programming Language: macros
    https://doc.rust-lang.org/beta/book/macros.html
  37. Rust by example: 15 macro_rules!
    http://rustbyexample.com/macros.html
  38. Primitive Type isize
    https://doc.rust-lang.org/nightly/std/primi­tive.isize.html
  39. Primitive Type usize
    https://doc.rust-lang.org/nightly/std/primi­tive.usize.html
  40. Primitive Type array
    https://doc.rust-lang.org/nightly/std/primi­tive.array.html
  41. Module std::slice
    https://doc.rust-lang.org/nightly/std/slice/
  42. Rust by Example: 2.3 Arrays and Slices
    http://rustbyexample.com/pri­mitives/array.html
  43. What is the difference between Slice and Array (stackoverflow)
    http://stackoverflow.com/qu­estions/30794235/what-is-the-difference-between-slice-and-array
  44. Learning Rust With Entirely Too Many Linked Lists
    http://cglab.ca/~abeinges/blah/too-many-lists/book/
  45. Testcase: linked list
    http://rustbyexample.com/cus­tom_types/enum/testcase_lin­ked_list.html
  46. Operators and Overloading
    https://doc.rust-lang.org/book/operators-and-overloading.html
  47. Module std::ops
    https://doc.rust-lang.org/std/ops/index.html
  48. Module std::cmp
    https://doc.rust-lang.org/std/cmp/index.html
  49. Trait std::ops::Add
    https://doc.rust-lang.org/stable/std/ops/trait.Add.html
  50. Trait std::ops::AddAssign
    https://doc.rust-lang.org/std/ops/trait.AddAssign.html
  51. Trait std::ops::Drop
    https://doc.rust-lang.org/std/ops/trait.Drop.html
  52. Trait std::cmp::Eq
    https://doc.rust-lang.org/std/cmp/trait.Eq.html
  53. Struct std::boxed::Box
    https://doc.rust-lang.org/std/boxed/struct.Box.html
  54. Explore the ownership system in Rust
    https://nercury.github.io/rus­t/guide/2015/01/19/ownership­.html
  55. Rust's ownership and move semantic
    http://www.slideshare.net/sa­neyuki/rusts-ownership-and-move-semantics
  56. Trait std::marker::Copy
    https://doc.rust-lang.org/stable/std/marker/tra­it.Copy.html
  57. Trait std::clone::Clone
    https://doc.rust-lang.org/stable/std/clone/tra­it.Clone.html
  58. The Stack and the Heap
    https://doc.rust-lang.org/book/the-stack-and-the-heap.html
  59. Rust Compare: Pointers & References
    http://www.rust-compare.com/site/pointers.html
  60. Rust Compare: Parameters
    http://www.rust-compare.com/site/params.html
  61. Why does this compile? Automatic dereferencing?
    https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183
  62. Understanding Pointers, Ownership, and Lifetimes in Rust
    http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html
  63. Rust lang series episode #25 — pointers (#rust-series)
    https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series
  64. Rust – home page
    https://www.rust-lang.org/en-US/
  65. Rust – Frequently Asked Questions
    https://www.rust-lang.org/en-US/faq.html
  66. Destructuring and Pattern Matching
    https://pzol.github.io/get­ting_rusty/posts/20140417_des­tructuring_in_rust/
  67. The Rust Programming Language
    https://doc.rust-lang.org/book/
  68. Rust (programming language)
    https://en.wikipedia.org/wi­ki/Rust_%28programming_lan­guage%29
  69. Go – home page
    https://golang.org/
  70. Stack Overflow – Most Loved, Dreaded, and Wanted language
    https://stackoverflow.com/re­search/developer-survey-2016#technology-most-loved-dreaded-and-wanted
  71. Rust vs Go (dva roky staré hodnocení, od té doby došlo k posunům v obou jazycích)
    http://jaredforsyth.com/2014/03/22/rust-vs-go/
  72. Rust vs Go: My experience
    https://www.reddit.com/r/go­lang/comments/21m6jq/rust_vs_go_my_ex­perience/
  73. Friends of Rust (Organizations running Rust in production)
    https://www.rust-lang.org/en-US/friends.html
  74. Rust programs versus C++ g++
    https://benchmarksgame.ali­oth.debian.org/u64q/compa­re.php?lang=rust&lang2=gpp
  75. Další benchmarky (nejedná se o reálné příklady „ze života“)
    https://github.com/kostya/benchmarks
  76. Go na Redditu
    https://www.reddit.com/r/golang/
  77. Rust vs. Go
    http://vschart.com/compare/rust/vs/go-language
  78. Abstraction without overhead: traits in Rust
    https://blog.rust-lang.org/2015/05/11/traits.html
  79. Method Syntax
    https://doc.rust-lang.org/book/method-syntax.html
  80. Traits in Rust
    https://doc.rust-lang.org/book/traits.html
  81. Functional Programming in Rust – Part 1 : Function Abstraction
    http://blog.madhukaraphatak­.com/functional-programming-in-rust-part-1/
  82. Of the emerging systems languages Rust, D, Go and Nim, which is the strongest language and why?
    https://www.quora.com/Of-the-emerging-systems-languages-Rust-D-Go-and-Nim-which-is-the-strongest-language-and-why
  83. Chytré ukazatele (moderní verze jazyka C++) [MSDN]
    https://msdn.microsoft.com/cs-cz/library/hh279674.aspx
  84. UTF-8 Everywhere
    http://utf8everywhere.org/
  85. Rust by Example
    http://rustbyexample.com/
  86. Rust oficiálně ve Fedoře
    https://mojefedora.cz/rust-oficialne-ve-fedore/
  87. Resource acquisition is initialization
    https://en.wikipedia.org/wi­ki/Resource_acquisition_is_i­nitialization
  88. TIOBE index (October 2016)
    http://www.tiobe.com/tiobe-index/
  89. Porovnání Go, D a Rustu na OpenHubu:
    https://www.openhub.net/lan­guages/compare?language_na­me[]=-1&language_name[]=-1&language_name[]=dmd&lan­guage_name[]=golang&langu­age_name[]=rust&language_na­me[]=-1&measure=commits
  90. String Types in Rust
    http://www.suspectsemantic­s.com/blog/2016/03/27/str­ing-types-in-rust/
  91. Trait (computer programming)
    https://en.wikipedia.org/wi­ki/Trait_%28computer_program­ming%29
  92. Type inference
    https://en.wikipedia.org/wi­ki/Type_inference
Našli jste v článku chybu?