Přetěžování operátorů a správa paměti v programovacím jazyku Rust

3. 1. 2017
Doba čtení: 17 minut

Sdílet

 Autor: Rust project
V sedmé části seriálu o Rustu nejprve dokončíme téma přetěžování operátorů. Následně se zaměříme na způsob alokace objektů na haldě (heapu), s čímž souvisí problematika řešení správy paměti v Rustu.

Obsah

1. Přetěžování operátorů a správa paměti v programovacím jazyku Rust

2. Přetížení operátoru +=

3. Trait specifikující chování operátoru +=

4. Měnitelná proměnná na levé straně operátoru +=

5. Trait „Drop“

6. Implementace traitu „Drop“

7. Explicitní volání destruktoru

8. Alokace paměti pro objekty ukládané na haldu (heap)

9. Základní použití traitu „Box“

10. Alokace komplexního čísla na haldě

11. Konstruktor pro alokaci na haldě

12. Životnost objektů

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

14. Odkazy na Internetu

1. Přetěžování operátorů a správa paměti v programovacím jazyku Rust

V předchozí části seriálu o programovacím jazyku Rust jsme se mj. seznámili i se způsobem přetížení některých operátorů pro uživatelské datové typy, tj. pro datové struktury implementující traity s „předpisem“ metod, které s hodnotami uživatelských typů pracují popř. s nimi manipulují (modifikují je). Ukázali jsme si, jak lze přetížit operátor + a taktéž operátor == pro uživatelský datový typ představující jednu z možných implementací komplexních čísel. Operátory + a == jsou ve skutečnosti pro přetěžování vlastně velmi jednoduché, protože se v jejich implementaci nemusí (a neměly by) měnit hodnoty vstupních operandů – jinými slovy to znamená, že tyto operátory lze přetížit a používat i pro neměnné (immutable) hodnoty, což bylo ostatně v našich demonstračních příkladech splněno. Jen pro připomenutí, jak lze takové přetížení operátorů implementovat:

impl Add for Complex {
 
    type Output = Complex;
 
    fn add(self, right: Complex) -> Self::Output {
        Complex{real: self.real + right.real,
                imag: self.imag + right.imag}
    }
 
}
impl PartialEq for Complex {
 
    fn eq(&self, right: &Complex) -> bool {
        self.real == right.real && self.imag == right.imag
    }
}

2. Přetížení operátoru +=

Poněkud složitější situace nastane ve chvíli, kdy je nutné přetížit i ty operátory, u nichž se jeden z operandů mění. Jedná se především o všechny složené operátory, které vznikají kombinací nějakého binárního operátoru s operátorem přiřazení. Tyto operátory, které můžeme najít i v mnoha dalších programovacích jazycích, se zapisují následujícím způsobem:

lvalue OPERATOR= value

Přičemž by tento zápis měl být ekvivalentní zápisu:

lvalue = lvalue OPERATOR value

Příkladem může být složený operátor +=, který je zapotřebí implementovat jako kombinaci operátoru přiřazení a součtu. Tento operátor je implementován v traitu nazvaném AddAssign a v našem případě komplexních čísel může vypadat takto:

impl AddAssign for Complex {
    fn add_assign(&mut self, right: Complex) {
        *self = Complex::new(self.real + right.real,
                             self.imag + right.imag)
    }
}

Povšimněte si, jak se tento trait odlišuje od již zmíněného traitu Add:

  1. Není zde uveden řádek s typem výsledku (ani nemusí být, protože víme, že se modifikuje první operand).
  2. První operand musí být deklarován jako &mut (mutable).
  3. Výsledek je přiřazen, nikoli vrácen.

3. Trait specifikující chování operátoru +=

Následující demonstrační příklad vznikl rozšířením již minule popsaného příkladu. Nyní je však možné s komplexními čísly provádět i operace complex1+=complex2:

use std::ops::Add;
use std::ops::AddAssign;
use std::cmp::PartialEq;
 
struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
    fn print(&self) {
        println!("complex number: {:}+{:}i", self.real, self.imag);
    }
 
}
 
impl Add<Complex> for Complex {
 
    type Output = Complex;
 
    fn add(self, right: Complex) -> Self::Output {
        Complex::new(self.real + right.real,
                     self.imag + right.imag)
    }
}
 
impl Add<f32> for Complex {
 
    type Output = Complex;
 
