Rust: struktury, n-tice a vlastnictví objektů

1. 12. 2016
Doba čtení: 19 minut

Sdílet

 Autor: Rust project
Čtvrtá část seriálu o jazyce Rust je věnována popisu struktur (struct) a n-tic (tuple). Následně se budeme zabývat problematikou vlastnictví objektů a s ní souvisejících sémantik „move“ a „copy“.

Obsah

1. Struktury, přístup k prvkům struktur

2. Pořadí inicializace prvků struktury

3. Měnitelné a neměnitelné struktury

4. Částečná kopie prvků struktury do struktury jiné

5. N-tice (tuple)

6. Destructuring n-tice a struktury

7. Deklarace typu prvků n-tice

8. Speciální případ – n-tice s jediným prvkem

9. Předání struktury do funkce

10. Předání struktury do funkce přes referenci

11. Změna obsahu struktury ve funkci

12. Vlastnictví objektů a sémantika „move“

13. Sémantika „copy“

14. Mutace naklonované struktury

15. Pravidlo pro použití rysů copy a clone

16. Odkazy na Internetu

1. Struktury, přístup k prvkům struktur

V programovacím jazyku Rust je možné deklarovat takzvané struktury (struct), které jsou někdy známé i pod jménem záznamy (records). Struktury obsahují datové položky, které jsou jednoznačně pojmenované (v rámci jmenného prostoru struktury) a je pro ně definován i datový typ. Zachováno je i pořadí položek ve struktuře, což může být v některých případech důležité. Deklarací struktury vzniká nový uživatelský datový typ. Zkusme si například vytvořit strukturu představující komplexní číslo, v němž je reálná a imaginární složka tvořena hodnotami typu f32 (32bitové číslo s plovoucí řádovou čárkou):

struct Complex {
    real: f32,
    imag: f32,
}

Poté můžeme velmi snadno vytvořit hodnotu typu Complex a přiřadit ji do proměnné c1. Následně je možné přistupovat k položkám struktury pomocí zápisu proměnná.položka, což se nijak neliší od dalších programovacích jazyků:

fn main() {
    let c1 = Complex{real:0.0, imag:0.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

Samozřejmě je možné explicitně specifikovat typ proměnné (vyznačená část kódu), jinak si typ překladač odvodí automaticky:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1:Complex = Complex{real:0.0, imag:0.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

2. Pořadí inicializace prvků struktury

Vzhledem k tomu, že se při inicializaci prvků struktury musí jednotlivé prvky explicitně pojmenovat, nezáleží na jejich pořadí. V následujícím příkladu se nejprve inicializuje imaginární položka a poté položka reálná, výsledkem ale bude stejný objekt, jako v předchozím příkladu:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    // nezalezi na poradi inicializace prvku
    let c1 = Complex{imag:0.0, real:0.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

3. Měnitelné a neměnitelné struktury

Podobně, jako je tomu i u dalších typů proměnných, jsou i struktury implicitně neměnitelné. Pokud se pokusíme o změnu prvků struktury (tedy o zápis), je tato operace odhalena už při překladu:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1 = Complex{real:0.0, imag:0.0};
    c1.real = 10.0;
    c1.imag = 20.0;
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

Překladač by měl vypsat přibližně toto chybové hlášení, z něhož je jasně patrné, že se pokoušíme přepsat neměnitelnou (immutable) hodnotu:

error: cannot assign to immutable field `c1.real`
 --> 51_mutable_struct1_err.rs:8:5
  |
8 |     c1.real = 10.0;
  |     ^^^^^^^^^^^^^^
 
error: cannot assign to immutable field `c1.imag`
 --> 51_mutable_struct1_err.rs:9:5
  |
9 |     c1.imag = 20.0;
  |     ^^^^^^^^^^^^^^
 
error: aborting due to 2 previous errors

Pokud se programátoři setkají s předchozím chybovým hlášením, mohou se pokusit o úpravu deklarace struktury, konkrétně o přidání modifikátoru mut k datovým položkám (k této úpravě mohou „lákat“ i chybová hlášení zobrazená před tímto odstavcem). Zkusme si tedy příklad upravit:

struct Complex {
    mut real: f32,
    mut imag: f32,
}
 
fn main() {
    let c1 = Complex{real:0.0, imag:0.0};
    c1.real = 10.0;
    c1.imag = 20.0;
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

Tato úprava ovšem odporuje syntaxi programovacího jazyka Rust a je špatná i sémanticky, protože rys měnitelnosti či neměnitelnosti (mutability/immutability) se váže nikoli k datovému typu, ale k proměnné. Ostatně ani překladač nebude příliš spokojený:

error: expected identifier, found keyword `mut`
 --> 52_mutable_struct2_err.rs:2:5
  |
2 |     mut real: f32,
  |     ^^^
 
error: expected `:`, found `real`
 --> 52_mutable_struct2_err.rs:2:9
  |
2 |     mut real: f32,
  |         ^^^^
 
error: aborting due to previous error

Korektní vytvoření měnitelné proměnné typu struktura vypadá následovně:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let mut c1 = Complex{real:0.0, imag:0.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
    c1.real = 10.0;
    c1.imag = 20.0;
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

Samozřejmě je možné bez problémů vytvořit jak měnitelnou, tak i neměnitelnou strukturu (což je jeden z důvodů, proč je vlastnost měnitelnosti/neměnitelnosti vztažená k proměnným):

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let mut c1 = Complex{real:0.0, imag:0.0};
    let c2 = Complex{real:2.0, imag:4.0};
    println!("complex number #1: {}+{}i", c1.real, c1.imag);
    println!("complex number #2: {}+{}i", c2.real, c2.imag);
    c1.real = 10.0;
    c1.imag = 20.0;
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

4. Částečná kopie prvků struktury do struktury jiné

Programovací jazyk Rust podporuje i vytvoření nové struktury s tím, že se hodnoty jednotlivých položek zkopírují ze struktury jiné. Syntaxe zápisu je patrná z následujícího příkladu. Proměnná c2 je navázána na komplexní číslo, jehož reálná složka je určena explicitně a složka imaginární je zkopírována. Proměnná c3 vznikla kompletní kopií c2:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1 = Complex{real:0.0, imag:0.0};
    let c2 = Complex{real:2.0, ..c1};
    let c3 = Complex{..c2};
    println!("complex number 1: {}+{}i", c1.real, c1.imag);
    println!("complex number 2: {}+{}i", c2.real, c2.imag);
    println!("complex number 3: {}+{}i", c3.real, c3.imag);
}

O tom, že se provede kopie položek, se snadno přesvědčíme:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let mut c1 = Complex{real:0.0, imag:0.0};
    let mut c2 = Complex{real:2.0, ..c1};
    let mut c3 = Complex{..c2};
    println!("complex number 1: {}+{}i", c1.real, c1.imag);
    println!("complex number 2: {}+{}i", c2.real, c2.imag);
    println!("complex number 3: {}+{}i", c3.real, c3.imag);
    c1.imag = 100.0;
    println!("complex number 1: {}+{}i", c1.real, c1.imag);
    println!("complex number 2: {}+{}i", c2.real, c2.imag);
    println!("complex number 3: {}+{}i", c3.real, c3.imag);
}

Po spuštění:

complex number 1: 0+0i
complex number 2: 2+0i
complex number 3: 2+0i
complex number 1: 0+100i
complex number 2: 2+0i
complex number 3: 2+0i

Změna imaginární složky komplexního čísla c1 nemá vliv na čísla c2 a c3.

5. N-tice (tuple)

Jedním ze základních strukturovaných datových typů podporovaných jazykem Rust jsou n-tice (tuple). Prvky n-tice jsou uspořádány (v pořadí jejich zápisu) a nemusí být stejného datového typu. Konstruktor pro vytvoření n-tice je podobný zápisu známému z Pythonu – prvky n-tice se zapisují do kulatých závorek a oddělují se čárkami (s jednou výjimkou specifikovanou níže). Zajímavý je způsob přístupu k prvkům n-tice, protože se používá zápis n_tice.index (s tečkou, nikoli s hranatými závorkami), přičemž indexy začínají nulou, stejně jako u polí. Podívejme se na jednoduchý příklad:

fn main() {
    let c1 = (1.0, 2.0);
    let c2 = (0.0, 0.0);
    println!("complex number 1: {}+{}i", c1.0, c1.1);
    println!("complex number 2: {}+{}i", c2.0, c2.1);
}

N-tice lze samozřejmě předávat jako parametry funkcí a funkce mohou n-tice vracet jako svoji návratovou hodnotu. Jak se dozvíme v dalším textu, podporují n-tice sémantiku Copy a rys Clone (to ovšem jen za předpokladu, že ji podporují i všechny prvky n-tice, splněno je to pro primitivní datové typy atd.).

Samozřejmě jsou podporovány i n-tice obsahující další n-tice. Při přístupu k prvkům takové datové struktury je nutné si dát pozor na správné uzávorkování (nestačí jen napsat x.1.3.1):

fn main() {
    let x = (1.0, (2, 3, 4, (true, false)));
    println!("{}", x.0);
    println!("{}", (x.1).1);
    println!("{}", ((x.1).3).1);
}

6. Destructuring n-tice a struktury

V některých programovacích jazycích se setkáme s termínem destructuring, kterým se většinou označuje „rozložení“ nějaké datové struktury na položek a přiřazení těchto položek do proměnných (viz například Python či Clojure). V programovacím jazyce Rust podobnou funkcionalitu taktéž nalezneme; je součástí obecnějšího konceptu nazývaného „pattern matching“, o němž jsme se již částečně zmínili v úvodních článcích. Podívejme se, jak je možné nejprve vytvořit dvouprvkovou n-tici, uložit ji do proměnné c1 a následně tuto n-tici rozložit na jednotlivé položky, které se uloží do proměnných real a imag (Pythonisti by neměli zapomenout na závorky):

fn main() {
    let c1 = (1.0, 2.0);
 
    let (real, imag) = c1;
 
    println!("complex number: {}+{}i", real, imag);
}

Destructuring pro struktury se zapisuje následovně (opět se jedná o součást konceptu pattern matchingu). Povšimněte si, že nové proměnné se jmenují stejně, jako položky struktury:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1 = Complex{imag:0.0, real:0.0};
    let Complex{real,imag} = c1;
    println!("complex number: {}+{}i", real, imag);
}

Popř. je možné explicitně zapsat nová jména pro proměnné:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1 = Complex{imag:0.0, real:0.0};
    let Complex{real:r,imag:i} = c1;
    println!("complex number: {}+{}i", r, i);
}

7. Deklarace typu prvků n-tice

Pokud je to nutné, lze explicitně deklarovat typy prvků n-tice. Většinou to sice není zapotřebí, protože si typy dokáže odvodit samotný překladač, ale někdy může být vhodné se vyjádřit explicitně. V tomto případě vypadá zápis následovně:

fn main() {
    let c1:(f32,f32) = (1.0, 2.0);
    let c2:(f32,f32) = (0.0, 0.0);
 
    println!("complex number 1: {}+{}i", c1.0, c1.1);
    println!("complex number 2: {}+{}i", c2.0, c2.1);
}

Připomeňme si, že podobně (tedy za dvojtečkou zapsanou za jménem proměnné) jsme zapisovali i specifikaci jiných datových typů, takže se v případě n-tic vlastně nejedná o nic nového:

let mut i:i8 = 0;

8. Speciální případ – n-tice s jediným prvkem

Speciálním případem, který dobře znají například programátoři používající programovací jazyk Python, je n-tice obsahující jediný prvek. Taková n-tice není nijak zajímavá ze sémantického hlediska, ovšem z hlediska syntaktického ano, protože prvky n-tice se zapisují do kulatých závorek, které mají v programovacím jazyku Rust i další významy. Aby se skutečně zajistilo, že překladač pochopí zápis jednoprvkové n-tice, je nutné za tímto prvkem zapsat čárku:

fn main() {
    let x = (1.0);
    let y = (1.0,);
 
    println!("x {}", x);
    println!("y {}", y.0);
}

V tomto příkladu je do proměnné x přiřazena hodnota 10 (vypočtená výrazem v závorkách) a do proměnné y je přiřazena jednoprvková n-tice.

9. Předání struktury do funkce

Struktury je možné (zdánlivě bez problémů) předat nějaké funkci, což by nemělo být příliš překvapivé, protože po deklaraci struktury se jedná o plnohodnotný datový typ. V následujícím příkladu je ukázáno, jakým způsobem se předání může provést:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
    print_complex(c1);
}

Tento příklad bude fungovat bez problémů, takže by se mohlo zdát, že předávání parametrů v Rustu pracuje stejně jako například v céčku. Ve skutečnosti je však sémantika zásadně odlišná, protože pokud je nějaký objekt předán funkci (nebo jen přiřazen k jiné proměnné), stává se tato funkce vlastníkem objektu a současně původní vlastník objektu (ve funkci main) toto vlastnictví ztrácí. Co to znamená v praxi ukazuje další příklad, v němž zavoláme funkci print_complex(), která se stane vlastníkem objektu navázaného na proměnnou c1. Z tohoto důvodu poslední příkaz ve funkci main nepůjde přeložit:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
    print_complex(c1);
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

Z chybového hlášení jsem pro přehlednost odstranil nerelevantní řádky týkající se expanze makra println!. Význam některých termínů („move“, „Copy trait“) bude vysvětlen v navazujících kapitolách, konkrétně v kapitole 12 a 13:

error[E0382]: use of moved value: `c1.real`
  --> 60_passing_struct_err.rs:14:40
   |
13 |     print_complex(c1);
   |                   -- value moved here
14 |     println!("complex number: {}+{}i", c1.real, c1.imag);
   |                                        ^^^^^^^ value used here after move
60_passing_struct_err.rs:14:5: 14:58 note: in this expansion of println! (defined in )
   |
   = note: move occurs because `c1` has type `Complex`, which does not implement the `Copy` trait
 
error[E0382]: use of moved value: `c1.imag`
  --> 60_passing_struct_err.rs:14:49
   |
13 |     print_complex(c1);
   |                   -- value moved here
14 |     println!("complex number: {}+{}i", c1.real, c1.imag);
   |                                                 ^^^^^^^ value used here after move
60_passing_struct_err.rs:14:5: 14:58 note: in this expansion of println! (defined in )
   |
   = note: move occurs because `c1` has type `Complex`, which does not implement the `Copy` trait
 
error: aborting due to 2 previous errors

10. Předání struktury do funkce přes referenci

Předchozí problém je možné obejít předáním komplexního čísla referencí, což je téma, kterému jsme se věnovali minule. Povšimněte si, že se ve funkci print_complex stále přistupuje k prvkům struktury s využitím operátoru tečky a nikoli přes operátor ->, který čtenáři pravděpodobně znají z céčka a C++:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:&Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    println!("complex number: {}+{}i", c1.real, c1.imag);
    print_complex(&c1);
    println!("complex number: {}+{}i", c1.real, c1.imag);
}

