Hlavní navigace

Programovací jazyk Rust: vlákna a sdílení objektů mezi nimi

Pavel Tišnovský

V deváté části seriálu o jazyku Rust se budeme zabývat prací s vlákny (threads), s čímž souvisí i sdílení objektů mezi vlákny. I v této oblasti programátorům pomáhá k tvorbě aplikací typový systém Rustu.

Obsah

1. Programovací jazyk Rust: vlákna a sdílení objektů mezi nimi

2. Metoda thread::sleep a výpočet časového intervalu pro tuto metodu

3. Vytvoření a spuštění nového vlákna

4. Použití anonymní funkce představující tělo nového vlákna

5. Uzávěry a problematika předání vlastnictví objektů

6. Jak tedy do vláken předávat parametry?

7. Korektní implementace uzávěru představujícího tělo nového vlákna

8. Čekání na dokončení běhu vlákna

9. Alokace objektů na haldě s atomickým počítáním referencí (thread-safe)

10. Vytvoření několika threadů s referencemi na společný objekt

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

12. Odkazy na Internetu

1. Programovací jazyk Rust: vlákna a sdílení objektů mezi nimi

V dnešní části seriálu o programovacím jazyku Rust se seznámíme se způsobem vytváření nových vláken (threads), čekání na dokončení běhu vláken (tato operace se někdy nazývá join) a taktéž s tím, jak lze mezi jednotlivými vlákny sdílet objekty uložené na haldě (heapu). Uvidíme, že i v této oblasti se pro zajištění korektnosti programu ve velké míře uplatňuje typový systém Rustu.

2. Metoda thread::sleep a výpočet časového intervalu pro tuto metodu

Pro zahřátí si ukažme velmi jednoduchý příklad, v němž se sice žádná vlákna nevytváří, ovšem modul std::thread je zde použit. V tomto příkladu se desetkrát vypíše hodnota počitadla, přičemž mezi každým výpisem je běh programu (resp. přesněji řečeno hlavního vlákna programu) pozastaven na dobu přibližně jedné sekundy. Pozastavení zajišťuje funkce thread::sleep(), které se předává objekt typu Duration (ten obsahuje specifikaci časového intervalu v sekundách a nanosekundách). Pro převod mezi milisekundami a typem Duration se používá funkce time::Duration::from_millis(). Důležité je, že tato funkce je nezávislá na typu operačního systému (ovšem při převodu se může provést zaokrouhlení časového intervalu podle možností konkrétního jádra):

use std::thread;
use std::time;
 
fn delay(ms : u64) {
    let amount = time::Duration::from_millis(ms);
    thread::sleep(amount);
}
 
fn main() {
    for i in 1..10 {
        println!("counting: {}", i);
        delay(1000);
    }
}

Příklad spuštění tohoto programu:

counting: 1
counting: 2
counting: 3
counting: 4
counting: 5
counting: 6
counting: 7
counting: 8
counting: 9

3. Vytvoření a spuštění nového vlákna

Vytvoření a spuštění nového vlákna je v programovacím jazyku Rust velmi jednoduché. Postačuje pouze použít funkci thread::spawn, které se předá anonymní funkce, jenž tvoří tělo nového vlákna. Mezi dvojici znaků || se vkládají jména a popř. i typy parametrů (viz též https://www.root.cz/clanky/rust-funkce-lambda-vyrazy-a-rozhodovaci-konstrukce-match/#k06):

thread::spawn(|| print_hello());

Ukažme si příklad, který po svém spuštění vytvoří deset vláken, z nichž každé vypíše na standardní výstup stejnou zprávu:

use std::thread;
 
fn print_hello() {
    println!("Hello from a thread...");
}
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        thread::spawn(|| print_hello());
    }
    println!("Stopping");
}

Konkrétní chování programu závisí na tom, kolik vláken stihne vypsat zprávu před ukončením hlavního vlákna, v němž je spuštěno tělo funkce main:

Starting
Hello from a thread...
Hello from a thread...
Hello from a thread...
Hello from a thread...
Hello from a thread...
Stopping

Program si můžeme nepatrně upravit, aby bylo patrné, jak se prolíná běh hlavního vlákna s ostatními vlákny:

use std::thread;
 
fn print_hello() {
    println!("Hello from a thread...");
}
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        println!("{}", i);
        thread::spawn(|| print_hello());
    }
    println!("Stopping");
}

Jeden z příkladů spuštění programu (po každém spuštění se může pořadí zpráv nepatrně lišit):

1
2
3
4
Hello from a thread...
5
Hello from a thread...
Hello from a thread...
6
Hello from a thread...
Hello from a thread...
7
8
9
Stopping

4. Použití anonymní funkce představující tělo nového vlákna

Vzhledem k tomu, že se do funkce thread::spawn() předává kód, který se spustí v nově vytvořeném vlákně, můžeme na tomto místě použít i anonymní funkci, s čímž se v praxi setkáme poměrně často. Konkrétně to znamená, že namísto:

thread::spawn(|| print_hello());

je možné psát:

thread::spawn(|| {println!("Hello from a thread...");});

Celý program, který po svém spuštění vytvoří deset vláken, může být zkrácen takto:

use std::thread;
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        thread::spawn(|| {println!("Hello from a thread...");});
    }
    println!("Stopping");
}

V kódu nikde nečekáme na ukončení vláken, což znamená, že jedno spuštění programu může dopadnout dobře:

Starting
Hello from a thread...
Hello from a thread...
Hello from a thread...
Hello from a thread...
Hello from a thread...
Stopping
Hello from a thread...
Hello from a thread...
Hello from a thread...

Zatímco při dalším spuštění dojde k běhové chybě, protože se nějaké vlákno bude snažit zapisovat do standardního výstupu, který je však již uzavřen:

Starting
Hello from a thread...
Hello from a thread...
Hello from a thread...
Hello from a thread...
Hello from a thread...
Stopping
Hello from a thread...
Hello from a thread...
Hello from a thread...
thread '<unnamed>' panicked at 'cannot access stdout during shutdown', ../src/libcore/option.rs:700
note: Run with `RUST_BACKTRACE=1` for a backtrace.

5. Uzávěry a problematika předání vlastnictví objektů

Při vytvoření a spuštění nového vlákna nemusí být tělo vlákna tvořeno „pouhou“ funkcí, ale může se jednat o uzávěr (closure). Uzávěry, s nimiž jsme se ve stručnosti seznámili ve třetí části tohoto článku, jsou funkce (typicky anonymní), které na sebe mají navázánu alespoň jednu tzv. volnou proměnnou. Ukažme si velmi jednoduchý příklad použití. Budeme potřebovat, aby se do každého vlákna předalo identifikační číslo vlákna, protože v těle vlákna budeme chtít vytisknout jeho číslo:

{println!("Hello from a thread #{}", i);}

Intuitivně by se implementace pro deset vláken mohla napsat následovně:

use std::thread;
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        thread::spawn(|| {println!("Hello from a thread #{}", i);});
    }
    println!("Stopping");
}

Poznámka: znaky || již známe – lze do nich zapsat případné parametry předávané do uzávěru, což ovšem v případě vláken nemá význam.

Ve skutečnosti ovšem toto řešení není korektní, protože program nebude možné přeložit. Při pokusu o překlad se vypíše toto chybové hlášení:

error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function
 --> 112_thread_clojure_error.rs:6:23
  |
6 |         thread::spawn(|| {println!("Hello from a thread #{}", i);});
  |                       ^^                                      - `i` is borrowed here
  |                       |
  |                       may outlive borrowed value `i`
  |
help: to force the closure to take ownership of `i` (and any other referenced variables), use the `move` keyword, as shown:
  |         thread::spawn(move || {println!("Hello from a thread #{}", i);});
 
error: aborting due to previous error

6. Jak tedy do vláken předávat parametry?