    fn add(self, right: f32) -> Self::Output {
        Complex::new(self.real + right,
                     self.imag)
    }
}
 
impl AddAssign for Complex {
    fn add_assign(&mut self, right: Complex) {
        *self = Complex::new(self.real + right.real,
                             self.imag + right.imag)
    }
}
 
impl PartialEq for Complex {
 
    fn eq(&self, right: &Complex) -> bool {
        self.real == right.real && self.imag == right.imag
    }
}
 
fn main() {
    let c1 = Complex::new(1.0, 1.0);
    let c2 = Complex::new(3.0, 4.0);
    c1.print();
    c2.print();
    let c3 = c1 + c2;
    let c4 = Complex::new(4.0, 5.0);
    c3.print();
    c4.print();
    println!("c3 == c4? {}", (if c3==c4 { "yes"} else {"no"}));
 
    let c6 = Complex::new(1.0, 1.0);
    let c7 = Complex::new(3.0, 4.0);
    c6 += c7;
    c6.print();
}

4. Měnitelná proměnná na levé straně operátoru +=

Při pokusu o překlad předchozího kódu však překladač zcela správně vypíše chybové hlášení, protože se snažíme o změnu proměnné c6, která je neměnitelná (immutable):

error: cannot borrow immutable local variable `c6` as mutable
  --> 90_addassign_trait_no_mut.rs:69:5
   |
67 |     let c6 = Complex::new(1.0, 1.0);
   |         -- use `mut c6` here to make mutable
68 |     let c7 = Complex::new(3.0, 4.0);
69 |     c6 += c7;
   |     ^^ cannot borrow mutably
 
error: aborting due to previous error

Úprava je velmi snadná a spočívá v náhradě řádku:

let c6 = Complex::new(1.0, 1.0);

za řádek:

let mut c6 = Complex::new(1.0, 1.0);

Celý zdrojový kód demonstračního příkladu nyní vypadá následovně:

use std::ops::Add;
use std::ops::AddAssign;
use std::cmp::PartialEq;
 
struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
    fn print(&self) {
        println!("complex number: {:}+{:}i", self.real, self.imag);
    }
 
}
 
impl Add<Complex> for Complex {
 
    type Output = Complex;
 
    fn add(self, right: Complex) -> Self::Output {
        Complex::new(self.real + right.real,
                     self.imag + right.imag)
    }
}
 
impl Add<f32> for Complex {
 
    type Output = Complex;
 
    fn add(self, right: f32) -> Self::Output {
        Complex::new(self.real + right,
                     self.imag)
    }
}
 
impl AddAssign for Complex {
    fn add_assign(&mut self, right: Complex) {
        *self = Complex::new(self.real + right.real,
                             self.imag + right.imag)
    }
}
 
impl PartialEq for Complex {
 
    fn eq(&self, right: &Complex) -> bool {
        self.real == right.real && self.imag == right.imag
    }
}
 
fn main() {
    let c1 = Complex::new(1.0, 1.0);
    let c2 = Complex::new(3.0, 4.0);
    c1.print();
    c2.print();
    let c3 = c1 + c2;
    let c4 = Complex::new(4.0, 5.0);
    c3.print();
    c4.print();
    println!("c3 == c4? {}", (if c3==c4 { "yes"} else {"no"}));
 
    let mut c6 = Complex::new(1.0, 1.0);
    let c7 = Complex::new(3.0, 4.0);
    c6 += c7;
    c6.print();
}

Tento program již pracuje podle očekávání:

complex number: 1+1i
complex number: 3+4i
complex number: 4+5i
complex number: 4+5i
c3 == c4? yes
complex number: 4+5i

5. Trait „Drop“

V dalších kapitolách se zaměříme na popis způsobů alokace a dealokace objektů. Při sledování stavu objektů může být užitečné implementovat pro zvolený uživatelský datový typ trait nazvaný Drop. Tento trait obsahuje pouze jedinou metodu pojmenovanou drop, přičemž tato metoda je zavolána ve chvíli, kdy se program dostane na místo, odkud již není daný objekt dosažitelný (může se jednat o návrat z funkce, výskok z programového bloku atd.):

pub trait Drop {
    fn drop(&mut self);
}

Ve chvíli, kdy je metoda drop zavolána, ještě objekt existuje, což znamená, že self je možné použít pro přístup ke složkám objektu, provést ruční dealokaci, zavření souborů či socketů atd. Teprve po výskoku z této metody je provedena automatická dealokace, což znamená, že drop vlastně nahrazuje klasické destruktory.