11. Změna obsahu struktury ve funkci

Pokud budete potřebovat změnit obsah struktury ve funkci, musí se samozřejmě použít modifikátor mut, a to jak při uložení struktury do proměnné, tak i v deklaraci funkce. Překlad následujícího příkladu skončí s chybou, protože se pokoušíme změnit položku neměnitelné hodnoty:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:&Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn add_real(c:&Complex,real:f32) {
    c.real += real;
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    print_complex(&c1);
    add_real(&c1, 41.);
    print_complex(&c1);
}
error: cannot assign to immutable field `c.real`
  --> test.rs:11:5
   |
11 |     c.real += real;
   |     ^^^^^^^^^^^^^^
 
error: aborting due to previous error

Stačí však malá úprava spočívající v použití modifikátoru mut:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:&Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn add_real(c:&mut Complex,real:f32) {
    c.real += real;
}
 
fn main() {
    let mut c1 = Complex{real:1.0, imag:2.0};
    print_complex(&c1);
    add_real(&mut c1, 41.);
    print_complex(&c1);
}

Povšimněte si, že překladači nelze „vnutit“, aby neměnitelnou strukturu začal považovat za strukturu měnitelnou:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:&Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn add_real(c:&mut Complex,real:f32) {
    c.real += real;
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    print_complex(&c1);
    add_real(&mut c1, 41.);
    print_complex(&c1);
}

