Správa paměti v programovacím jazyku Rust s počítáním referencí

10. 1. 2017
Doba čtení: 21 minut

Sdílet

 Autor: Rust project
Už jsme si ukázali alokaci objektů na haldě (heapu) s jejich automatickou dealokací ve chvíli, kdy objekt přestal být viditelný. Dnes si popíšeme další způsob, který spočívá v počítání referencí.

Obsah

1. Přímé volání destruktoru?

2. Nepřímé volání destruktoru s využitím funkce std::mem::drop

3. Alokace objektů na haldě s počítáním referencí

4. Demonstrační příklad – alokace struktury představující komplexní číslo

5. Vytvoření většího množství referencí na jediný objekt alokovaný na haldě

6. Mutace objektu alokovaného na haldě, na nějž existuje větší množství referencí

7. Sémantika „move“ a referencované objekty na haldě

8. Sémantika „move“ a použití Rc::clone()

9. Reference na objekt vlastněná jiným objektem

10. Poznámka na závěr: použití datového typu Self

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

12. Odkazy na Internetu

1. Přímé volání destruktoru?

V předchozí části seriálu o programovacím jazyku Rust jsme si řekli, že metodu drop() deklarovanou pro nějakou datovou strukturu, například pro naši implementaci komplexních čísel, není možné volat explicitně – toto zakázané volání je detekováno, podobně jako další sémantické chyby, již při překladu. Jen pro připomentí – v následujícím kódu se pokoušíme o explicitní volání destruktoru:

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();   // zakázáno, porušení je detekováno překladačem
    c2.drop();   // zakázáno, porušení je detekováno překladačem
    c3.drop();   // zakázáno, porušení je detekováno překladačem
}

Při pokusu o překlad kódu s takto napsanou funkcí main() se vypíše následující 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

Ve skutečnosti však není nutné čekat na to, až se destruktor zavolá automaticky, protože existuje způsob nepřímého zavolání destruktoru. Podrobnosti si uvedeme v navazující kapitole.

2. Nepřímé volání destruktoru s využitím funkce std::mem::drop

Destruktor je možné v případě potřeby volat nepřímo, a to konkrétně s využitím funkce nazvané std::mem::drop(). U funkce z tohoto modulu není zapotřebí explicitně zapisovat jmenný prostor, proto ji můžeme volat pouze jako drop(). I přes stejné pojmenování je mezi následujícími programovými řádky dosti podstatný rozdíl, protože na druhém řádku se pokoušíme zavolat destruktor přímo (jako by se jednalo o běžnou funkci), zatímco na řádku třetím voláme funkci std::mem::drop, která sice taktéž zavolá destruktor, ale postará se i o zajištění celé sémantiky dealokace objektu (viz též sedmou část tohoto seriálu):

let c1 = Complex::new(1.0, 1.0);
c1.drop();   // explicitní zavolání destruktoru - nelze provést
drop(c1);    // dealokace objektu - toto lze provést

Následuje výpis dvou prakticky shodných zdrojových kódů, které se od sebe odlišují pouze tím, že v prvním příkladu necháme na překladači, kam přesně umístí volání destruktoru (bude to ihned poté, co zanikne viditelnost proměnné, na níž je komplexní číslo navázáno), zatímco ve druhém příkladu se destruktor zavolá dříve (explicitním nepřímým zavoláním):

Zdrojový kód, v němž je volání destruktoru navázáno na oblast viditelnosti proměnných

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");
}

Zdrojový kód, v němž se destruktory volají explicitně (i když nepřímo)

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));
    drop(c);
    println!("fn2 end");
}
 
fn fn1() {
    println!("fn1 begin");
    let c = Box::new(Complex::new(1.0, 1.0));
    drop(c);
    fn2();
    println!("fn1 end");
}
 
fn main() {
    println!("main begin");
    let c = Box::new(Complex::new(0.0, 0.0));
    drop(c);
    fn1();
    println!("main end");
}

Rozdíl ve výpisech obou příkladů nám naznačuje, kde přesně došlo k dealokaci komplexních čísel:

Automatické volání destruktorů            "Vynucené" zavolání destruktorů
--------------------------------------------------------------------------
main begin                                main begin
Constructing complex number: 0+0i         Constructing complex number: 0+0i
fn1 begin                                 Dropping complex number: 0+0i
Constructing complex number: 1+1i         fn1 begin
fn2 begin                                 Constructing complex number: 1+1i
Constructing complex number: 2+2i         Dropping complex number: 1+1i
fn2 end                                   fn2 begin
Dropping complex number: 2+2i             Constructing complex number: 2+2i
fn1 end                                   Dropping complex number: 2+2i
Dropping complex number: 1+1i             fn2 end
main end                                  fn1 end
Dropping complex number: 0+0i             main end

Poznámka: volání drop(c1) v našem případě odpovídá sémantice „move“ (protože jsme neimplementovali Copy či Clone traity), což znamená, že si překladač pohlídá následující (samozřejmě nekorektní) zápis, a to s použitím prostředků, o nichž jsme se již zmiňovali v předchozích částech (vlastnictví objektů):

fn fn2() {
    println!("fn2 begin");
    let c = Box::new(Complex::new(2.0, 2.0));
    drop(c);
    println!("{}", c.real);
    println!("fn2 end");
}

Při pokusu o překlad dostaneme po právu vynadáno, že se snažíme přistupovat k objektu předaného jinému vlastníkovi:

error[E0382]: use of moved value: `c.real`
  --> test.rs:25:20
   |
24 |     drop(c);
   |          - value moved here
25 |     println!("{}", c.real);
   |                    ^^^^^^ value used here after move
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:25:5: 25:28 note: in this expansion of println! (defined in <std macros>)
   |
   = note: move occurs because `c` has type `Box<Complex>`, which does not implement the `Copy` trait
 
error: aborting due to previous error

Poznámka2: do drop() lze předat i referenci, potom se ovšem destruktor nezavolá, neboť vlastník se předáním reference nezmění. Ostatně si to můžete velmi snadno vyzkoušet:

fn fn2() {
    println!("fn2 begin");
    let c = Box::new(Complex::new(2.0, 2.0));
    drop(&c);
    println!("{}", c.real);
    println!("fn2 end");
}

3. Alokace objektů na haldě s počítáním referencí

Prozatím známe pouze jeden způsob alokace objektů na haldě, a to konkrétně použitím objektu typu Box. Připomeňme si, že po zápisu:

let c = Box::new(Complex::new(2.0, 2.0));

se na haldě (heap) alokuje místo pro datovou strukturu představující komplexní číslo. Adresa tohoto místa je uložena do objektu typu Box, kde je zapouzdřena, protože nás její konkrétní hodnota nezajímá, a tento objekt je následně vrácen a navázán na proměnnou c. Ve chvíli, kdy skončí viditelnost proměnné c, dojde i k uvolnění objektu z haldy. V průběhu tohoto procesu se zavolá destruktor. Samozřejmě je možné hodnotu z proměnné c přesunout (sémantika „move“) do jiné proměnné, další funkce atd.; vše je hlídáno překladačem, který v každém okamžiku ví, kdy se má objekt uvolnit.

Ovšem u mnoha programů potřebujeme zajistit odlišné chování. Konkrétně stále vyžadujeme vytvoření objektu na haldě, ovšem takovým způsobem, aby jsme se na něj mohli odkázat z více míst v programu (tedy aby byl objekt sdílený). V takovém případě nám chování objektů typu Box přestane vyhovovat a musíme se poohlédnout po jiném způsobu alokace. V programovacím jazyce Rust lze použít objekt typu Rc, kde „RC“ je odvozeno od výrazu „reference count(er)“. Interně je skutečně v Rc uložen atribut nesoucí informaci o počtu referencí existujících na objekt uložený na haldě (dokonce existuje i metoda vracející hodnotu tohoto atributu, tato metoda je však označena jako deprecated). Nová reference se získá zavoláním funkce Rc.clone(). Toto volání navíc zvýší počitadlo referencí o jedničku. Počitadlo je snižováno ve chvíli, kdy zanikne viditelnost proměnné, do něhož se reference uložila (stejně jako u Box). Ve chvíli, kdy počitadlo klesne k nule, je referencovaný objekt z haldy odstraněn a přitom se samozřejmě zavolá jeho destruktor.

Poznámka: pokud nepoužijeme Rc.clone(), nebude mezi chováním Rc a Box znatelný rozdíl.