6. Implementace traitu „Drop“

Do příkladu s implementací jedné varianty komplexních čísel přidáme implementaci traitu Drop. Tato implementace nebude provádět žádný skutečný úklid objektu, pouze vypíše, který objekt je právě odstraňován z paměti. Vzhledem k tomu, že je při volání metody drop celý objekt ještě kompletní, lze bez problémů přistupovat k jeho prvkům (datovým složkám):

impl Drop for Complex {
    fn drop(&mut self) {
        println!("Dropping complex number: {:}+{:}i", self.real, self.imag);
    }
}

Celý zdrojový kód další verze našeho demonstračního příkladu nyní vypadá takto:

use std::ops::Add;
use std::ops::AddAssign;
use std::cmp::PartialEq;
 
struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
    fn print(&self) {
        println!("complex number: {:}+{:}i", self.real, self.imag);
    }
 
}
 
impl Add<Complex> for Complex {
 
    type Output = Complex;
 
    fn add(self, right: Complex) ->  Self::Output {
        Complex::new(self.real + right.real,
                     self.imag + right.imag)
    }
}
 
impl Add<f32> for Complex {
 
    type Output = Complex;
 
    fn add(self, right: f32) ->  Self::Output {
        Complex::new(self.real + right,
                     self.imag)
    }
}
 
impl AddAssign for Complex {
    fn add_assign(&mut self, right: Complex) {
        *self = Complex::new(self.real + right.real,
                             self.imag + right.imag)
    }
}
 
impl PartialEq for Complex {
 
    fn eq(&self, right: &Complex) ->  bool {
        self.real == right.real && self.imag == right.imag
    }
}
 
impl Drop for Complex {
    fn drop(&mut self) {
        println!("Dropping complex number: {:}+{:}i", self.real, self.imag);
    }
}
 
fn main() {
    let c1 = Complex::new(1.0, 1.0);
    let c2 = Complex::new(3.0, 4.0);
    c1.print();
    c2.print();
    let c3 = c1 + c2;
    let c4 = Complex::new(4.0, 5.0);
    c3.print();
    c4.print();
    println!("c3 == c4? {}", (if c3==c4 { "yes"} else {"no"}));
 
    let mut c6 = Complex::new(1.0, 1.0);
    let c7 = Complex::new(3.0, 4.0);
    c6 += c7;
    c6.print();
}

Po spuštění tohoto příkladu by se na standardní výstup měly vypsat následující zprávy:

complex number: 1+1i
complex number: 3+4i
Dropping complex number: 3+4i
Dropping complex number: 1+1i
complex number: 4+5i
complex number: 4+5i
c3 == c4? yes
Dropping complex number: 1+1i
Dropping complex number: 3+4i
complex number: 4+5i
Dropping complex number: 4+5i
Dropping complex number: 4+5i
Dropping complex number: 4+5i

7. Explicitní volání destruktoru

Destruktory, resp. přesněji řečeno metodu drop, se volají pouze automaticky ve chvíli, kdy se mění oblast platnosti objektu. Metodu drop nelze zavolat explicitně, o čemž se ostatně můžeme velmi snadno přesvědčit pokusem o překlad následujícího příkladu:

use std::ops::Add;
use std::ops::AddAssign;
use std::cmp::PartialEq;
 
struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {

    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
    fn print(&self) {
        println!("complex number: {:}+{:}i", self.real, self.imag);
    }

}
 
impl Drop for Complex {
    fn drop(&mut self) {
        println!("Dropping complex number: {:}+{:}i", self.real, self.imag);
    }
}
 
fn main() {
    let c1 = Complex::new(1.0, 1.0);
    let c2 = Complex::new(3.0, 4.0);
    let c3 = Complex::new(0.0, 0.0);
    c1.print();
    c2.print();
    c3.print();
    c1.drop();
    c2.drop();
    c3.drop();
}

Při pokusu o překlad se vypíše toto chybové hlášení:

error[E0040]: explicit use of destructor method
  --> test.rs:35:8
   |
35 |     c1.drop();
   |        ^^^^ call to destructor method
 
error[E0040]: explicit use of destructor method
  --> test.rs:36:8
   |
36 |     c2.drop();
   |        ^^^^ call to destructor method
 
error[E0040]: explicit use of destructor method
  --> test.rs:37:8
   |