Překlad tohoto příkladu skončí s následující chybou:

error: cannot borrow immutable local variable `c1` as mutable
  --> test.rs:17:19
   |
15 |     let c1 = Complex{real:1.0, imag:2.0};
   |         -- use `mut c1` here to make mutable
16 |     print_complex(&c1);
17 |     add_real(&mut c1, 41.);
   |                   ^^ cannot borrow mutably
 
error: aborting due to previous error

12. Vlastnictví objektů a sémantika „move“

Vraťme se ještě k problematice předávání a vypůjčování vlastnictví objektů, s nímž jsme se již setkali v deváté kapitole. Zkusme si vytvořit jednodušší příklad, v němž vytvoříme komplexní číslo, přiřadíme ho k proměnné c1, posléze ho přiřadíme k proměnné c2 a následně proměnnou c2 použijeme při volání funkce print_complex:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    let c2 = c1;
    print_complex(c2);
}

Tento příklad bude fungovat, protože se vlastnictví komplexního čísla postupně předává od c1 přes c2 až do funkce print_complex. Ovšem již při nepatrné změně dojde k chybě při překladu, protože se pokoušíme přistoupit ke komplexnímu číslu přes c1, které ho již nevlastní:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    let c2 = c1;
    print_complex(c1);
}

V tomto případě překladač zobrazí následující chybové hlášení u posledního programového řádku:

error[E0382]: use of moved value: `c1`
  --> test.rs:13:19
   |
12 |     let c2 = c1;
   |         -- value moved here
13 |     print_complex(c1);
   |                   ^^ value used here after move
   |
   = note: move occurs because `c1` has type `Complex`, which does not implement the `Copy` trait
 
error: aborting due to previous error

Důležité je si uvědomit, proč vlastně překladač hlídá vlastnictví objektů. Předpokládejme, že namísto pouhých struktur pracujeme například s vektory. Ty jsou představovány objekty se dvěma částmi – jedna část obsahuje prvky vektoru, druhá část pak různé další atributy, například délku vektoru. Přiřazení typu let v1=vec![1,2,3]; let v2=v1; musí mj. zajistit, aby se při volání destruktorů (zde konkrétně na konci bloku) vektor z paměti uvolnil jen jedenkrát, popř. aby přiřazení provedla „hlubokou kopii“ (deep copy) objektu (tím pádem se destruktor zavolá pro každý objekt zvlášť, protože po provedení hluboké kopie/naklonování získáme dva samostatné objekty). V jazyce Rust je implicitně podporována takzvaná sémantika „move“ znamenající, že po přenesení vlastnictví již není možné původní proměnnou (či parametr) použít a tudíž se ani nemusíme starat o to, kdo a v jakém okamžiku bude volat destruktory.

13. Sémantika „copy“

Kromě sémantiky „move“ je možné (explicitně) použít takzvanou sémantiku „copy“, která určuje, že se namísto předání vlastnictví, například volané funkci, provede zkopírování hodnoty (ve skutečnosti se zde ponechávají možnosti pro optimalizace na úrovni LLVM, ale tím se prozatím nebudeme zabývat). Z pohledu programátora je zajištění takové funkcionality pro struktury jednoduché – musí pouze specifikovat, že jeho struktura má rys (trait) Copy a Clone:

#[derive(Copy, Clone)]
struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let c1 = Complex{real:1.0, imag:2.0};
    print_complex(c1);
    let c2 = c1;
    print_complex(c2);
}

Poznámka: nejsem si jistý tím, jestli se trait má skutečně překládat jako „rys“ či zda vůbec oficiální překlad existuje, ovšem další možnosti „atribut“ či „vlastnost“ už se používají v odlišném významu (pro překlad slov attribute a property). Prozatím můžeme trait pokládat za rozšířené rozhraní, které kromě hlaviček metod obsahuje i jejich těla, ale už nikoli stavové informace (podobně jako rozhraní v Javě 8, které ovšem nemá všechny vlastnosti traitů). To, že má nějaký typ či objekt určitý rys (trait) tedy není zajištěno přímým děděním.