Poznámka2: pokud se nějaký objekt má sdílet mezi větším množstvím vláken, musí se namísto Rc použít Arc s atomickou modifikací hodnoty počitadla referencí. Příklad si ukážeme příště.

4. Demonstrační příklad – alokace struktury představující komplexní číslo

Podívejme se na to, co se stane ve chvíli, kdy nahradíme alokaci komplexního čísla na haldě s použitím Box za alokaci s použitím Rc. V následujícím příkladu je přidán první řádek (use) a volání Box::new() bylo nahrazeno za Rc::new():

use std::rc::Rc;
 
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 = Rc::new(Complex::new(2.0, 2.0));
    println!("fn2 end");
}
 
fn fn1() {
    println!("fn1 begin");
    let c = Rc::new(Complex::new(1.0, 1.0));
    fn2();
    println!("fn1 end");
}
 
fn main() {
    println!("main begin");
    let c = Rc::new(Complex::new(0.0, 0.0));
    fn1();
    println!("main end");
}

Po překladu a spuštění získáme na standardním výstupu následující řádky, které naznačují, že se chování Rc v tomto případě nijak neliší od chování Box:

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

5. Vytvoření většího množství referencí na jediný objekt alokovaný na haldě

Nyní se zaměřme na tu důležitou část alokace na haldě – na vlastní počítání referencí. V následujícím zdrojovém kódu jsou na haldě postupně alokována tři komplexní čísla, jejichž oblast životnosti je omezena viditelností proměnných navázaných do objekty typu Rc. Opět zde tedy není většího rozdílu oproti použití Box:

use std::rc::Rc;
 
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);
    }
}
 
fn main() {
    println!("main begin");
    let c = Rc::new(Complex::new(0.0, 0.0));
    c.print();
    {
        println!("inner block begin");
        let c2 = Rc::new(Complex::new(0.0, 0.0));
        c2.print();
        {
            println!("inmost block begin");
            let c3 = Rc::new(Complex::new(0.0, 0.0));
            c3.print();
            println!("inmost block end");
        }
        println!("inner block end");
    }
    println!("main end");
}

Podle zpráv vypisovaných na standardní výstup je patrné, že se skutečně alokovala tři komplexní čísla a dealokace probíhala v opačném pořadí (stačí změnit hodnoty reálné či imaginární složky):

main begin
Constructing complex number: 0+0i
complex number: 0+0i
inner block begin
Constructing complex number: 0+0i
complex number: 0+0i
inmost block begin
Constructing complex number: 0+0i
complex number: 0+0i
inmost block end
Dropping complex number: 0+0i
inner block end
Dropping complex number: 0+0i
main end
Dropping complex number: 0+0i

Příklad však můžeme snadno změnit tak, aby všechny tři lokální proměnné c, c2 a c3 obsahovaly Rc s referencí na jediné komplexní číslo. Zde již musíme použít Rc.clone(), nestačí pouhé přiřazení:

use std::rc::Rc;
 
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);
    }
}
 
fn main() {
    println!("main begin");
    let c = Rc::new(Complex::new(0.0, 0.0));
    c.print();
    {
        println!("inner block begin");
        let c2 = c.clone();
        c2.print();
        {
            println!("inmost block begin");
            let c3 = c.clone();
            c3.print();
            println!("inmost block end");
        }
        println!("inner block end");
    }
    println!("main end");
}

Chování programu se zásadním způsobem změní, protože se skutečně vytvoří jediné komplexní číslo, jehož destruktor je zavolán až ve chvíli, kdy skončí oblast viditelnosti proměnné c (tím se totiž sníží hodnota počitadla referencí na nulu):

main begin
Constructing complex number: 0+0i
complex number: 0+0i
// nyní je počitadlo referencí nastaveno na 1
inner block begin
// zvýšení počitadla referencí na 2
complex number: 0+0i
inmost block begin
// zvýšení počitadla referencí na 3
complex number: 0+0i
inmost block end
inner block end
main end
// proměnná c není viditelná → počitadlo kleslo na 0 → proběhne dealokace
Dropping complex number: 0+0i

6. Mutace objektu alokovaného na haldě, na nějž existuje větší množství referencí

