Unsafe Rust and FFI

13 декември 2022

Unsafe Rust

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

Unsafe Rust

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

Unsafe Rust

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


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

Unsafe Rust

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


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


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

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 ни предоставя точно 5 неща които не можем да правим при нормални обстоятелства:

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

Unsafe Rust

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

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


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

Union

Синтаксисът е сходен с този на структурите, но полетата на union-ите споделят една и съща памет.

1 2 3 4
union U {
    a: u32,
    b: bool,
}
union U {
    a: u32,
    b: bool,
}
fn main() {}

Unsafe Rust

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

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

Unsafe Rust

FFI

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

Unsafe Rust

FFI

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


Foreign Function Interface

FFI

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

FFI

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


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

Викане на 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

За любопитните, разпространени x86 конвенции са

Викане на 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 status: 1 | = note: "cc" "-m64" "/tmp/rustc1Howw8/symbols.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.2aezuftndu01cu8m.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.329yrj1mcssjdem5.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.3hdc4c1u78mig5ds.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.4h8glayev5bpfd9g.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.4uz0wvliwhjnnop.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.5485ufl2w968ozg1.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.58tstedgl1ghxxrc.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.2f9m9fz2mpp8p4cg.rcgu.o" "-Wl,--as-needed" "-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,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-05737cf45bd30456.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-9f873b61fdec9b03.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-7f13930fcac1846f.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-098633b847612f3b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-f14b73d282b0245e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-2c5b4433ebc1d822.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-59591a7b405fe395.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-384947c6d5f697ff.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-b08a86c6880b47a8.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-58adeee671f9ba8e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-f156b880fc73e7f0.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-4458c5022988e1ab.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-02e61e5ec4aa9e8b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-a0d9b33b5161957b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-04cec55a79224c36.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-3fb6d8496dc7d6a6.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-7d46c016841a97d4.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-a1f7b8b60464cc57.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-272ca28f0b8538d5.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-znoexecstack" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro,-znow" "-nodefaultlibs" = note: /usr/bin/ld: /home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.58tstedgl1ghxxrc.rcgu.o: in function `main_df8618577ecb652a8a0d873977025ed4a2695877::main': /main_df8618577ecb652a8a0d873977025ed4a2695877.rs:9: undefined reference to `add_in_c' collect2: error: ld returned 1 exit status = help: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified = note: use the `-l` flag to specify native libraries to link = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname) error: could not compile `rust` due to previous error
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 status: 1 | = note: "cc" "-m64" "/tmp/rustciDqDON/symbols.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.11batfs7n202lm8w.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.1ju26kuvdljyw6g7.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.1l5d7buc69nn894f.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.1xhae7fjenbdce0r.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.414rtkjfrp41iyj1.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.4oh0iml7mm38tmja.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.pfdxr6gnqhbq3pk.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.5b4be173w1v4szel.rcgu.o" "-Wl,--as-needed" "-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,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-05737cf45bd30456.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-9f873b61fdec9b03.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-7f13930fcac1846f.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-098633b847612f3b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-f14b73d282b0245e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-2c5b4433ebc1d822.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-59591a7b405fe395.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-384947c6d5f697ff.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-b08a86c6880b47a8.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-58adeee671f9ba8e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-f156b880fc73e7f0.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-4458c5022988e1ab.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-02e61e5ec4aa9e8b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-a0d9b33b5161957b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-04cec55a79224c36.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-3fb6d8496dc7d6a6.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-7d46c016841a97d4.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-a1f7b8b60464cc57.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-272ca28f0b8538d5.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-znoexecstack" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro,-znow" "-nodefaultlibs" = note: /usr/bin/ld: cannot find -lmath: No such file or directory collect2: error: ld returned 1 exit status error: could not compile `rust` due to previous error; 1 warning emitted
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.

Exports

За експортиране на функции към C или друг език се налага да използваме #[no_mangle], за да предотвратим промяна на името в генерираните символи на библиотеката.

1 2 3
#[no_mangle]
extern fn call_me_from_c() {
}
#[no_mangle]
extern fn call_me_from_c() {
}
fn main() {}

Други неща

Variadic functions

1 2 3 4 5 6 7 8 9
#![feature(c_variadic)]

pub unsafe extern "C" fn add(n: usize, mut args: ...) -> usize {
    let mut sum = 0;
    for _ in 0..n {
        sum += args.arg::<usize>();
    }
    sum
}

Importing global variables

1
extern void* magic;

1 2 3 4 5 6
use std::os::raw::c_void;

extern {
    #[no_mangle]
    static magic: *const c_void;
}

Writing wrappers

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

1 2 3 4 5 6 7 8 9 10 11
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]) -> 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: the type is defined here --> src/bin/main_6d02c4ba60eade12e3231c62b2a0c34bf55cf37d.rs:4:1 | 4 | struct FooBar { | ^^^^^^^^^^^^^
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());
}
warning: getting the inner pointer of a temporary `CString` --> src/bin/main_5e7ecc6a2f055e7fe36ca2bf3ab4a108367d0f0a.rs:10:43 | 10 | print(CString::new("Hello!").unwrap().as_ptr()); | ------------------------------- ^^^^^^ this pointer will be invalid | | | this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime | = note: `#[warn(temporary_cstring_as_ptr)]` on by default = note: pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned = help: for more information, see https://doc.rust-lang.org/reference/destructors.html
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
warning: getting the inner pointer of a temporary `CString` --> src/bin/main_d9386e15d088f5ee58864d5e03ea6d1c7a0bce2f.rs:12:43 | 12 | let ptr = CString::new("Hello!").unwrap().as_ptr(); // временния CString се деалокира | ------------------------------- ^^^^^^ this pointer will be invalid | | | this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime | = note: `#[warn(temporary_cstring_as_ptr)]` on by default = note: pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned = help: for more information, see https://doc.rust-lang.org/reference/destructors.html
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

Автоматично генериране на bindings

Bindgen

Ресурси

Ресурси

Ресурси

Ресурси

Въпроси