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
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
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:
- 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().
- 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/presentations/blob/master/rust/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/presentations/blob/master/rust/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/presentations/blob/master/rust/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/wiki/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/presentations/blob/master/rust/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/presentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář:
292_tcp_listener1.rs | https://github.com/tisnik/presentations/blob/master/rust/292_tcp_listener1.rs |
293_tcp_listener2.rs | https://github.com/tisnik/presentations/blob/master/rust/293_tcp_listener2.rs |
294_tcp_listener3.rs | https://github.com/tisnik/presentations/blob/master/rust/294_tcp_listener3.rs |
295_tcp_listener4.rs | https://github.com/tisnik/presentations/blob/master/rust/295_tcp_listener4.rs |
296_tcp_listener5.rs | https://github.com/tisnik/presentations/blob/master/rust/296_tcp_listener5.rs |
297_tcp_client1.rs | https://github.com/tisnik/presentations/blob/master/rust/297_tcp_client1.rs |
14. Odkazy na Internetu
- std::net::IpAddr
https://doc.rust-lang.org/std/net/enum.IpAddr.html - std::net::Ipv4Addr
https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html - std::net::Ipv6Addr
https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html - TcpListener
https://doc.rust-lang.org/std/net/struct.TcpListener.html - TcpStream
https://doc.rust-lang.org/std/net/struct.TcpStream.html - Binary heap (Wikipedia)
https://en.wikipedia.org/wiki/Binary_heap - Binární halda (Wikipedia)
https://cs.wikipedia.org/wiki/Bin%C3%A1rn%C3%AD_halda - Halda (datová struktura)
https://cs.wikipedia.org/wiki/Halda_%28datov%C3%A1_struktura%29 - Struct std::collections::HashSet
https://doc.rust-lang.org/std/collections/struct.HashSet.html - Struct std::collections::BTreeSet
https://doc.rust-lang.org/std/collections/struct.BTreeSet.html - Struct std::collections::BinaryHeap
https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html - Set (abstract data type)
https://en.wikipedia.org/wiki/Set_%28abstract_data_type%29#Language_support - Associative array
https://en.wikipedia.org/wiki/Associative_array - Hash Table
https://en.wikipedia.org/wiki/Hash_table - B-tree
https://en.wikipedia.org/wiki/B-tree - Pedro Celis: Robin Hood Hashing (naskenované PDF!)
https://cs.uwaterloo.ca/research/tr/1986/CS-86–14.pdf - Robin Hood hashing
http://codecapsule.com/2013/11/11/robin-hood-hashing/ - Robin Hood hashing: backward shift deletion
http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ - Module std::collections
https://doc.rust-lang.org/std/collections/ - Module std::vec
https://doc.rust-lang.org/nightly/std/vec/index.html - Struct std::collections::VecDeque
https://doc.rust-lang.org/std/collections/struct.VecDeque.html - Struct std::collections::LinkedList
https://doc.rust-lang.org/std/collections/struct.LinkedList.html - Module std::fmt
https://doc.rust-lang.org/std/fmt/ - Macro std::println
https://doc.rust-lang.org/std/macro.println.html - Enum std::result::Result
https://doc.rust-lang.org/std/result/enum.Result.html - Module std::result
https://doc.rust-lang.org/std/result/ - Result
http://rustbyexample.com/std/result.html - Rust stdlib: Option
https://doc.rust-lang.org/std/option/enum.Option.html - Module std::option
https://doc.rust-lang.org/std/option/index.html - Rust by example: option
http://rustbyexample.com/std/option.html - Rust by example: if-let
http://rustbyexample.com/flow_control/if_let.html - Rust by example: while let
http://rustbyexample.com/flow_control/while_let.html - Rust by example: Option<i32>
http://rustbyexample.com/std/option.html - An Overview of Macros in Rust
http://words.steveklabnik.com/an-overview-of-macros-in-rust - A Practical Intro to Macros in Rust 1.0
https://danielkeep.github.io/practical-intro-to-macros.html - The Rust Programming Language: macros
https://doc.rust-lang.org/beta/book/macros.html - Rust by example: 15 macro_rules!
http://rustbyexample.com/macros.html - Primitive Type isize
https://doc.rust-lang.org/nightly/std/primitive.isize.html - Primitive Type usize
https://doc.rust-lang.org/nightly/std/primitive.usize.html - Primitive Type array
https://doc.rust-lang.org/nightly/std/primitive.array.html - Module std::slice
https://doc.rust-lang.org/nightly/std/slice/ - Rust by Example: 2.3 Arrays and Slices
http://rustbyexample.com/primitives/array.html - What is the difference between Slice and Array (stackoverflow)
http://stackoverflow.com/questions/30794235/what-is-the-difference-between-slice-and-array - Learning Rust With Entirely Too Many Linked Lists
http://cglab.ca/~abeinges/blah/too-many-lists/book/ - Testcase: linked list
http://rustbyexample.com/custom_types/enum/testcase_linked_list.html - Operators and Overloading
https://doc.rust-lang.org/book/operators-and-overloading.html - Module std::ops
https://doc.rust-lang.org/std/ops/index.html - Module std::cmp
https://doc.rust-lang.org/std/cmp/index.html - Trait std::ops::Add
https://doc.rust-lang.org/stable/std/ops/trait.Add.html - Trait std::ops::AddAssign
https://doc.rust-lang.org/std/ops/trait.AddAssign.html - Trait std::ops::Drop
https://doc.rust-lang.org/std/ops/trait.Drop.html - Trait std::cmp::Eq
https://doc.rust-lang.org/std/cmp/trait.Eq.html - Struct std::boxed::Box
https://doc.rust-lang.org/std/boxed/struct.Box.html - Explore the ownership system in Rust
https://nercury.github.io/rust/guide/2015/01/19/ownership.html - Rust's ownership and move semantic
http://www.slideshare.net/saneyuki/rusts-ownership-and-move-semantics - Trait std::marker::Copy
https://doc.rust-lang.org/stable/std/marker/trait.Copy.html - Trait std::clone::Clone
https://doc.rust-lang.org/stable/std/clone/trait.Clone.html - The Stack and the Heap
https://doc.rust-lang.org/book/the-stack-and-the-heap.html - Rust Compare: Pointers & References
http://www.rust-compare.com/site/pointers.html - Rust Compare: Parameters
http://www.rust-compare.com/site/params.html - Why does this compile? Automatic dereferencing?
https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183 - Understanding Pointers, Ownership, and Lifetimes in Rust
http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html - Rust lang series episode #25 — pointers (#rust-series)
https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series - Rust – home page
https://www.rust-lang.org/en-US/ - Rust – Frequently Asked Questions
https://www.rust-lang.org/en-US/faq.html - Destructuring and Pattern Matching
https://pzol.github.io/getting_rusty/posts/20140417_destructuring_in_rust/ - The Rust Programming Language
https://doc.rust-lang.org/book/ - Rust (programming language)
https://en.wikipedia.org/wiki/Rust_%28programming_language%29 - Go – home page
https://golang.org/ - Stack Overflow – Most Loved, Dreaded, and Wanted language
https://stackoverflow.com/research/developer-survey-2016#technology-most-loved-dreaded-and-wanted - 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/ - Rust vs Go: My experience
https://www.reddit.com/r/golang/comments/21m6jq/rust_vs_go_my_experience/ - Friends of Rust (Organizations running Rust in production)
https://www.rust-lang.org/en-US/friends.html - Rust programs versus C++ g++
https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=rust&lang2=gpp - Další benchmarky (nejedná se o reálné příklady „ze života“)
https://github.com/kostya/benchmarks - Go na Redditu
https://www.reddit.com/r/golang/ - Rust vs. Go
http://vschart.com/compare/rust/vs/go-language - Abstraction without overhead: traits in Rust
https://blog.rust-lang.org/2015/05/11/traits.html - Method Syntax
https://doc.rust-lang.org/book/method-syntax.html - Traits in Rust
https://doc.rust-lang.org/book/traits.html - Functional Programming in Rust – Part 1 : Function Abstraction
http://blog.madhukaraphatak.com/functional-programming-in-rust-part-1/ - 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 - Chytré ukazatele (moderní verze jazyka C++) [MSDN]
https://msdn.microsoft.com/cs-cz/library/hh279674.aspx - UTF-8 Everywhere
http://utf8everywhere.org/ - Rust by Example
http://rustbyexample.com/ - Rust oficiálně ve Fedoře
https://mojefedora.cz/rust-oficialne-ve-fedore/ - Resource acquisition is initialization
https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization - TIOBE index (October 2016)
http://www.tiobe.com/tiobe-index/ - Porovnání Go, D a Rustu na OpenHubu:
https://www.openhub.net/languages/compare?language_name[]=-1&language_name[]=-1&language_name[]=dmd&language_name[]=golang&language_name[]=rust&language_name[]=-1&measure=commits - String Types in Rust
http://www.suspectsemantics.com/blog/2016/03/27/string-types-in-rust/ - Trait (computer programming)
https://en.wikipedia.org/wiki/Trait_%28computer_programming%29 - Type inference
https://en.wikipedia.org/wiki/Type_inference