Unsafe Rust and FFI

12 декември 2019

Unsafe Rust

Разглеждали сме малко unsafe Rust в минали лекции за указатели.

Unsafe Rust

Силната страна на Rust са статичните гаранции за поведението на програмата.

Unsafe Rust

Силната страна на Rust са статичните гаранции за поведението на програмата.


Но тези проверки са консервативни и съществуват програми, които са 'safe', но компилатора не може да ги верифицира. За да може да пишем такива програми се налага да кажем на компилатора да смекчи ограниченията си.

Unsafe Rust

Силната страна на Rust са статичните гаранции за поведението на програмата.


Но тези проверки са консервативни и съществуват програми, които са 'safe', но компилатора не може да ги верифицира. За да може да пишем такива програми се налага да кажем на компилатора да смекчи ограниченията си.


За тази цел в Rust има ключовата дума unsafe, която премахва някои ограничения на компилатора.

Unsafe Rust

Съществуват 4 контекста в които може да използваме unsafe.

Unsafe Rust

Всички функции които използваме през FFI трябва да се маркират като unsafe.
Спокойно, ще видим какво точно е FFI по-късно в лекцията.

1 2 3
unsafe fn danger_will_robinson() {
    // Scary stuff...
}

Unsafe Rust

Блокове, маркирани с unsafe.

1 2 3
unsafe {
    // Scary stuff...
}

Unsafe Rust

Блокове, маркирани с unsafe.

1 2 3
unsafe {
    // Scary stuff...
}

Това е може би най-често срещаното използване на unsafe.

Unsafe Rust

unsafe типажи.

1
unsafe trait Scary { ... }

Unsafe Rust

Вече сме ги виждали впрочем, най-често използваните са

1 2
pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }

Unsafe Rust

Както знаем вече, те се имплементират автоматично върху типове, които компилатора сметне за подходящи.

Но специално Send и Sync може да ги деимплементираме с !, когато не искаме да се имплементират за типа ни.

1
impl<T> !Sync for Rc<T> where T: ?Sized

Unsafe Rust

Понякога може да имаме обратното - Send или Sync да не са имплементирани за наш тип, но всъщност да е правилно да бъдат имплементирани.

Unsafe Rust

Понякога може да имаме обратното - Send или Sync да не са имплементирани за наш тип, но всъщност да е правилно да бъдат имплементирани.


Тогава може да си ги имплементираме сами, но това е unsafe, защото има шанс да нарушим гаранциите на компилатора.
В този случай трябва сами да гарантираме, че семантиките, които репрезентират типажите, са спазени.

Unsafe Rust

Тогава може да ги имплементираме по следния начин.

1
unsafe impl Scary for i32 { ... }

Unsafe Rust

Ако програмата ви segfault-ва, може да сме сигурни, че това се случва в unsafe код.

Unsafe Rust

Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…

Unsafe Rust

Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…

Unsafe Rust

Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…

Unsafe Rust

Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…

Unsafe Rust

Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…

Unsafe Rust

…има и неща които може да направим в unsafe код, но е добре да ги избягваме.

Unsafe Rust

Чрез unsafe, Rust ни предоставя 3 неща които не можем да правим при нормални обстоятелства:

  1. Четене и писане в static mutable променливи.
  2. Дереференциране на голи указатели.
  3. Извикване на unsafe функции.

Това е всичко.

Unsafe Rust

Чрез unsafe, Rust ни предоставя 3 неща които не можем да правим при нормални обстоятелства:

  1. Четене и писане в static mutable променливи.
  2. Дереференциране на голи указатели.
  3. Извикване на unsafe функции.

Това е всичко.


Когато използвате unsafe, няма да изключите правила на компилатора като borrow checker.

Unsafe Rust

FFI

Какво означава FFI?

Unsafe Rust

FFI

Какво означава FFI?


Foreign Function Interface

FFI

За какво се ползва?

FFI

За какво се ползва?


За извикване на функции, които не са написани в нашият код.
Това са най-често функции в библиотеки (dll, lib и т.н.), върху които нямаме контрол.

Викане на C функции от Rust