Zajímavé bude zjistit, co se stane ve chvíli, kdy změníme objekt na haldě a přitom na tento objekt bude existovat větší množství referencí. Existují dva způsoby mutace objektu, přičemž první způsob používá metodu Rc::get_mut() (ovšem jen ve chvíli, kdy existuje jen jedna reference) a druhý způsob používá metodu Rc::make_mut(), která v případě potřeby objekt naklonuje („clone on write“):

*Rc::get_mut(&mut c).unwrap() = Complex::new(100., 100.);

Způsob změny objektu předtím, než je na něj vytvořeno větší množství referencí, lze implementovat takto:

use std::rc::Rc;
 
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);
    }
}
 
fn main() {
    println!("main begin");
    let mut c = Rc::new(Complex::new(0.0, 0.0));
    print!("original value: ");
    c.print();
    *Rc::get_mut(&mut c).unwrap() = Complex::new(100., 100.);
    print!("new value: ");
    c.print();
    {
        println!("inner block begin");
        let c2 = c.clone();
        c2.print();
        {
            println!("inmost block begin");
            let c3 = c.clone();
            c3.print();
            println!("inmost block end");
        }
        println!("inner block end");
    }
    println!("main end");
}

Změna se projeví dealokací původního komplexního čísla a alokací (+ konstrukcí) čísla nového:

main begin
Constructing complex number: 0+0i
original value: complex number: 0+0i
Constructing complex number: 100+100i
Dropping complex number: 0+0i
new value: complex number: 100+100i
inner block begin
complex number: 100+100i
inmost block begin
complex number: 100+100i
inmost block end
inner block end
main end
Dropping complex number: 100+100i

7. Sémantika „move“ a referencované objekty na haldě

V předchozím textu jsme si řekli, že pouhé předání objektu Rc odpovídá sémantice „move“, Jinými slovy: předáním objektu či jeho přiřazením do jiné proměnné dojde i ke změně vlastníka. To si můžeme ukázat na příkladu, v němž se pokusíme dvakrát zavolat funkci f2() a přitom jí předat stejný objekt (obsahující referenci na komplexní číslo uložené na haldě + počitadlo referencí):

fn f2(c:Rc<Complex>) {
    c.print();
}

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

use std::rc::Rc;
 
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);
    }
}
 
fn f1() -> Rc<Complex> {
    Rc::new(Complex::new(0.0, 0.0))
}
 
fn f2(c:Rc<Complex>) {
    c.print();
}
 
fn main() {
    println!("main begin");
    let c = f1();
    c.print();
    f2(c);
    f2(c);
}

Při překladu je změna vlastníka detekována, podobně jako je tomu i u dalších datových typů (Rc tedy není nijak výjimečný):

error[E0382]: use of moved value: `c`
  --> 104_rc_move_semantic_no_clone.rs:40:8
   |
39 |     f2(c);
   |        - value moved here
40 |     f2(c);
   |        ^ value used here after move
   |
   = note: move occurs because `c` has type `std::rc::Rc<Complex>`, which does not implement the `Copy` trait
 
error: aborting due to previous error

8. Sémantika „move“ a použití Rc::clone()

Předchozí příklad lze velmi snadno upravit takovým způsobem, aby pracoval podle očekávání. Postačuje naklonovat Rc, což interně znamená, že se zvýší počitadlo referencí a vrátí se Rc s referencí na to samé komplexní číslo. Uvolnění tohoto čísla z paměti bude provedeno ve chvíli, kdy bude počitadlo referencí sníženo na nulu:

let c = f1();
c.print();
f2(c.clone());
f2(c.clone());

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

use std::rc::Rc;
 
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);
    }
}
 
fn f1() -> Rc<Complex> {
    Rc::new(Complex::new(0.0, 0.0))
}
 
fn f2(c:Rc<Complex>) {
    c.print();
}
 
fn main() {
    println!("main begin");
    let c = f1();
    c.print();
    f2(c.clone());
    f2(c.clone());
}

Překlad takto upraveného příkladu proběhne korektně, zajímavé však bude sledovat chování programu v době běhu:

main begin
Constructing complex number: 0+0i
complex number: 0+0i
complex number: 0+0i
complex number: 0+0i
end of main
Dropping complex number: 0+0i

Vidíme, že komplexní číslo bylo vytvořeno (zkonstruováno) jen jedenkrát, uvolněno z paměti bylo až za koncem funkce main.

