Programovací jazyk Rust: použití FFI při předávání struktur

15. 6. 2017
Doba čtení: 24 minut

Sdílet

 Autor: Rust project
Při popisu FFI jsme si již řekli, jak je možné do nativní céčkové knihovny předávat primitivní datové typy, řetězce i pole. Ještě nám však zbývá si popsat předávání záznamů čili struktur, jak hodnotou, tak i odkazem.

Obsah

1. Předávání struktur mezi Rustem a céčkovou knihovnou

2. Vytvoření nového projektu

3. Céčková část příkladu – definice struktury a funkce akceptující tuto strukturu

4. Volání céčkové funkce z Rustu s předáním struktury

5. Překlad a spuštění prvního příkladu

6. Problematika zarovnání prvků ve struktuře a „výplní“ mezi prvky

7. Změna pořadí prvků struktury

8. Překlad a spuštění druhého příkladu

9. Export binární podoby struktury

10. Předání struktury odkazem (přes ukazatel)

11. Céčková část aplikace

12. Část aplikace naprogramovaná v Rustu

13. Ukazatel versus reference

14. Modifikace prvků struktury v nativním kódu

15. Rustovská část

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

17. Odkazy na Internetu

1. Předávání struktur mezi Rustem a céčkovou knihovnou

V programovacím jazyku C je možné používat primitivní numerické datové typy, ukazatele, homogenní datovou strukturu (neboli pole), unie, bitová pole a taktéž záznamy, které jsou v céčku pojmenovány struct, takže se jim běžně říká i struktury. Tyto složené datové typy jsou podporovány i Rustem, takže vzniká zcela logická otázka, do jaké míry je možné při komunikaci mezi céčkem a Rustem záznamy/struktury používat. Ve skutečnosti je tato operace podporována a je velmi často používána – záznamy je možné předávat jak hodnotou, tak i odkazem (neboli přes ukazatel). Dnes si ukážeme některá úskalí použití záznamů, protože při komunikaci mezi Rustem a C je samozřejmě nutné zaručit, že se budou používat shodné typy prvků, stejný způsob zarovnání prvků atd.

Poznámka: komunikace s využitím bitových polí a unií je složitější a setkáme se s ní méně často.

2. Vytvoření nového projektu

Podobně jako minule, i dnes budeme používat pro správu projektů (překlad, slinkování, spuštění, otestování) nástroj Cargo. Zkusme si nejprve vytvořit prázdný projekt spravovaný systémem Cargo. Projekt bude sloužit k vytvoření spustitelné binární aplikace, a proto musíme použít přepínač –bin:

$ cargo new --bin ffi_project1

Po spuštění předchozího příkazu získáme následující adresářovou strukturu projektu:

.
├── Cargo.toml
└── src
    └── main.rs

Do projektu přidáme soubor build.rs:

// balicek gcc zajisti preklad a vytvoreni knihovny za nas
extern crate gcc;
 
fn main() {
    // preklad a vytvoreni knihovny
    gcc::compile_library("libffi3.a", >["src/ffi3.c"]);
}

Projektový soubor nepatrně upravíme:

[package]
name = "ffi_project3"
version = "0.1.0"
authors = ["Pavel Tisnovsky <ptisnovs@redhat.com>"]
build = "build.rs"
 
[dependencies]
 
[build-dependencies]
gcc = "0.3"

A přidáme (příkazem touch) prázdný zdrojový soubor psaný v céčku:

$ touch src/ffi3.c

Výsledkem by měla být tato adresářová struktura:

.
├── build.rs
├── Cargo.lock
├── Cargo.toml
└── src
    ├── ffi3.c
    └── main.rs

3. Céčková část příkladu – definice struktury a funkce akceptující tuto strukturu

Podívejme se nejprve na céčkovou část celého projektu, protože zde by programátory nemělo prakticky nic překvapit :-). Nejprve definujeme strukturu s přesným popisem datových typů včetně jejich šířky, tj. nepoužíváme jen char nebo int, ale explicitně uint8_t, int32_t atd. (pokud použijete int apod., bude problematické takový projekt spustit na jiných platformách):

typedef struct {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    int32_t d;
    int32_t e;
    float   f;
} test_struct;

Následně je definována funkce, která po svém zavolání vypíše velikost předané struktury (ta je známa v době překladu) a její prvky. Povšimněte si, že této funkci skutečně předáváme celou strukturu, což nemusí být ve všech případech žádoucí chování (struktury mohou být poměrně rozsáhlé):

void print_struct(test_struct s)
{
    printf("sizeof(test_struct) = %lu bytes\n", sizeof(s));
    printf("a = %d\n", s.a);
    printf("b = %d\n", s.b);
    printf("c = %d\n", s.c);
    printf("d = %d\n", s.d);
    printf("e = %d\n", s.e);
    printf("f = %f\n", s.f);
}