1 2 3
int add_in_c(int a, int b) {
    return a + b;
}
1 2 3 4 5
use std::os::raw::c_int;

extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

Викане на C функции от Rust

extern

Викане на C функции от Rust

extern

Викане на C функции от Rust

extern

Викане на C функции от Rust

extern

Викане на C функции от Rust

extern

Външни функции са unsafe, защото компилаторът не може да гарантира, че работят правилно.

1 2 3 4 5 6 7 8
extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

fn main() {
    let res = unsafe { add_in_c(1, 2) };
    println!("{}", res);
}

Викане на C функции от Rust

Calling convention

Calling conventions се задават на extern блока. По подразбиране е "C".

1 2 3 4 5 6 7
extern "C" {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

extern "system" {
    fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> c_int;
}

Викане на C функции от Rust

Calling convention

За какво служи конвенцията на извикване на функции?

Викане на C функции от Rust

Calling convention

За какво служи конвенцията на извикване на функции?


За да уеднакви правилата които трябва да спазват извикващата и извиканата функция, така че да няма разминаване при подаване на параметри и връщане на стойност.

Викане на C функции от Rust

Calling convention

Ако нямаше конвенция, извикването на функции щеше да е хаос.

Четирите правила които определя са:

Викане на C функции от Rust

Calling convention

Ако нямаше конвенция, извикването на функции щеше да е хаос.

Четирите правила които определя са:

Викане на C функции от Rust

Calling convention

Ако нямаше конвенция, извикването на функции щеше да е хаос.

Четирите правила които определя са:

Викане на C функции от Rust

Calling convention

Ако нямаше конвенция, извикването на функции щеше да е хаос.

Четирите правила които определя са:

Викане на C функции от Rust

Calling convention

Ако нямаше конвенция, извикването на функции щеше да е хаос.

Четирите правила които определя са:

Викане на C функции от Rust

Calling convention

За любопитните, в момента двете най-разпространени конвенции са

Викане на C функции от Rust

Calling convention

Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.

Викане на C функции от Rust

Calling convention

Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.

Викане на C функции от Rust

Calling convention

Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.

Викане на C функции от Rust

Calling convention

Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.

Викане на C функции от Rust

Нека да пробваме да компилираме

1 2 3 4 5 6 7 8 9 10 11 12 13
use std::os::raw::c_int;

extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

fn main() {
    let res = unsafe {
        add_in_c(1, 2)
    };

    println!("{}", res);
}
error: linking with `cc` failed: exit code: 1 | = note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.1udhigrkjwz87wtz.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.204y5cxrdvxs4mev.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.291w1mp0ua1a5ok1.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.2jb7gvajht83snc6.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.35ubxizdu4kfhlxa.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.552qm06vt26adgtz.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.5axfd7a4dqvcs8im.rcgu.o" "-o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.1b9w6n88isu2iz8r.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,--start-group" "-Wl,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-fae576517123aa4e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-a72070139220275e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-093434daf7d99801.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-24daf38551b7a03b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace-36d70d9746402ce9.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-7acfc843240167a8.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-eb2e0f5fe057b8b3.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-75e9ddd83715a368.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-af51e7c6fd7d1248.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-27f2a77b2995d98c.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-ad10152c26711a1e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-291bd2456cb6c9fe.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-fc6e9071307a3016.rlib" "-Wl,--end-group" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-ebe4001ded7f33e7.rlib" "-Wl,-Bdynamic" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil" = note: /usr/bin/ld: /home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-8093fbdab92859ca.2jb7gvajht83snc6.rcgu.o: in function `main_df8618577ecb652a8a0d873977025ed4a2695877::main': /main_df8618577ecb652a8a0d873977025ed4a2695877.rs:9: undefined reference to `add_in_c' collect2: error: ld returned 1 exit status
use std::os::raw::c_int;

extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

fn main() {
    let res = unsafe {
        add_in_c(1, 2)
    };

    println!("{}", res);
}

Очаквано, не сме казали на компилатора къде да намери функцията

Linking

Ръчният начин

Static linking

компилираме C кода до math.lib / libmath.a

1 2 3
cargo rustc -- -L . -l math
# или
cargo rustc -- -L . -l static=math

Dynamic linking

компилираме C кода до math.dll / libmath.so

1 2 3
cargo rustc -- -L . -l math
# или
cargo rustc -- -L . -l dylib=math

Linking

Правилният начин

1 2 3 4
#[link(name="math")]
extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

Linking

Правилният начин

1 2 3 4
#[link(name="math")]
extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

Linking

Правилният начин

1 2 3 4
#[link(name="math")]
extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

Linking

Правилният начин

1 2 3 4
#[link(name="math")]
extern {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

Linking

Правилният начин

Linking

Правилният начин

Linking

Правилният начин

Linking

Правилният начин

1
cargo rustc -- -L .

Linking

Странности

Няма значение на кой блок е поставен #[link] атрибута.

1 2 3 4 5 6 7 8 9 10 11 12 13
#[link(name="foo")]
#[link(name="bar")]
extern {}

extern {
    fn foo_init();
    fn foo_stuff(x: c_int);
}

extern {
    fn bar_init();
    fn bar_stuff() -> c_int;
}

Build scripts

Cargo предоставя възможност за изпълняване на скрипт преди компилиране на crate-a.

Използва се при FFI, ако искаме сами да си компилираме C кода и да укажем как да се линкнем към него.

http://doc.crates.io/build-script.html

Build scripts

1 2 3 4 5
[package]
name = "ffi"
version = "0.1.0"
authors = ["..."]
build = "build.rs"
1 2 3 4 5
// build.rs

fn main() {
    ...
}

Build scripts

Build scripts

Build scripts

1 2
[build-dependencies]
...

Build scripts

Build scripts

Build scripts

Build scripts

Build scripts

1 2 3 4 5
// build.rs

fn main() {
    println!("cargo:rustc-link-search=.");
}
// build.rs

fn main() {
    println!("cargo:rustc-link-search=.");
}

Callbacks

1 2 3 4 5 6 7
// main.c

typedef int (*callback)(int);

int apply(int a, callback op) {
    return op(a);
}
1 2 3 4 5 6 7 8 9 10 11 12
// main.rs

#[link(name="math")]
extern "C" {
    fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int;
}

fn cube(x: i32) -> i32 { x * x * x }

fn main() {
    println!("{}", unsafe { apply(11, cube) });
}

Callbacks

1 2 3 4 5 6 7
// main.c

typedef int (*callback)(int);

int apply(int a, callback op) {
    return op(a);
}
1 2 3 4 5 6 7 8 9 10 11 12
// main.rs

#[link(name="math")]
extern "C" {
    fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int;
}

fn cube(x: i32) -> i32 { x * x * x }

fn main() {
    println!("{}", unsafe { apply(11, cube) });
}
warning: `extern` block uses type `fn(i32) -> i32`, which is not FFI-safe --> src/bin/main_00831ccc8c58c48de66cefd63726757c274b7239.rs:6:28 | 6 | fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int; | ^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: `#[warn(improper_ctypes)]` on by default = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention error: linking with `cc` failed: exit code: 1 | = note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.1q1iwdd9hli4iqgx.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.2ckuhegw2gx8ehs8.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.2jyxji9mrikj9exn.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.2m1pc62sun3k7ms7.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.32gq5w0y716dmeb.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.4cdtnk26phabv170.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.i2o8en5rxshqflq.rcgu.o" "-o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-b719ad1b770f15c0.4iimfup1ebfgpfpe.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-lmath" "-Wl,--start-group" "-Wl,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-fae576517123aa4e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-a72070139220275e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-093434daf7d99801.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-24daf38551b7a03b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace-36d70d9746402ce9.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-7acfc843240167a8.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-eb2e0f5fe057b8b3.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-75e9ddd83715a368.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-af51e7c6fd7d1248.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-27f2a77b2995d98c.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-ad10152c26711a1e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-291bd2456cb6c9fe.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-fc6e9071307a3016.rlib" "-Wl,--end-group" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-ebe4001ded7f33e7.rlib" "-Wl,-Bdynamic" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil" = note: /usr/bin/ld: cannot find -lmath collect2: error: ld returned 1 exit status
use std::os::raw::c_int;

#[link(name="math")]
extern "C" {
    fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int;
}

fn cube(x: i32) -> i32 { x * x * x }

fn main() {
    println!("{}", unsafe { apply(11, cube) });
}

Callbacks

Kомпилаторът ни подсказва:

Callbacks

Kомпилаторът ни подсказва:

Callbacks

Kомпилаторът ни подсказва:

Callbacks

1 2 3 4 5 6 7 8 9 10 11 12
// main.rs

#[link(name="math")]
extern "C" {
    fn apply(a: c_int, op: extern fn(c_int) -> c_int) -> c_int;
}

extern fn cube(x: i32) -> i32 { x * x * x }

fn main() {
    println!("{}", unsafe { apply(11, cube) });
}

Panics

A panic! across an FFI boundary is undefined behavior.

Когато подаваме или експортираме rust функции трябва да се подсигурим, че те не могат да се панират. В тази ситуация е удобно да се използва catch_unwind.

1 2 3 4 5 6 7 8 9 10 11 12
use std::panic::catch_unwind;

extern fn oh_no() -> i32 {
    let result = catch_unwind(|| {
        panic!("Oops!");
    });

    match result {
        Ok(_) => 0,
        Err(_) => 1,
    }
}
fn main() {}
use std::panic::catch_unwind;

extern fn oh_no() -> i32 {
    let result = catch_unwind(|| {
        panic!("Oops!");
    });

    match result {
        Ok(_) => 0,
        Err(_) => 1,
    }
}

Panics

NB! Не го използвайте за generic try/catch block!!

Не е гарантирано, че ще хване всички паници, защото някои от тях са abort.

Други неща

Други неща

Други неща

Writing wrappers

Много често е удобно да напишем "rusty" интерфейс към библиотеката

1 2 3 4 5 6 7 8 9 10 11 12
extern crate libc;
use libc::{c_int, c_size_t};

#[link(name="math")]
extern {
    fn math_array_sum(arr: *const c_int, len: c_size_t) -> c_int;
}

/// Safe wrapper
pub fn array_sum(arr: &[c_int]) {
    unsafe { math_array_sum(arr.as_ptr(), arr.len()) }
}

Writing wrappers

Други

Writing wrappers

Други

Writing wrappers

Други

Writing wrappers

Други

Споделяне на структури

Структурите в rust нямат определено подреждане на полетата.

1 2 3 4 5 6 7 8
struct FooBar {
    int foo;
    short bar;
};

void foobar(FooBar x) {
    // ...
}
1 2 3 4 5 6 7 8
struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern {
    fn foobar(x: FooBar);
}

Споделяне на структури

Структурите в rust нямат определено подреждане на полетата.

1 2 3 4 5 6 7 8
struct FooBar {
    int foo;
    short bar;
};

void foobar(FooBar x) {
    // ...
}
1 2 3 4 5 6 7 8
struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern {
    fn foobar(x: FooBar);
}
warning: `extern` block uses type `FooBar`, which is not FFI-safe --> src/bin/main_6d02c4ba60eade12e3231c62b2a0c34bf55cf37d.rs:10:18 | 10 | fn foobar(x: FooBar); | ^^^^^^ not FFI-safe | = note: `#[warn(improper_ctypes)]` on by default = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: type defined here --> src/bin/main_6d02c4ba60eade12e3231c62b2a0c34bf55cf37d.rs:4:1 | 4 | / struct FooBar { 5 | | foo: c_int, 6 | | bar: c_short, 7 | | } | |_^
use std::os::raw::{c_int, c_short};
fn main() {}
struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern {
    fn foobar(x: FooBar);
}

Споделяне на структури

За да споделим структура между Rust и C трябва да забраним на компилатора да размества полетата с #[repr(C)].

1 2 3 4 5 6 7 8 9
extern {
    fn foobar(x: FooBar);
}

#[repr(C)]
struct FooBar {
    foo: c_int,
    bar: c_short,
}
use std::os::raw::{c_int, c_short};
fn main() {}
extern {
    fn foobar(x: FooBar);
}

#[repr(C)]
struct FooBar {
    foo: c_int,
    bar: c_short,
}

Споделяне на низове

Споделяне на низове

Споделяне на низове

Споделяне на низове

Споделяне на низове

CString

1 2 3 4 5 6 7 8 9
use std::ffi::CString;

// създава се от неща които имплементират Into<Vec<u8>>,
// в това число &str и String
let hello = CString::new("Hello!").unwrap();

unsafe {
    print(hello.as_ptr());
}
use std::os::raw::c_char;
extern {
fn print(s: *const c_char);
}
use std::ffi::CString;

fn main() {
// създава се от неща които имплементират Into>,
// в това число &str и String
let hello = CString::new("Hello!").unwrap();

unsafe {
    print(hello.as_ptr());
}
}

Споделяне на низове

CString

Какъв е проблема?

1 2 3 4 5 6 7
extern {
    fn print(s: *const c_char);
}

unsafe {
    print(CString::new("Hello!").unwrap().as_ptr());
}
use std::os::raw::c_char;
use std::ffi::CString;
extern {
    fn print(s: *const c_char);
}

fn main () {
unsafe {
    print(CString::new("Hello!").unwrap().as_ptr());
}
}

Споделяне на низове

CString

Работим с голи указатели, а не с референции. Трябва да се погрижим паметта да живее достатъчно!

1 2 3 4 5 6
let hello = CString::new("Hello!").unwrap();
let ptr = hello.as_ptr();                             // `ptr` е валиден докато `hello` е жив
unsafe { print(ptr) };                                // Ок

let ptr = CString::new("Hello!").unwrap().as_ptr();   // временния CString се деалокира
unsafe { print(ptr) };                                // подаваме dangling pointer
use std::os::raw::c_char;
use std::ffi::CString;
extern {
fn print(s: *const c_char);
}
fn main() {
let hello = CString::new("Hello!").unwrap();
let ptr = hello.as_ptr();                             // `ptr` е валиден докато `hello` е жив
unsafe { print(ptr) };                                // Ок

let ptr = CString::new("Hello!").unwrap().as_ptr();   // временния CString се деалокира
unsafe { print(ptr) };                                // подаваме dangling pointer
}

Споделяне на низове

CStr

Споделяне на низове

CStr

Споделяне на низове

CStr

Option and the "nullable pointer optimization"

1 2 3 4
typedef int (*callback)(int);

// callback can be a function pointer or NULL
void register(f: callback);
1 2 3 4
extern "C" {
    /// Registers the callback.
    fn register(cb: Option<extern "C" fn(c_int) -> c_int>);
}

Opaque types

Често C код използва opaque types.

1 2 3 4
struct Foo;

Foo* init();
int stuff(Foo* foo);

Opaque types

За да представим такъв тип в rust можем да използваме празен enum

Не можем да създадем променлива от този тип, защото enum-а няма варианти

1 2 3 4 5 6
enum Foo {}

extern {
    fn init() -> *const Foo;
    fn stuff(foo: *const Foo) -> c_int;
}
use std::os::raw::c_int;
fn main() {}
enum Foo {}

extern {
    fn init() -> *const Foo;
    fn stuff(foo: *const Foo) -> c_int;
}

Компилиране до библиотека

До сега сме правили библиотеки за Rust чрез cargo new --lib.
При компилация този вид crate ни дава rlib и има следния Cargo.toml

1 2 3 4 5 6
[package]
name = "project_name"
version = "0.1.0"
authors = ["..."]

[dependencies]

Компилиране до библиотека

Това е достатъчно когато правим crate за Rust екосистемата, но понякога се налага да създадем специфично статична или динамична библиотека.

Компилиране до библиотека

В този случай може да използвате Cargo.toml, за да укажете това

1 2 3 4 5 6 7 8
[package]
name = "project_name"
version = "0.1.0"

[lib]
crate-type = ["..."]

[dependencies]

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

На мястото на триеточието може да поставим следните типове:

Компилиране до библиотека

При компилация с cargo файловете се намират в target/${target_type}

bindgen crate

Bindgen

Ресурси

Ресурси

Ресурси

Ресурси

Въпроси