37 |     c3.drop();
   |        ^^^^ call to destructor method
 
error: aborting due to 3 previous errors

8. Alokace paměti pro objekty ukládané na haldu (heap)

Programovací jazyk Rust neobsahuje žádnou formu automatického správce paměti (garbage collector), který je použit, samozřejmě s výjimkou céčka a standardního C++, v prakticky všech vyšších programovacích jazycích. To v praxi znamená, že programátor musí explicitně stanovit, kdy a jakým způsobem jsou objekty alokovány a dealokovány. Prozatím jsme s tímto konceptem vlastně neměli žádný větší problém, a to z toho důvodu, že objekty jsou implicitně alokovány na zásobníku (stack), resp. přesněji řečeno v právě aktivním zásobníkovém rámci (stack frame). To je v mnoha ohledech výhodné, protože se po návratu z funkcí zásobníkový rámec (a tím pádem i objekty v něm uložené) automaticky uvolní z paměti, navíc nenastává ani fragmentace paměti atd. Ovšem ve chvíli, kdy je nutné vytvořit objekt viditelný i mimo aktivní zásobníkový rámec (či rámce nad ním), je nutné objekty alokovat na haldě (heapu). To se provádí s využitím speciálního konstruktoru Box::new(x:T), který alokuje novou oblast na haldě a posléze do této oblasti uloží x. Návratovým typem Box::new(x:T) je Box<T>.

9. Základní použití traitu „Box“

Podívejme se nyní na pravděpodobně úplně nejjednodušší příklad ukazující, jakým způsobem je možné alokovat nějaký objekt na haldě. Tímto objektem je zde myšlena celočíselná hodnota 42. Pokud v programu pouze zapíšeme:

fn foo() {
    let x = 42;
 
    println!("{}", x);
}

vytvoří se proměnná x a na ní navázaná hodnota na zásobníku, resp. přesněji řečeno na zásobníkovém rámci. Životnost této proměnné i hodnoty navázané na proměnnou je omezena dobou, po kterou se řízení programu nachází v této funkci popř. ve funkcích volaných. Naproti tomu následující funkce:

fn main() {
    let x = Box::new(42);
 
    println!("{}", x);
}

je poněkud komplikovanější, protože volání Box::new(42) alokuje oblast paměti na haldě a posléze do této oblasti uloží číslo 42. Následně se vrátí struktura, která mj. obsahuje i adresu alokované oblasti paměti na haldě, to vše je zabalené do objektu typu Box.

Navíc tento objekt implementuje i trait Drop, který zajistí automatické uvolnění oblasti alokované na haldě, což si ukážeme v navazujících příkladech. Povšimněte si, že se jedná o dosti podstatný rozdíl oproti mnoha dalším jazykům, které po alokaci vrací ukazatele či reference a ztrácí tak informace o tom, jaká je životnost objektu.

10. Alokace komplexního čísla na haldě

Následující příklad ukazuje, jakým způsobem můžeme na haldě alokovat prostor pro komplexní číslo, tj. pro datovou strukturu obsahující dva prvky typu f32: reálnou a imaginární složku. Ve skutečnosti je komplexní číslo vytvořeno na zásobníkovém rámci a až poté zkopírováno na haldu, ovšem právě tento typ operace je optimalizován (Rust je postaven nad LLVM), takže se většinou kopie bitového obrazu objektu zcela eliminuje:

struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
}
 
impl Drop for Complex {
    fn drop(&mut self) {
        println!("Dropping complex number: {:}+{:}i", self.real, self.imag);
    }
}
 
fn main() {
    let x = Box::new(42);
 
    println!("{}", x);
 
    let c = Box::new(Complex::new(1.0, 2.0));
}

Po překladu a spuštění tohoto příkladu získáme na standardním výstupu následující dva řádky, které ukazují, že se Drop skutečně zavolá:

42
Dropping complex number: 1+2i

11. Konstruktor pro alokaci na haldě

Malým rozšířením zdrojového kódu můžeme zařídit, aby třída popisující komplexní čísla obsahovala i speciální typ konstruktoru, který vytvoří komplexní číslo přímo na haldě. Tento konstruktor se může jmenovat například new_on_heap. Tím, že samotné vytvoření komplexního čísla je použito ve volání konstruktoru Box::new() pomůžeme překladači programovacího jazyka Rust při optimalizaci výsledného strojového kódu:

fn new_on_heap(real: f32, imag: f32) -> Box<Complex> {
    Box::new(Complex{real:real, imag:imag})
}

Úplný zdrojový kód tohoto příkladu vypadá následovně:

struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Complex {
        Complex{real:real, imag:imag}
    }
 
    fn new_on_heap(real: f32, imag: f32) -> Box<Complex> {
        Box::new(Complex{real:real, imag:imag})
    }
 
    fn print(&self) {
        println!("complex number: {}+{}i", self.real, self.imag);
    }
 
}
 
impl Drop for Complex {
    fn drop(&mut self) {
        println!("Dropping complex number: {:}+{:}i", self.real, self.imag);
    }
}
 
fn main() {
    let c1 = Box::new(Complex::new(1.0, 2.0));
    let c2 = Complex::new_on_heap(3.0, 4.0);
    c1.print();
    c2.print();
}

Po spuštění příkladu se na standardní výstup vypíšou následující zprávy:

complex number: 1+2i
complex number: 3+4i
Dropping complex number: 3+4i
Dropping complex number: 1+2i

12. Životnost objektů

V dalším, dnes již posledním, demonstračním příkladu si ukážeme, že destruktory objektů vytvořené implementací traitu Drop jsou skutečně volány ve chvíli, kdy dochází k ukončení životnosti (či viditelnosti) objektů. Nejedná se tedy o žádnou obdobu metody finalize známé z Javy, která může být volána mnohdy až dlouho poté, co je objekt nedosažitelný, nebo dokonce nemusí být volána vůbec:

struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Complex {
        println!("Constructing complex number: {:}+{:}i", real, imag);
        Complex{real:real, imag:imag}
    }
 
}
 
impl Drop for Complex {
    fn drop(&mut self) {
        println!("Dropping complex number: {:}+{:}i", self.real, self.imag);
    }
}
 
fn fn2() {
    println!("fn2 begin");
    let c = Box::new(Complex::new(2.0, 2.0));
    println!("fn2 end");
}
 
fn fn1() {
    println!("fn1 begin");
    let c = Box::new(Complex::new(1.0, 1.0));
    fn2();
    println!("fn1 end");
}
 
fn main() {
    println!("main begin");
    let c = Box::new(Complex::new(0.0, 0.0));
    fn1();
    println!("main end");
}

Překladač sice bude při překladu tohoto demonstračního příkladu vypisovat varování o tom, že vytvořené objekty nejsou nikde použity, to však vůbec nevadí, protože zdrojový kód je v pořádku:

  --> 95_lifetime.rs:23:9
   |
23 |     let c = Box::new(Complex::new(2.0, 2.0));
   |         ^
 
warning: unused variable: `c`, #[warn(unused_variables)] on by default
  --> 95_lifetime.rs:29:9
   |
29 |     let c = Box::new(Complex::new(1.0, 1.0));
   |         ^
 
warning: unused variable: `c`, #[warn(unused_variables)] on by default
  --> 95_lifetime.rs:36:9
   |
36 |     let c = Box::new(Complex::new(0.0, 0.0));
   |         ^

Zajímavé jsou pro nás výsledky běhu tohoto příkladu, protože v něm z funkce main voláme funkci fn1 a z ní funkci fn2. To samozřejmě ovlivňuje životnost lokálních proměnných (všechny se jmenují stejně) a tím pádem i alokaci/dealokaci komplexních čísel na haldě:

ict ve školství 24

main begin
Constructing complex number: 0+0i
fn1 begin
Constructing complex number: 1+1i
fn2 begin
Constructing complex number: 2+2i
fn2 end
Dropping complex number: 2+2i
fn1 end
Dropping complex number: 1+1i
main end
Dropping complex number: 0+0i

Vidíme, že komplexní číslo 0+0i bylo vytvořeno jako první ve funkci main a dealokace proběhla až na samotném konci programu, tj. ihned po opuštění funkce main. Podobně je tomu s životností komplexních čísel 1+1i a 2+2i (to má nejkratší životnost).

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ý (dnes již poněkud objemný) repositář:

14. Odkazy na Internetu

  1. Operators and Overloading
    https://doc.rust-lang.org/book/operators-and-overloading.html
  2. Module std::ops
    https://doc.rust-lang.org/std/ops/index.html
  3. Module std::cmp
    https://doc.rust-lang.org/std/cmp/index.html
  4. Trait std::ops::Add
    https://doc.rust-lang.org/stable/std/ops/trait.Add.html
  5. Trait std::ops::AddAssign
    https://doc.rust-lang.org/std/ops/trait.AddAssign.html
  6. Trait std::ops::Drop
    https://doc.rust-lang.org/std/ops/trait.Drop.html
  7. Trait std::cmp::Eq
    https://doc.rust-lang.org/std/cmp/trait.Eq.html
  8. Struct std::boxed::Box
    https://doc.rust-lang.org/std/boxed/struct.Box.html
  9. Explore the ownership system in Rust
    https://nercury.github.io/rus­t/guide/2015/01/19/ownership­.html
  10. Rust's ownership and move semantic
    http://www.slideshare.net/sa­neyuki/rusts-ownership-and-move-semantics
  11. Trait std::marker::Copy
    https://doc.rust-lang.org/stable/std/marker/tra­it.Copy.html
  12. Trait std::clone::Clone
    https://doc.rust-lang.org/stable/std/clone/tra­it.Clone.html
  13. The Stack and the Heap
    https://doc.rust-lang.org/book/the-stack-and-the-heap.html
  14. Rust Compare: Pointers & References
    http://www.rust-compare.com/site/pointers.html
  15. Rust Compare: Parameters
    http://www.rust-compare.com/site/params.html
  16. Why does this compile? Automatic dereferencing?
    https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183
  17. Understanding Pointers, Ownership, and Lifetimes in Rust
    http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html
  18. Rust lang series episode #25 — pointers (#rust-series)
    https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series
  19. Rust – home page
    https://www.rust-lang.org/en-US/
  20. Rust – Frequently Asked Questions
    https://www.rust-lang.org/en-US/faq.html
  21. Destructuring and Pattern Matching
    https://pzol.github.io/get­ting_rusty/posts/20140417_des­tructuring_in_rust/
  22. The Rust Programming Language
    https://doc.rust-lang.org/book/
  23. Rust (programming language)
    https://en.wikipedia.org/wi­ki/Rust_%28programming_lan­guage%29
  24. Go – home page
    https://golang.org/
  25. Stack Overflow – Most Loved, Dreaded, and Wanted language
    https://stackoverflow.com/re­search/developer-survey-2016#technology-most-loved-dreaded-and-wanted
  26. 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/
  27. Rust vs Go: My experience
    https://www.reddit.com/r/go­lang/comments/21m6jq/rust_vs_go_my_ex­perience/
  28. Friends of Rust (Organizations running Rust in production)
    https://www.rust-lang.org/en-US/friends.html
  29. Rust programs versus C++ g++
    https://benchmarksgame.ali­oth.debian.org/u64q/compa­re.php?lang=rust&lang2=gpp
  30. Další benchmarky (nejedná se o reálné příklady „ze života“)
    https://github.com/kostya/benchmarks
  31. Go na Redditu
    https://www.reddit.com/r/golang/
  32. Rust vs. Go
    http://vschart.com/compare/rust/vs/go-language
  33. Abstraction without overhead: traits in Rust
    https://blog.rust-lang.org/2015/05/11/traits.html
  34. Method Syntax
    https://doc.rust-lang.org/book/method-syntax.html
  35. Traits in Rust
    https://doc.rust-lang.org/book/traits.html
  36. Functional Programming in Rust – Part 1 : Function Abstraction
    http://blog.madhukaraphatak­.com/functional-programming-in-rust-part-1/
  37. 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
  38. Chytré ukazatele (moderní verze jazyka C++) [MSDN]
    https://msdn.microsoft.com/cs-cz/library/hh279674.aspx
  39. UTF-8 Everywhere
    http://utf8everywhere.org/
  40. Rust by Example
    http://rustbyexample.com/
  41. Rust oficiálně ve Fedoře
    https://mojefedora.cz/rust-oficialne-ve-fedore/
  42. Resource acquisition is initialization
    https://en.wikipedia.org/wi­ki/Resource_acquisition_is_i­nitialization
  43. TIOBE index (October 2016)
    http://www.tiobe.com/tiobe-index/
  44. 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
  45. String Types in Rust
    http://www.suspectsemantic­s.com/blog/2016/03/27/str­ing-types-in-rust/
  46. Trait (computer programming)
    https://en.wikipedia.org/wi­ki/Trait_%28computer_program­ming%29
  47. Type inference
    https://en.wikipedia.org/wi­ki/Type_inference

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.