O co se jedná? Uzávěry skutečně mohou přistupovat k původně volným proměnným, které jsou na ně navázány (tj. v našem případě konkrétně k proměnné i), což většinou nezpůsobuje žádný problém. I proto uzávěry přistupují k proměnným přes reference, tj. v těle uzávěru vlastně máme k dispozici &i a ne i (toto je implementační vlastnost, se kterou se setkáme i v mnoha dalších jazycích, i když většinou taktéž skrytě).

Právě zde ovšem leží jádro problému – ve chvíli, kdy je tělo uzávěru spuštěno v jiném vlákně, může dojít k tomu, že oblast platnosti proměnné i skončí dříve, než běh vlákna – jinými slovy smyčka for i in 1..10 úspěšně nastartuje všechna vlákna a přejde k příkazu println!{„Stopping“};, zatímco vlákno může stále běžet. To by vedlo ke vzniku takzvaného dangling pointeru nebo wild pointeru, protože z vlákna by bylo možné přes referenci na i přistupovat do paměti, která již původnímu i nepatří (zde se sice bavíme o proměnné i, ale vždy máme na mysli objekt, který je na i navázán).

Programovací jazyk Rust tuto skutečnost, která může vést k velmi špatně detekovatelným chybám, které se dokonce ani nemusí projevit při testování, kontroluje a proto vypsal zmíněné chybové hlášení.

7. Korektní implementace uzávěru představujícího tělo nového vlákna

Korektní implementace uzávěru (resp. vlákna, kterému potřebujeme předat parametry) musí být nepatrně změněna z :

thread::spawn(|| {println!("Hello from a thread #{}", i);});

na:

thread::spawn(move || {println!("Hello from a thread #{}", i);});

Modifikátorem move umístěným před |názvy_parametrů| specifikujeme, že uzávěr nezíská pouze referenci na navázané proměnné, ale přímo jejich vlastnictví (interně se pro uzávěr vytvoří vlastní rámec na zásobníku). Teoreticky by tedy použití tohoto modifikátoru mělo vést k chybě překladu, protože proměnná i je použita jako počitadlo smyčky. Jenže zde se navíc projeví další vlastnost – celočíselné datové typy (a nejenom ony) implementují trait Copy, takže uzávěr sice skutečně získá vlastnictví, ovšem vlastnictví kopie původní hodnoty. A to je přesně to chování, kterého potřebujeme dosáhnout:

use std::thread;
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        thread::spawn(move || {println!("Hello from a thread #{}", i);});
    }
    println!("Stopping");
}

Poznámka: zde bych chtěl znovu upozornit na to, jak návrh typového systému Rustu ovlivňuje i další vlastnosti tohoto jazyka. Díky typovému systému, metodám pro předávání vlastnictví objektů a taktéž traitu Copy nám Rust „nutí“ korektní implementaci vláken, a to bez toho, aby vznikaly problémy při přístupu ke sdíleným prostředkům. Pokud je totiž zapotřebí skutečně sdílet společné objekty, jsme (většinou) donuceni využít dále popsaný Arc.

Spuštění může vypadat například takto (ovšem chování se samozřejmě bude odlišovat podle toho, jak přesně scheduler přepíná mezi vlákny):

Starting
Hello from a thread #3
Hello from a thread #2
Hello from a thread #1
Hello from a thread #4
Hello from a thread #6
Hello from a thread #7
Hello from a thread #5
Stopping
Hello from a thread #9

8. Čekání na dokončení běhu vlákna

Předchozí demonstrační příklady ve skutečnosti nebyly napsány příliš korektně, protože po určitém počtu pokusů o spuštění dojde k běhové chybě – nějaké vlákno se začne pokoušet o zápis na standardní výstup v době, kdy už je tento výstup zavřený z hlavního vlákna, v němž je spuštěna funkce main. Ovšem i při běžném programování se setkáme se situacemi, kdy potřebujeme počkat na ukončení běhu jiného vlákna. K docílení tohoto stavu lze využít hned několik technik, ovšem nejjednodušší je zavolat metodu join() na objekt vrácený funkcí thread::spawn(). Prozatím jsme tento objekt nijak nevyužívali (a tudíž ani neukládali do proměnné), ale samozřejmě je to možné. Typ tohoto objektu je std::thread::JoinHandle. Kód, který vytvoří nové vlákno a ihned poté počká ne jeho dokončení, bude vypadat následovně:

use std::thread;
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        let thr = thread::spawn(move || {println!("Hello from a thread #{}", i);});
        thr.join();
    }
    println!("Stopping");
}

Typ proměnné thr je Rustem odvozen automaticky, ale lze ho samozřejmě napsat i explicitně. Nesmíme zapomenout na to, že se jedná o generický typ (podle „typu“ uzávěru), takže se používá zápis Typ<_>:

use std::thread;
 
fn main() {
    println!("Starting");
    for i in 1..10 {
        let thr : thread::JoinHandle<_> = thread::spawn(move || {println!("Hello from a thread #{}", i);});
        thr.join();
    }
    println!("Stopping");
}

Po spuštění zjistíme, že díky čekání na dokončení vlákna jsou jednotlivá vlákna spouštěna sériově a nikoli paralelně:

Starting
Hello from a thread #1
Hello from a thread #2
Hello from a thread #3
Hello from a thread #4
Hello from a thread #5
Hello from a thread #6
Hello from a thread #7
Hello from a thread #8
Hello from a thread #9
Stopping

9. Alokace objektů na haldě s atomickým počítáním referencí (thread-safe)

S problematikou vláken velmi úzce souvisí i způsob vytváření objektů na haldě se sdílením těchto objektů (resp. referencí na tyto objekty) mezi více vlákny. V předchozím článku jsme se seznámili s počítáním referencí realizovaných přes objekt typu Rc. Připomeňme si, že je možné vytvořit objekt na haldě, „zabalit“ ho do Rc a posléze na tento objekt vytvořit libovolné množství referencí přes funkci Rc::clone(). Díky zabalení objektu do Rc je možné počítat reference na objekt na haldě s tím, že pokud počitadlo referencí klesne na nulu, je možné objekt automaticky dealokovat (a předtím zavolat jeho destruktor). To je sice pěkné a funkční řešení, jenže jak se bude chovat počitadlo referencí v případě, že objekt bude sdílen mezi větším množstvím vláken?

Odpověď na tuto otázku je jednoznačná – funkčnost není zaručena a je nutné využít jiný mechanismus. Tento mechanismus spočívá v použití datového typu Arc<T> namísto nám již známého typu Rc<T>. Název Arc nemá nic společného s obloukem, protože se jedná o zkratku názvu „Atomic Reference Counting“. Slovo „Atomic“ je zde důležité, protože podstatným rozdílem mezi Rc<T> a Arc<T> je použití počitadla, jehož hodnota se zvyšuje, snižuje a testuje na nulu v atomické operaci, kterou nemohou ostatní vlákna přerušit a přístup mají až k výsledku. To je sice poměrně neefektivní operace, která může (pokud nemáme vhodně navržený hardware) dokonce vést k tomu, že budou ostatní vlákna pozastavena, na druhou stranu se však jedná o jednu z mála možností, jak zaručit, že nedojde například k přepsání jednoho výsledku (hodnoty počitadla) výsledkem jiným (v mezním případě může dojít k přetečení či častěji k podtečení hodnoty počitadla přes 0).

10. Vytvoření několika threadů s referencemi na společný objekt

Podívejme se na jednoduché použití sdílení objektu mezi více vlákny s použitím Arc<T>. Vrátíme se k našemu příkladu s objektem (datovou strukturou) reprezentujícím komplexní číslo. Konstrukce komplexního čísla a jeho alokace na haldu se provede následovně (oproti demonstračnímu příkladu z předchozího článku jsme zde pouze nahradili Rc a Arc):

let c = Arc::new(Complex::new(1.0, 1.0));