Úplný zdrojový kód céčkové části vypadá takto:

#include <stdint.h>
#include <stdio.h>
 
typedef struct {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    int32_t d;
    int32_t e;
    float   f;
} test_struct;
 
void print_struct(test_struct s)
{
    printf("sizeof(test_struct) = %lu bytes\n", sizeof(s));
    printf("a = %d\n", s.a);
    printf("b = %d\n", s.b);
    printf("c = %d\n", s.c);
    printf("d = %d\n", s.d);
    printf("e = %d\n", s.e);
    printf("f = %f\n", s.f);
}

4. Volání céčkové funkce z Rustu s předáním struktury

V části naprogramované v Rustu je nutné nejprve deklarovat datovou strukturu takovým způsobem, aby přesně odpovídala céčkové části. Pro tento účel je nutné použít #[repr©], protože překladač Rustu musí uspořádat prvky struktury takovým způsobem, aby byla struktura bitově kompatibilní s céčkem (bitové obrazy struktury musí být naprosto shodné). Samozřejmě je také nutné dodržet pořadí prvků (na druhou stranu se může pojmenování prvků lišit):

#[repr(C)]
struct TestStruct {
    a: u8,
    b: u8,
    c: u8,
    d: i32,
    e: i32,
    f: f32
}

V další části kódu musíme provést deklaraci externí (nativní) funkce napsané v céčku. Povšimněte si, že hlavička funkce přesně odpovídá konvencím Rustu (typ parametru, návratová hodnota):

extern {
    fn print_struct(s:TestStruct) -> ();
}

Tuto funkci voláme opět v bloku unsafe:

let s : TestStruct = TestStruct{a:1, b:255, c:42, d:10000, e:-10000, f:3.14};
unsafe {
    print_struct(s);
}

V programu dále strukturu vytvoříme a následně vypíšeme její velikost, a to konkrétně funkcí mem::size_of(), která velikost zjistí z generického typu:

use std::mem;
 
let s : TestStruct = TestStruct{a:1, b:255, c:42, d:10000, e:-10000, f:3.14};
println!("sizeof on Rust side = {} bytes", mem::size_of::<TestStruct>());

Úplný zdrojový kód části psané v Rustu tedy bude vypadat následovně:

use std::mem;
 
#[repr(C)]
struct TestStruct {
    a: u8,
    b: u8,
    c: u8,
    d: i32,
    e: i32,
    f: f32
}
 
extern {
    fn print_struct(s:TestStruct) -> ();
}
 
fn main() {
    let s : TestStruct = TestStruct{a:1, b:255, c:42, d:10000, e:-10000, f:3.14};
    println!("sizeof on Rust side = {} bytes", mem::size_of::<TestStruct>());
    unsafe {
        print_struct(s);
    }
}

5. Překlad a spuštění příkladu

Vzhledem k tomu, že je celý projekt spravován nástrojem Cargo, je jeho překlad a slinkování jednoduché, což už ostatně známe z předchozích částí tohoto seriálu:

$ cargo build
   Compiling gcc v0.3.50
   Compiling ffi_project3 v0.1.0 (file:///home/tester/ffi_project3)
    Finished debug [unoptimized + debuginfo] target(s) in 3.42 secs

I pro spuštění projektu použijeme nástroj Cargo. Měl by se vypsat následující text:

$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/ffi_project3`
sizeof on Rust side = 16 bytes
sizeof(test_struct) = 16 bytes
a = 1
b = 255
c = 42
d = 10000
e = -10000
f = 3.140000

Poznámka: na architekturách odlišných od x86–64 se může lišit velikost struktury hlášené jak céčkovou částí, tak i částí psanou v Rustu. Stále by však měly být obě velikosti shodné (pokud nebudou shodné, budou se velmi pravděpodobně lišit i hodnoty přečtené v céčkové části).

6. Problematika zarovnání prvků ve struktuře a „výplní“ mezi prvky

Pokud jste se pečlivě podívali na výstup vypsaný předchozím projektem, asi jste si všimli, že datová struktura má hlášenou velikost 16 bajtů a nikoli pouze 15 bajtů, jak by se mohlo zdát při pouhém součtu velikostí jednotlivých prvků:

    a: u8,    1 bajt
    b: u8,    1 bajt
    c: u8,    1 bajt
    d: i32,   4 bajty
    e: i32,   4 bajty
    f: f32    4 bajty
celkem:      15 bajtů

Ve skutečnosti totiž na většině v současnosti používaných architektur mikroprocesorů (snad jen s výjimkou osmibitových mikrořadičů) mohou mezi prvky struktury vznikat volná místa, která sice zbytečně zabírají paměť, ovšem umožňují, aby se k prvkům struktury o velikosti slov přistupovalo mnohem rychleji, protože tyto prvky budou takzvaně zarovnány (aligned). To znamená, že prvky budou například umístěny na adresách dělitelných čtyřmi, osmi atd. (v závislosti na platformě a architektuře procesoru, například na x86 bude zarovnání většinou odpovídat šířce ukládaného slova). Bajty mezi prvky, které nejsou použity, se nazývají padding bytes:

    a: u8,    1 bajt
    b: u8,    1 bajt
    c: u8,    1 bajt
    xxx       1 bajt - výplň (padding)
    d: i32,   4 bajty
    e: i32,   4 bajty
    f: f32    4 bajty
celkem:      16 bajtů

Na jiných architekturách může být způsob zarovnání odlišný a některé překladače ho umožňují zcela vypnout. V případě GCC je možné použít __attribute__((__packed__)) atd. To znamená, že další příklad po překladu pomocí GCC a spuštění vypíše 16 bajtů a 15 bajtů:

#include <stdint.h>
#include <stdio.h>
 
typedef struct {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    int32_t d;
    int32_t e;
    float   f;
} test_struct1;
 
typedef struct __attribute__((__packed__)) {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    int32_t d;
    int32_t e;
    float   f;
} test_struct2;
 
int main(void)
{
    printf("sizeof(test_struct1) = %lu bytes\n", sizeof(test_struct1));
    printf("sizeof(test_struct2) = %lu bytes\n", sizeof(test_struct2));
    return 0;
}

7. Změna pořadí prvků struktury

Zkusme si nyní prohodit pořadí prvků ve struktuře. Namísto uspořádání:

typedef struct {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    int32_t d;
    int32_t e;
    float   f;
} test_struct;

Použijeme strukturu, v níž je vždy mezi prvky se šířkou čtyři bajty vložen prvek jednobajtový:

typedef struct {
    uint8_t a;
    int32_t b;
    uint8_t c;
    int32_t d;
    uint8_t e;
    float   f;
} test_struct;

Upravená céčková část programu vypadá takto:

#include <stdint.h>
#include <stdio.h>
 
typedef struct {
    uint8_t a;
    int32_t b;
    uint8_t c;
    int32_t d;
    uint8_t e;
    float   f;
} test_struct;
 
void print_struct(test_struct s)
{
    printf("sizeof(test_struct) = %lu bytes\n", sizeof(s));
    printf("a = %d\n", s.a);
    printf("b = %d\n", s.b);
    printf("c = %d\n", s.c);
    printf("d = %d\n", s.d);
    printf("e = %d\n", s.e);
    printf("f = %f\n", s.f);
}

Část psaná v Rustu samozřejmě musí reflektovat všechny změny ve struktuře (ostatně – sami si vyzkoušejte, co se stane, když tuto část nezměníte):

use std::mem;
 
#[repr(C)]
struct TestStruct {
    a: u8,
    b: i32,
    c: u8,
    d: i32,
    e: u8,
    f: f32
}
 
extern {
    fn print_struct(s:TestStruct) -> ();
}
 
fn main() {
    let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
    println!("sizeof on Rust side = {} bytes", mem::size_of::<TestStruct>());
    unsafe {
        print_struct(s);
    }
}

8. Překlad a spuštění druhého příkladu

Překlad se provede naprosto stejným způsobem jako v předchozím příkladu:

$ cargo build
   Compiling gcc v0.3.50
   Compiling ffi_project4 v0.1.0 (file:///home/tester/ffi_project4)
    Finished debug [unoptimized + debuginfo] target(s) in 3.45 secs

Po spuštění však dostaneme odlišný výsledek, protože je hlášeno, že struktura má nyní velikost 24 bajtů a nikoli šestnáct bajtů, i když prvky zůstaly zachovány, pouze se prohodily:

$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/ffi_project4`
sizeof on Rust side = 24 bytes
sizeof(test_struct) = 24 bytes
a = 1
b = 1000
c = 255
d = 10000
e = 127
f = 3.140000

Ve skutečnosti totiž nyní naše datová struktura vypadá následovně:

    a: u8,    1 bajt
    xxx       3 bajty - výplň (padding)
    b: i32,   4 bajty
    c: u8,    1 bajt
    xxx       3 bajty - výplň (padding)
    d: i32,   4 bajty
    e: u8,    1 bajt
    xxx       3 bajty - výplň (padding)
    f: f32    4 bajty
celkem:      24 bajtů

Povšimněte si, že prvky o šířce čtyř bajtů skutečně budou začínat na adrese dělitelné čtyřmi (předpokládáme, že i celá struktura začíná na adrese dělitelné čtyřmi, což je pravda).

9. Export binární podoby struktury

Nepatrnou změnou céčkové části projektu si můžeme ověřit, jakým způsobem je struktura uložena v paměti. Kromě funkce print_struct() použijeme novou funkci export_struct() určenou pro export binárního obrazu struktury do souboru (pro jednoduchost bez testů, zda se soubor podařilo vytvořit, otevřít, zapsat data a zavřít:

void export_struct(test_struct s)
{
    FILE *fout = fopen("test.bin", "wb");
    fwrite(&s, sizeof(s), 1, fout);
    fclose(fout);
}

Nová varianta céčkové části bude vypadat následovně:

#include <stdint.h>
#include <stdio.h>
 
typedef struct {
    uint8_t a;
    int32_t b;
    uint8_t c;
    int32_t d;
    uint8_t e;
    float   f;
} test_struct;
 
void export_struct(test_struct s)
{
    FILE *fout = fopen("test.bin", "wb");
    fwrite(&s, sizeof(s), 1, fout);
    fclose(fout);
}
 
void print_struct(test_struct s)
{
    printf("sizeof(test_struct) = %lu bytes\n", sizeof(s));
    printf("a = %d\n", s.a);
    printf("b = %d\n", s.b);
    printf("c = %d\n", s.c);
    printf("d = %d\n", s.d);
    printf("e = %d\n", s.e);
    printf("f = %f\n", s.f);
}

Pokud volání nové funkce přidáme do části psané v Rustu, získáme po spuštění projektu soubor pojmenovaný test.bin, který bude mít na architektuře x86–64 délku 24 bajtů a následující obsah:

$ hexdump -C test.bin 
00000000  01 a4 8f 30 e8 03 00 00  ff 94 5f 53 10 27 00 00  |...0......_S.'..|
00000010  7f d0 61 2f c3 f5 48 40                           |..a/..H@|
00000018

Poznámka: na jiných architekturách může být obsah odlišný, protože se mohou použít odlišná pravidla pro zarovnávání a taktéž může být rozdílný způsob uložení bajtů v širších slovech (little endian versus big endian).

Význam jednotlivých bajtů v souboru je následující:

Offset Sekvence bajtů Význam
0 01 0×01 = 1, první celočíselná konstanta typu bajt
1 a4 8f 30 výplň (padding)
4 e8 03 00 00 0×000003e8 = 1000, druhá celočíselná konstanta typu int32
8 ff 0×ff = 255, třetí celočíselná konstanta typu bajt
9 94 5f 53 výplň (padding)
12 10 27 00 00 0×00002710 = 10000, čtvrtá celočíselná konstanta typu int32
16 7f 0×7f = 127, pátá celočíselná konstanta typu bajt
17 d0 61 2f výplň (padding)
20 c3 f5 48 40 0×4048f5c3 = π v IEEE 754 reprezentaci

Porovnejte si tuto tabulku s rozborem struktury provedené v předchozí kapitole.

10. Předání struktury odkazem (přes ukazatel)

Předávání struktur hodnotou (by value), tedy způsobem, který jsme si ukázali v předchozích kapitolách, se sice používá, ale nemusí být vždy ideální. Nejedná se totiž o příliš efektivní operaci, protože – jak jsme sami viděli – je nutné do nativní funkce překopírovat celou strukturu, která je relativně velká (a v reálných příkladech může být ještě větší). Navíc není možné prvky struktury předané hodnotou měnit, což však na druhou stranu může být vítaná vlastnost. Ovšem při komunikaci mezi Rustem a céčkem lze předat i pouhé ukazatele na strukturu, která tak bude mezi oběma částmi aplikace sdílená. V navazujících kapitolách si ukážeme, jak se to provádí.

11. Céčková část aplikace

Nejprve si ukažme, jak se změní céčková část aplikace ve chvíli, kdy jí potřebujeme předat ukazatel na strukturu. Hlavička funkce bude nyní následující (čteme – „s je ukazatel na hodnotu typu test_struct“):

void print_struct(test_struct *s);

Přístup k prvkům struktury přes ukazatel je v céčku velmi snadný, protože lze použít operátor → a zápis s->prvek je mnohem čitelnější než (*s).prvek (zde je navíc nutné použít závorky kvůli prioritě operátorů):

printf("a = %d\n", s->a);
printf("b = %d\n", s->b);
printf("c = %d\n", s->c);
printf("d = %d\n", s->d);
printf("e = %d\n", s->e);
printf("f = %f\n", s->f);

V upraveném příkladu si navíc necháváme vytisknout jak velikost struktury, tak i velikost ukazatele předaného do volané funkce:

printf("sizeof(pointer)     = %lu bytes\n", sizeof(s));
printf("sizeof(test_struct) = %lu bytes\n", sizeof(*s));

Úplný zdrojový kód céčkové části vypadá takto:

#include <stdint.h>
#include <stdio.h>
 
typedef struct {
    uint8_t a;
    int32_t b;
    uint8_t c;
    int32_t d;
    uint8_t e;
    float   f;
} test_struct;
 
void print_struct(test_struct *s)
{
    printf("sizeof(pointer)     = %lu bytes\n", sizeof(s));
    printf("sizeof(test_struct) = %lu bytes\n", sizeof(*s));
    printf("a = %d\n", s->a);
    printf("b = %d\n", s->b);
    printf("c = %d\n", s->c);
    printf("d = %d\n", s->d);
    printf("e = %d\n", s->e);
    printf("f = %f\n", s->f);
}

12. Část aplikace naprogramovaná v Rustu

I v té části aplikace, která je psaná v Rustu, musí dojít k úpravě, protože potřebujeme získat ukazatel na strukturu. To se provede až překvapivě jednoduše, konkrétně pomocí operátoru &, který slouží k získání reference:

let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
unsafe {
    let pointer = &s;
    print_struct(pointer);
}

Taktéž je nutné upravit hlavičku externí funkce, protože této funkci předáváme referenci (adresu):

extern {
    fn print_struct(s: *const TestStruct) -> ();
}

Upravený program bude vypadat takto:

use std::mem;
 
#[repr(C)]
struct TestStruct {
    a: u8,
    b: i32,
    c: u8,
    d: i32,
    e: u8,
    f: f32
}
 
extern {
    fn print_struct(s: *const TestStruct) -> ();
}
 
fn main() {
    let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
    println!("sizeof on Rust side = {} bytes", mem::size_of::<TestStruct>());
    unsafe {
        let pointer = &s;
        print_struct(pointer);
    }
}

Spuštění příkladu:

$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/ffi_project5`
sizeof on Rust side = 24 bytes
sizeof(pointer)     = 8 bytes
sizeof(test_struct) = 24 bytes
a = 1
b = 1000
c = 255
d = 10000
e = 127
f = 3.140000

13. Ukazatel versus reference

Ve skutečnosti zápisem:

let pointer = &s;

získáme referenci a nikoli skutečný ukazatel. Ten lze získat spíše následovně:

let pointer :*const TestStruct = &s;

Samotná hodnota je sice stejná (= adresa struktury), ale způsob práce se liší. U ukazatelů se například nekontroluje, zda ukazují na živou hodnotu, takže lze zapsat:

#[repr(C)]
#[derive(Debug)]
struct TestStruct {
    a: u8,
    b: i32,
    c: u8,
    d: i32,
    e: u8,
    f: f32
}
 
fn main() {
    let pointer :*const TestStruct;
    {
        let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
        pointer = &s;
        println!("{:?}", pointer);
    }
    println!("{:?}", pointer);
}

U referencí naproti tomu získáme všechny kontroly překladače:

#[derive(Debug)]
struct TestStruct {
    a: u8,
    b: i32,
    c: u8,
    d: i32,
    e: u8,
    f: f32
}
 
fn main() {
    let pointer;
    {
        let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
        pointer = &s;
        println!("{:?}", pointer);
    }
    println!("{:?}", pointer);
}

Tento program se již nepřeloží (což je ostatně zcela korektní):

error: `s` does not live long enough
  --> test.rs:16:20
   |
16 |         pointer = &s;
   |                    ^
   |
note: reference must be valid for the block suffix following statement 0 at 13:16...
  --> test.rs:13:17
   |
13 |     let pointer;
   |                 ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 15:84
  --> test.rs:15:85
   |
15 |         let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
   |                                                                                     ^
 
error: aborting due to previous error

14. Modifikace prvků struktury v nativním kódu

Připomeňme si, že céčková část se strukturou předanou přes ukazatel provádět prakticky cokoli, například měnit její prvky:

#include <stdint.h>
#include <stdio.h>
 
typedef struct {
    uint8_t a;
    int32_t b;
    uint8_t c;
    int32_t d;
    uint8_t e;
    float   f;
} test_struct;
 
void print_struct(test_struct *s)
{
    printf("sizeof(pointer)     = %lu bytes\n", sizeof(s));
    printf("sizeof(test_struct) = %lu bytes\n", sizeof(*s));
    printf("a = %d\n", s->a);
    printf("b = %d\n", s->b);
    printf("c = %d\n", s->c);
    printf("d = %d\n", s->d);
    printf("e = %d\n", s->e);
    printf("f = %f\n", s->f);
}
 
void change_struct(test_struct *s)
{
    s->a = 10;
    s->b = 20;
    s->c = 30;
    s->d = 40;
    s->e = 50;
    s->f = 60.0;
}

15. Rustovská část

To je ovšem v rozporu s Rustovskou deklarací, kde chybí modifikátor mut:

#[derive(Debug)]
#[repr(C)]
struct TestStruct {
    a: u8,
    b: i32,
    c: u8,
    d: i32,
    e: u8,
    f: f32
}
 
extern {
    fn print_struct(s: *const TestStruct) -> ();
    fn change_struct(s: *const TestStruct) -> ();
}
 
fn main() {
    let s : TestStruct = TestStruct{a:1, b:1000, c:255, d:10000, e:127, f:3.14};
    println!("original value: {:?}", s);
    unsafe {
        let pointer = &s;
        print_struct(pointer);
        change_struct(pointer);
        print_struct(pointer);
    }
    println!("changed value: {:?}", s);
}

Na tento rozpor nás ovšem překladač neupozorní (ani překladač Rustu a samozřejmě ani překladač céčka). Podívejme se, co se stane po spuštění aplikace:

bitcoin_skoleni

$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/ffi_project6`
original value: TestStruct { a: 1, b: 1000, c: 255, d: 10000, e: 127, f: 3.14 }
sizeof(pointer)     = 8 bytes
sizeof(test_struct) = 24 bytes
a = 1
b = 1000
c = 255
d = 10000
e = 127
f = 3.140000
sizeof(pointer)     = 8 bytes
sizeof(test_struct) = 24 bytes
a = 10
b = 20
c = 30
d = 40
e = 50
f = 60.000000
changed value: TestStruct { a: 10, b: 20, c: 30, d: 40, e: 50, f: 60 }

Vidíme, že céčková funkce change_struct() skutečně změnila data Rustu „pod rukama“, což obecně není korektní chování.

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

Všechny dnes popisované demonstrační příklady (projekty) byly, ostatně 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. Demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář (ovšem u projektů je lepší mít celý repositář, abyste nemuseli pracně stahovat všechny potřebné soubory):

Příklad Adresa
Projekt číslo 3  
Cargo.toml https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project3/Cargo.toml
build.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project3/build.rs
ffi3.c https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project3/src/ffi3­.c
main.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project3/src/ma­in.rs
   
Projekt číslo 4  
Cargo.toml https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project4/Cargo.toml
build.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project4/build.rs
ffi4.c https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project4/src/ffi4­.c
main.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project4/src/ma­in.rs
   
Projekt číslo 5  
Cargo.toml https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project5/Cargo.toml
build.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project5/build.rs
ffi5.c https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project5/src/ffi5­.c
main.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project5/src/ma­in.rs
   
Projekt číslo 6  
Cargo.toml https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project6/Cargo.toml
build.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project6/build.rs
ffi6.c https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project6/src/ffi6­.c
main.rs https://github.com/tisnik/pre­sentations/blob/master/rus­t/ffi/ffi_project6/src/ma­in.rs

17. Odkazy na Internetu

  1. Build Script Support
    http://doc.crates.io/build-script.html
  2. Creating a shared and static library with the gnu compiler [gcc]
    http://www.adp-gmbh.ch/cpp/gcc/create_lib.html
  3. FFI: Foreign Function Interface
    https://doc.rust-lang.org/book/ffi.html
  4. Primitive Type pointer
    https://doc.rust-lang.org/std/primitive.pointer.html
  5. Cargo: správce projektů a balíčků pro programovací jazyk Rust
    https://mojefedora.cz/cargo-spravce-projektu-a-balicku-pro-programovaci-jazyk-rust/
  6. Network Communication and Serialization in Rust
    https://www.safaribookson­line.com/blog/2014/01/28/net­work-communication-serialization-rust/
  7. Crate bincode
    http://tyoverby.com/binco­de/bincode/index.html
  8. Struct std::fs::File
    https://doc.rust-lang.org/std/fs/struct.File.html
  9. Trait std::io::Seek
    https://doc.rust-lang.org/std/io/trait.Seek.html
  10. Trait std::io::Read
    https://doc.rust-lang.org/std/io/trait.Read.html
  11. Trait std::io::Write
    https://doc.rust-lang.org/std/io/trait.Write.html
  12. Trait std::io::BufRead
    https://doc.rust-lang.org/std/io/trait.BufRead.html
  13. Module std::io::prelude
    https://doc.rust-lang.org/std/io/prelude/index.html
  14. std::net::IpAddr
    https://doc.rust-lang.org/std/net/enum.IpAddr.html
  15. std::net::Ipv4Addr
    https://doc.rust-lang.org/std/net/struct.Ipv4Addr.html
  16. std::net::Ipv6Addr
    https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html
  17. TcpListener
    https://doc.rust-lang.org/std/net/struct.TcpLis­tener.html
  18. TcpStream
    https://doc.rust-lang.org/std/net/struct.TcpStream.html
  19. Binary heap (Wikipedia)
    https://en.wikipedia.org/wi­ki/Binary_heap
  20. Binární halda (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Bin%C3%A1rn%C3%AD_halda
  21. Halda (datová struktura)
    https://cs.wikipedia.org/wi­ki/Halda_%28datov%C3%A1_struk­tura%29
  22. Struct std::collections::HashSet
    https://doc.rust-lang.org/std/collections/struc­t.HashSet.html
  23. Struct std::collections::BTreeSet
    https://doc.rust-lang.org/std/collections/struc­t.BTreeSet.html
  24. Struct std::collections::BinaryHeap
    https://doc.rust-lang.org/std/collections/struc­t.BinaryHeap.html
  25. Set (abstract data type)
    https://en.wikipedia.org/wi­ki/Set_%28abstract_data_ty­pe%29#Language_support
  26. Associative array
    https://en.wikipedia.org/wi­ki/Associative_array
  27. Hash Table
    https://en.wikipedia.org/wi­ki/Hash_table
  28. B-tree
    https://en.wikipedia.org/wiki/B-tree
  29. Pedro Celis: Robin Hood Hashing (naskenované PDF!)
    https://cs.uwaterloo.ca/re­search/tr/1986/CS-86–14.pdf
  30. Robin Hood hashing
    http://codecapsule.com/2013/11/11/ro­bin-hood-hashing/
  31. Robin Hood hashing: backward shift deletion
    http://codecapsule.com/2013/11/17/ro­bin-hood-hashing-backward-shift-deletion/
  32. Module std::collections
    https://doc.rust-lang.org/std/collections/
  33. Module std::vec
    https://doc.rust-lang.org/nightly/std/vec/index.html
  34. Struct std::collections::VecDeque
    https://doc.rust-lang.org/std/collections/struc­t.VecDeque.html
  35. Struct std::collections::LinkedList
    https://doc.rust-lang.org/std/collections/struc­t.LinkedList.html
  36. Module std::fmt
    https://doc.rust-lang.org/std/fmt/
  37. Macro std::println
    https://doc.rust-lang.org/std/macro.println.html
  38. Enum std::result::Result
    https://doc.rust-lang.org/std/result/enum.Result.html
  39. Module std::result
    https://doc.rust-lang.org/std/result/
  40. Result
    http://rustbyexample.com/std/re­sult.html
  41. Rust stdlib: Option
    https://doc.rust-lang.org/std/option/enum.Option.html
  42. Module std::option
    https://doc.rust-lang.org/std/option/index.html
  43. Rust by example: option
    http://rustbyexample.com/std/op­tion.html
  44. Rust by example: if-let
    http://rustbyexample.com/flow_con­trol/if_let.html
  45. Rust by example: while let
    http://rustbyexample.com/flow_con­trol/while_let.html
  46. Rust by example: Option<i32>
    http://rustbyexample.com/std/op­tion.html
  47. An Overview of Macros in Rust
    http://words.steveklabnik.com/an-overview-of-macros-in-rust
  48. A Practical Intro to Macros in Rust 1.0
    https://danielkeep.github.io/practical-intro-to-macros.html
  49. The Rust Programming Language: macros
    https://doc.rust-lang.org/beta/book/macros.html
  50. Rust by example: 15 macro_rules!
    http://rustbyexample.com/macros.html
  51. Primitive Type isize
    https://doc.rust-lang.org/nightly/std/primi­tive.isize.html
  52. Primitive Type usize
    https://doc.rust-lang.org/nightly/std/primi­tive.usize.html
  53. Primitive Type array
    https://doc.rust-lang.org/nightly/std/primi­tive.array.html
  54. Module std::slice
    https://doc.rust-lang.org/nightly/std/slice/
  55. Rust by Example: 2.3 Arrays and Slices
    http://rustbyexample.com/pri­mitives/array.html
  56. What is the difference between Slice and Array (stackoverflow)
    http://stackoverflow.com/qu­estions/30794235/what-is-the-difference-between-slice-and-array
  57. Learning Rust With Entirely Too Many Linked Lists
    http://cglab.ca/~abeinges/blah/too-many-lists/book/
  58. Testcase: linked list
    http://rustbyexample.com/cus­tom_types/enum/testcase_lin­ked_list.html
  59. Operators and Overloading
    https://doc.rust-lang.org/book/operators-and-overloading.html
  60. Module std::ops
    https://doc.rust-lang.org/std/ops/index.html
  61. Module std::cmp
    https://doc.rust-lang.org/std/cmp/index.html
  62. Trait std::ops::Add
    https://doc.rust-lang.org/stable/std/ops/trait.Add.html
  63. Trait std::ops::AddAssign
    https://doc.rust-lang.org/std/ops/trait.AddAssign.html
  64. Trait std::ops::Drop
    https://doc.rust-lang.org/std/ops/trait.Drop.html
  65. Trait std::cmp::Eq
    https://doc.rust-lang.org/std/cmp/trait.Eq.html
  66. Struct std::boxed::Box
    https://doc.rust-lang.org/std/boxed/struct.Box.html
  67. Explore the ownership system in Rust
    https://nercury.github.io/rus­t/guide/2015/01/19/ownership­.html
  68. Rust's ownership and move semantic
    http://www.slideshare.net/sa­neyuki/rusts-ownership-and-move-semantics
  69. Trait std::marker::Copy
    https://doc.rust-lang.org/stable/std/marker/tra­it.Copy.html
  70. Trait std::clone::Clone
    https://doc.rust-lang.org/stable/std/clone/tra­it.Clone.html
  71. The Stack and the Heap
    https://doc.rust-lang.org/book/the-stack-and-the-heap.html
  72. Rust Compare: Pointers & References
    http://www.rust-compare.com/site/pointers.html
  73. Rust Compare: Parameters
    http://www.rust-compare.com/site/params.html
  74. Why does this compile? Automatic dereferencing?
    https://users.rust-lang.org/t/why-does-this-compile-automatic-dereferencing/2183
  75. Understanding Pointers, Ownership, and Lifetimes in Rust
    http://koerbitz.me/posts/Understanding-Pointers-Ownership-and-Lifetimes-in-Rust.html
  76. Rust lang series episode #25 — pointers (#rust-series)
    https://steemit.com/rust-series/@jimmco/rust-lang-series-episode-25-pointers-rust-series
  77. Rust – home page
    https://www.rust-lang.org/en-US/
  78. Rust – Frequently Asked Questions
    https://www.rust-lang.org/en-US/faq.html
  79. Destructuring and Pattern Matching
    https://pzol.github.io/get­ting_rusty/posts/20140417_des­tructuring_in_rust/
  80. The Rust Programming Language
    https://doc.rust-lang.org/book/
  81. Rust (programming language)
    https://en.wikipedia.org/wi­ki/Rust_%28programming_lan­guage%29
  82. Go – home page
    https://golang.org/
  83. Stack Overflow – Most Loved, Dreaded, and Wanted language
    https://stackoverflow.com/re­search/developer-survey-2016#technology-most-loved-dreaded-and-wanted
  84. 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/
  85. Rust vs Go: My experience
    https://www.reddit.com/r/go­lang/comments/21m6jq/rust_vs_go_my_ex­perience/
  86. Friends of Rust (Organizations running Rust in production)
    https://www.rust-lang.org/en-US/friends.html
  87. Rust programs versus C++ g++
    https://benchmarksgame.ali­oth.debian.org/u64q/compa­re.php?lang=rust&lang2=gpp
  88. Další benchmarky (nejedná se o reálné příklady „ze života“)
    https://github.com/kostya/benchmarks
  89. Go na Redditu
    https://www.reddit.com/r/golang/
  90. Rust vs. Go
    http://vschart.com/compare/rust/vs/go-language
  91. Abstraction without overhead: traits in Rust
    https://blog.rust-lang.org/2015/05/11/traits.html
  92. Method Syntax
    https://doc.rust-lang.org/book/method-syntax.html
  93. Traits in Rust
    https://doc.rust-lang.org/book/traits.html
  94. Functional Programming in Rust – Part 1 : Function Abstraction
    http://blog.madhukaraphatak­.com/functional-programming-in-rust-part-1/
  95. 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
  96. Chytré ukazatele (moderní verze jazyka C++) [MSDN]
    https://msdn.microsoft.com/cs-cz/library/hh279674.aspx
  97. UTF-8 Everywhere
    http://utf8everywhere.org/
  98. Rust by Example
    http://rustbyexample.com/
  99. Rust oficiálně ve Fedoře
    https://mojefedora.cz/rust-oficialne-ve-fedore/
  100. Resource acquisition is initialization
    https://en.wikipedia.org/wi­ki/Resource_acquisition_is_i­nitialization
  101. TIOBE index (October 2016)
    http://www.tiobe.com/tiobe-index/
  102. 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
  103. String Types in Rust
    http://www.suspectsemantic­s.com/blog/2016/03/27/str­ing-types-in-rust/
  104. Trait (computer programming)
    https://en.wikipedia.org/wi­ki/Trait_%28computer_program­ming%29
  105. 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.