9. Reference na objekt vlastněná jiným objektem

Reálnému světu bude bližší následující příklad, v němž je deklarována datová struktura nazvaná ComplexNumberOwner. Tato struktura má dva prvky – celočíselný identifikátor a Rc s referencí na komplexní číslo alokované na haldě:

struct ComplexNumberOwner {
    id: i32,
    value: Rc<Complex>
}

Pokud vytvoříme větší množství těchto struktur (pole, vektor), lze mezi nimi v případě potřeby komplexní čísla sdílet. V následujícím kódu budou první tři struktury ComplexNumberOwner „vlastnit“ stejné komplexní číslo:

let c1 = Rc::new(Complex::new(1.0, 1.0));
let c2 = Rc::new(Complex::new(2.0, 2.0));
 
let owner1 = ComplexNumberOwner{id:1, value: c1.clone()};
let owner2 = ComplexNumberOwner{id:2, value: c1.clone()};
let owner3 = ComplexNumberOwner{id:3, value: c1.clone()};
 
let owner4 = ComplexNumberOwner{id:4, value: c2.clone()};

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

use std::rc::Rc;
 
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: Rc<Complex>
}
 
impl ComplexNumberOwner {
    fn print(&self) {
        println!("owner: number #{} with value {}+{}i", self.id, self.value.real, self.value.imag);
    }
}
 
fn main() {
    let c1 = Rc::new(Complex::new(1.0, 1.0));
    let c2 = Rc::new(Complex::new(2.0, 2.0));
 
    let owner1 = ComplexNumberOwner{id:1, value: c1.clone()};
    let owner2 = ComplexNumberOwner{id:2, value: c1.clone()};
    let owner3 = ComplexNumberOwner{id:3, value: c1.clone()};
    let owner4 = ComplexNumberOwner{id:4, value: c2.clone()};
 
    owner1.print();
    owner2.print();
    owner3.print();
    owner4.print();
}

Chování příkladu po spuštění:

Constructing complex number: 1+1i
Constructing complex number: 2+2i
owner: number #1 with value 1+1i
owner: number #2 with value 1+1i
owner: number #3 with value 1+1i
owner: number #4 with value 2+2i
Dropping complex number: 2+2i
Dropping complex number: 1+1i

Vidíme, že první tři objekty typu ComplexNumberOwner skutečně vlastní stejné komplexní číslo, které je následně po opuštění funkce main korektně uvolněno z paměti.

10. Poznámka na závěr: použití datového typu Self

Na závěr si uvedeme poměrně užitečný trik, který souvisí s datovým typem Self (musí se psát s velkým písmenem na začátku). Použití Self si nejlépe ukážeme na příkladu. Prozatím jsme ve všech demonstračních příkladech s komplexními čísly používali tento konstruktor, resp. přesněji řečeno metodu, která se jako konstruktor chovala:

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

Ve skutečnosti však překladač ví, v jakém kontextu je metoda deklarována, protože je umístěna do bloku impl Complex. V tomto kontextu je možné použít datový typ Self, což je vlastně pouze syntaktický cukr umožňující, aby se nemusel pořád opisovat datový typ (většinou není tvořen jen identifikátorem). Konstruktor lze tedy napsat i následovně:

fn new(real: f32, imag: f32) -> Self {
    Complex{real:real, imag:imag}
}

Pozor však na to, že následující zápis již není možný, protože Self zastupuje skutečný datový typ jen při deklaraci parametrů funkcí/metod a jejích návratových typů:

fn new(real: f32, imag: f32) -> Self {
    Self{real:real, imag:imag}
}

Navíc platí, že pokud je první parametr metody pojmenovaný self, je překladačem chápán jako self:Self. Totéž platí i pro další modifikace, tedy: &self znamená self: &Self a &mut self znamená self: &mut Self. To jsme ostatně již bez dalšího vysvětlení použili v deklaraci destruktoru:

bitcoin_skoleni

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

Podívejme se na úpravu příkladu s komplexními čísly, kde je tento syntaktický cukr použit:

struct Complex {
    real: f32,
    imag: f32,
}
 
impl Complex {
 
    fn new(real: f32, imag: f32) -> Self {
        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 c = Box::new(Complex::new(1.0, 2.0));
}

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

12. Odkazy na Internetu

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