Specifikací rysu Clone je specifikováno, že se má namísto předání vlastnictví vytvořit hluboká kopie objektu (zde tedy našeho komplexního čísla). Interně se hluboká kopie implementuje ve funkci .clone(). Specifikace rysu Copy určuje již zmíněnou sémantiku „copy“. Je nutné dávat pozor na to, že rys Copy vyžaduje i implementaci rysu Clone (naopak to však neplatí). To znamená, že můžeme mít strukturu, která je klonovatelná, ale nepoužije se pro ni sémantika „copy“.

Poznámka: základní datové typy implementují rys Clone, samotné přiřazení je zde navíc jednodušší, protože postačuje mělká kopie.

14. Mutace naklonované struktury

Pokud je objekt, resp. v našem případě datová struktura naklonován, je umístěn do odlišné oblasti operační paměti, než původní objekt. To je sice pro neměnitelné (immutable) datové struktury jen nepatrná změna, ovšem v následujícím demonstračním příkladu je ukázáno, co se stane, když použijeme měnitelné datové struktury. První struktura je zkonstruována a přiřazena k proměnné c1 s modifikátorem mut, což umožní změnu datových položek. Posléze je provedena hluboká kopie a přiřazení této kopie k proměnné c2, taktéž s modifikátorem mut:

#[derive(Copy, Clone)]
struct Complex {
    real: f32,
    imag: f32,
}
 
fn print_complex(c:Complex) {
    println!("complex number: {}+{}i", c.real, c.imag);
}
 
fn main() {
    let mut c1 = Complex{real:1.0, imag:2.0};
    print_complex(c1);
    let mut c2 = c1;
    c1.real = 0.;
    c2.real = 1000.;
    print_complex(c1);
    print_complex(c2);
}

Po spuštění příkladu je patrné, že změna datové položky jedné struktury nemá žádný vliv na stejně pojmenovanou datovou položku struktury druhé, tj. struktury začaly být na sobě nezávislé (to je přesně ta sémantika, kterou jsme vyžadovali):

complex number: 1+2i
complex number: 0+2i
complex number: 1000+2i

15. Pravidlo pro použití rysů copy a clone

Zkusme si nyní změnit naši datovou strukturu Complex takovým způsobem, že její prvky nebudou typu f32 (tedy 32bitové číslo s plovoucí řádovou čárkou), ale bude se jednat o další uživatelsky definovanou strukturu nazvanou Coordinate. Tato struktura obsahuje jen jedinou položku, která je typu f32. Upravený program vypadá následovně:

struct Coordinate {
    value: f32
}
 
#[derive(Copy, Clone)]
struct Complex {
    real: Coordinate,
    imag: Coordinate,
}
 
fn main() {
}

Pokus o překlad se v tomto případě nepovede:

error[E0204]: the trait `Copy` may not be implemented for this type
 --> 67_copy_trait_impossible.rs:5:10
  |
5 | #[derive(Copy, Clone)]
  |          ^^^^ field `real` does not implement `Copy`
67_copy_trait_impossible.rs:5:10: 5:14 note: in this expansion of #[derive(Copy)] (defined in 67_copy_trait_impossible.rs)
 
error: aborting due to previous error

Je tomu tak z toho prostého důvodu, protože datová struktura Complex je sice klonovatelná, ale její položky už nikoli, což je jeden z předpokladů úspěšného vytvoření úplné kopie objektu. Náprava je jednoduchá – vlastnosti Copy a Clone deklarujeme i pro strukturu Coordinate:

ict ve školství 24

#[derive(Copy, Clone)]
struct Coordinate {
    value: f32
}
 
#[derive(Copy, Clone)]
struct Complex {
    real: Coordinate,
    imag: Coordinate,
}
 
fn main() {
}

Důvod, proč překlad původní datové struktury:

#[derive(Copy, Clone)]
struct Complex {
    real: f32,
    imag: f32,
}

proběhl bez chybových hlášení spočívá v tom, že základní datové typy, včetně f32, rys Clone implementují.

16. Odkazy na Internetu

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