Aby se v demonstračním příkladu skutečně ukázalo, jakým způsobem se řeší sdílení jednoho objektu mezi vlákny, vytvoříme novou datovou strukturu ComplexNumberOwner i s pomocnou metodou. Opět zde došlo k náhradě Rc za Arc:

struct ComplexNumberOwner {
    id: i32,
    value: Arc<Complex>
}
 
impl ComplexNumberOwner {
    fn print(&self) {
        println!("owner: number #{} with value {}+{}i", self.id, self.value.real, self.value.imag);
    }
}

Dále ve funkci start_threads() vytvoříme jeden objekt typu komplexní číslo a umístíme ho na haldu:

let c = Arc::new(Complex::new(1.0, 1.0));

Posléze se v programové smyčce vytvoří deset samostatně běžících vláken a každému vláknu se předá nový „vlastník“ komplexního čísla, který jako svůj prvek obsahuje referenci na objekt alokovaný na haldě. Důležité je, že volání c.clone() vytvoří novou referenci a současně atomicky zvýší počitadlo referencí. Vlákna skončí až po uplynutí přibližně 400 ms, aby bylo možné dobře sledovat práci alokátoru a dealokátoru:

fn start_threads() {
    let c = Arc::new(Complex::new(1.0, 1.0));
 
    for id in 0..10 {
        let owner = ComplexNumberOwner{id:id, value: c.clone()};
 
        thread::spawn(move || {
                owner.print();
                delay(400);
        });
    }
}

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

use std::sync::Arc;
use std::thread;
use std::time;
 
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}
    }
 
    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);
    }
}
 
struct ComplexNumberOwner {
    id: i32,
    value: Arc<Complex>
}
 
impl ComplexNumberOwner {
    fn print(&self) {
        println!("owner: number #{} with value {}+{}i", self.id, self.value.real, self.value.imag);
    }
}
 
fn delay(ms : u64) {
    let amount = time::Duration::from_millis(ms);
    thread::sleep(amount);
}
 
fn start_threads() {
    let c = Arc::new(Complex::new(1.0, 1.0));
 
    for id in 0..10 {
        let owner = ComplexNumberOwner{id:id, value: c.clone()};
 
        thread::spawn(move || {
                owner.print();
                delay(400);
        });
    }
}
 
fn main() {
    println!("starting threads");
    start_threads();
    println!("all threads started");
    delay(2000);
}

Ukázka chování tohoto příkladu po jeho spuštění. Povšimněte si, že se skutečně zavolá jen jediný konstruktor a po ukončení vláken i destruktor:

starting threads
Constructing complex number: 1+1i
owner: number #0 with value 1+1i
owner: number #2 with value 1+1i
owner: number #1 with value 1+1i
owner: number #3 with value 1+1i
owner: number #4 with value 1+1i
owner: number #5 with value 1+1i
owner: number #6 with value 1+1i
owner: number #7 with value 1+1i
all threads started
owner: number #8 with value 1+1i
owner: number #9 with value 1+1i
Dropping complex number: 1+1i

11. 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ář:

12. Odkazy na Internetu

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

Díky za upozornění. Asi jsem to napsal nejasně, ale myslel jsem to tak, že funkčnost není zaručena v obecném případě (bez ohledu na jazyk), proto to je v Rustu řešeno.

A ano, v Rustu by to z normálním počitadlem referencí nešlo přeložit. Asi to doplním aspoň sem do diskuze, co by se stalo při náhradě Arc za "normální" Rc:

error[E0277]: the trait bound `std::rc::Rc<­Complex>: std::marker::Send` is not satisfied --> test.rs:52:9 |52 | thread::spawn(move || { | ^^^^^…

17. 1. 2017 7:47

„jak se bude chovat počitadlo referencí v případě, že objekt bude sdílen mezi větším množstvím vláken? Odpověď na tuto otázku je jednoznačná – funkčnost není zaručena “

Opravdu? Já myslel, že výsledek je zaručen – nezkompiluje se to.