453 lines
35 KiB
Markdown
453 lines
35 KiB
Markdown
ОР - Умеет собирать статические и динамические библиотеки из Rust кода, и экспортировать функции из них. А также проверять экспортируемые символы с помощью системных утилит (например, nm).
|
||
ОР - Умеет описывать Си ABI в Rust коде. (с точки зрения best practice)
|
||
ОР - Умеет линковать Си библиотеки к Rust коду (статически, динамически и в рантайме - все три варианта)
|
||
ОР - Понимает, как использовать Rust библиотеку в других языках (например, в python) и понимает, для чего это может быть полезно.
|
||
ОР - Умеет использовать bindgen и cc для генерации Rust API из Си header файлов и сборки Си библиотеки (https://github.com/DaveGamble/cJSON).
|
||
|
||
- [x] Умеет собирать статические и динамические библиотеки из Rust кода, и экспортировать функции из них. А также проверять экспортируемые символы с помощью системных утилит (например, nm).
|
||
- [x] Умеет описывать Си ABI в Rust коде. (с точки зрения best practice)
|
||
- [x] Умеет линковать Си библиотеки к Rust коду (статически, динамически и в рантайме - все три варианта)
|
||
- [x] Понимает, как использовать Rust библиотеку в других языках (например, в python) и понимает, для чего это может быть полезно.
|
||
- [x] Умеет использовать bindgen и cc для генерации Rust API из Си header файлов и сборки Си библиотеки (https://github.com/DaveGamble/cJSON).
|
||
- [x] Умеет импользовать bindgen и cc для генерации Rust API из Си header файлов
|
||
- [x] Практика: сборка Си библиотеки (https://github.com/DaveGamble/cJSON).
|
||
|
||
- [x] Прописать ОРы для глав ✅ 2025-11-17
|
||
- [x] Придумать квизы ✅ 2025-11-17
|
||
|
||
старт
|
||
- [ ] Сначала практически расписать бест практис с с аби,
|
||
- [x] потом рассказать про биндген
|
||
- [x] потом сс
|
||
- [x] потом линковка
|
||
- [x] ??а если мы захотим уже раст использовать как бблиотеку в другом языке?
|
||
- [x] рассказать низкоуровнево как прописывать экспорт раст кода
|
||
- [x] (упомянуть про как посмотреть экспорт)
|
||
- [x] в том числе и про разные способы линковки
|
||
- [x] потом показать на примере линковки к питону
|
||
- [ ] потом рассказать про бест практис (возможно решив какую-нибудь проблему в предыдущей главе)
|
||
## C ABI Best practice
|
||
- [x] Рассказать немного про бест практис, протом перейти к bindgen
|
||
- [x] best practice:
|
||
- [x] use std::ffi types
|
||
- [x] use \*-sys
|
||
- [x] Добавление поля links в Cargo.toml крейта \*-sys
|
||
- [x] use bindgen
|
||
- [x] генерация с build.rs (build dependencies)
|
||
- [ ] Option nonnull for c abi (null optimization)
|
||
?cstr
|
||
## Линковка
|
||
Рассказать про то, как низкоуровнево линкуется при помощи rustc, потом рассказать про то, как линковать с build.rs и потом cc
|
||
# Start
|
||
|
||
В прошлом уроке вы узнали, как описывается C ABI в Rust. Давайте теперь посмотрим, как используется C ABI в production коде. Для этого изучим best practice использования C ABI.
|
||
|
||
## Правильное использование C ABI
|
||
ОР - Умеет описывать Си ABI в Rust коде. (с точки зрения best practice)
|
||
### Совместимость начинается со стандартов
|
||
Допустим, у нас есть такая волшебная функция на С:
|
||
```c
|
||
int square(int x) {
|
||
return x * x;
|
||
}
|
||
```
|
||
Как описать эту функцию в Rust? Возможно так?
|
||
```rust
|
||
unsafe extern "C" {
|
||
fn square(x: i32) -> i32;
|
||
}
|
||
```
|
||
Но тут возникает проблема: в rust явно указана размерность в 32 бита, в то время как в C, int может быть не только размером 32 бита (к примеру у avr, той самой, которая используется в Arduino, размер int 16 бит). Для таких исключений есть модуль core::ffi (который реэкспортирован как std::ffi, так что его можно встретить с импортом по этому пути, но рекомендуется использовать именно по пути core от для поддержки {{no_std}}[https://docs.rust-embedded.org/book/intro/no-std.html] среды). И для описания функций рекомендуется использовать именно эти типы, так как в rust может добавиться поддержка новой архитектуры, со своими типами из C, и программа может незаметно стать памяти-небезопасной.
|
||
Перепишем функцию:
|
||
```rust
|
||
use core::ffi::c_int;
|
||
|
||
unsafe extern "C" {
|
||
fn square(x: c_int) -> c_int;
|
||
}
|
||
```
|
||
Заметка (extern без unsafe):
|
||
(может быть под тогл/скрываемый блок?)
|
||
Если читать код библиотек, можно встретить, что extern пишется без unsafe (взаимодействие и смысл этого ключевого слова вы изучите в следующем уроке). Так можно было делать раньше, но в rust это стало обязательным с версии 1.85. Идея заключается в том, что пишущий код человек может задекларировать функцию неправильно, и это обязанность программиста, а не компилятора соблюсти здесь безопасность по памяти (пример такой ситуации вы только что разобрали).
|
||
### Optional pointer (null optimization) - нужно ли?
|
||
### Отделение взаимодействия с ffi в отдельный крейт
|
||
Если вы пишете библиотеку, взаимодействующую с C ABI, отделяйте это взаимодействие в отдельный крейт (который обычно называют \*-sys). На это есть несколько веских {{причин}}[https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages]:
|
||
- Несколько разных библиотек могут переиспользовать уже написанный код для взаимод
|
||
- ействия с библиотекой
|
||
- Так, одна и та же библиотека не будет собираться несколько раз и не будет слинкована несколько раз (то есть не будет существовать несколько разных или одинаковых версий библиотеки в бинаре)
|
||
- Легкость изменения библиотеки, от которой зависит бинарь (версии, поиска ее пути и всей остальной конфигурации)
|
||
|
||
Квиз - множественный выбор
|
||
Зачем отделять FFI-взаимодействие в отдельный \*-sys крейт?
|
||
|
||
Чтобы ускорить выполнение сборки программы с нуля.
|
||
Нет. Это не ускорит сборку конченой программы. Но, будет полезно, если разрабатываете библиотеку, ведь при каждом изменении не будет выполнятся build скрипт с линковкой.
|
||
|
||
Чтобы ускорить выполнение программы во время рантайма.
|
||
Нет, отделение логики не ускорит программу.
|
||
|
||
Чтобы переиспользовать взаимодействие с библиотекой.
|
||
Правильно, при отделении логики в -sys крейт, ее смогут переиспользовать другие крейты. Так же, как и при отделении логики в фукнцию, которую можно использовать в другом месте кода
|
||
|
||
Чтобы не собирать статическую библиотеку несколько раз.
|
||
Правильно, при переиспользовании -sys крейта статические библиотеки, которые собираются из исходного кода, будут собраны и включены в бинарь только один раз, вместо сборки в каждом использующем библиотеку крейте
|
||
|
||
Чтобы было проще обновлять или перенастраивать зависимую C-библиотеку (версия, путь, параметры сборки).
|
||
Правильно, программист сможет изменить используемую библиотеку через аттрибут {{target.\<triple>.\<links>}}[https://doc.rust-lang.org/cargo/reference/config.html#targettriplelinks] в файле настроек cargo (./cargo/config.toml)
|
||
|
||
Чтобы избежать необходимости писать unsafe-код в основном проекте.
|
||
Нет. Хоть и часть unsafe логики отделяется в этот крейт (а именно декларация функций), взаимодействие с этими функциями все еще будет не безопасным
|
||
### Добавление поля links в Cargo.toml крейта \*-sys
|
||
При написании крейта, который линкуется с одной библиотекой, используйте {{links}}[https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key] в манифесте Cargo.toml, для указания, с какой библиотекой происходит линковка:
|
||
```toml
|
||
# Cargo.toml
|
||
[package]
|
||
links = "mylib"
|
||
```
|
||
Благодаря этому, программисты, в коде которых будет использоваться такой крейт, смогут {{перезаписать использование build скрипта}}[https://doc.rust-lang.org/cargo/reference/build-scripts.html#overriding-build-scripts], благодаря чему мы и получим третий плюс из предыдущего пункта
|
||
### Автоматическая генерация extern внешних функций
|
||
ОР - Умеет использовать bindgen и cc для генерации Rust API из Си header файлов и сборки Си библиотеки (https://github.com/DaveGamble/cJSON).
|
||
С обновлением версий библиотек, меняется и их интерфейс. К примеру, могут добавиться новые функции. Но поддерживать актуальные декларации внешних функций (так называемые {{байндинги}}[на английском термин binding, исходит от to bind, который переводится как связывать. говорит компилятору языка, что у нас существует такая функция, а линкеру объясняет, с какой функцией надо связывать]) вручную весьма трудозатратная задача (как и в принципе изначальное написание таких байндингов вручную). На помощь приходит автоматическая генерация при помощи крейта {{bindgen}}[https://github.com/rust-lang/rust-bindgen].
|
||
Одним из вариантов использования bindgen является его cli. Установите его:
|
||
```bash
|
||
cargo install bindgen-cli
|
||
```
|
||
Создайте новый проект, запишите следующее в файлы:
|
||
```rust
|
||
// src/main.rs
|
||
include!("./bindgen.rs");
|
||
|
||
fn main() {
|
||
println!("Squared eleven: {}", unsafe { square(11) });
|
||
}
|
||
```
|
||
```c
|
||
// src/mylib.c
|
||
int square(int x) {
|
||
return x * x;
|
||
}
|
||
```
|
||
```c
|
||
// src/mylib.h
|
||
int square(int x);
|
||
```
|
||
теперь используем bindgen для генерации байндингов:
|
||
```bash
|
||
bindgen src/mylib.h -o src/bindgen.rs
|
||
```
|
||
можете посмотреть в файл `src/bindgen.rs`, и увидеть там аналогичную декларацию функции, что вы писали, но сгенерированную автоматически :)
|
||
Теперь соберем C библиотеку:
|
||
```bash
|
||
clang -c src/mylib.c -o mylib.o
|
||
```
|
||
*Вместо clang можно использовать и gcc (и, теоретически, любой другой C компилятор), но так как rust зависит от инфраструктуры llvm, все примеры будут именно для clang*
|
||
И соберем и запустим программу:
|
||
```bash
|
||
RUSTFLAGS="-L. -l./mylib.o" cargo run
|
||
```
|
||
*Переменная среды RUSTFLAGS позволяет добавить флаги, которые будут переданы rustc. В данном случае переданы флаги, которые говорят включить в сборку вашу библиотеку. То, как обычно это делается (через скрипт сборки и через cc) будет разобрано чуть позже в этом уроке.*
|
||
|
||
Ваша программа успешно запустилась и выдала ожидаемый результат:
|
||
```
|
||
Squared eleven: 121
|
||
```
|
||
## Автоматизация сборки
|
||
ОР - Умеет линковать Си библиотеки к Rust коду (статически, динамически и в рантайме - все три варианта)
|
||
Для того, чтобы не делать все это вручную, в cargo есть возможность написать свой скрипт сборщика, который выполнит нужные нам действия перед компиляцией и передаст cargo инструкции для компиляции кода. Обычно такой код расположен в файле build.rs (но путь к нему можно изменить через атрибут {{build}}[https://doc.rust-lang.org/cargo/reference/manifest.html#the-build-field] в Cargo.toml).
|
||
Давайте сделаем то же самое, но используя build.rs. Для начала, подчистим выхлоп предыдущего эксперимента:
|
||
```bash
|
||
rm src/bindgen.rs # но пока оставим mylib.o
|
||
```
|
||
Теперь напишем build.rs (в корне проекта):
|
||
```rust
|
||
// build.rs
|
||
use bindgen;
|
||
use std::{env, path::PathBuf};
|
||
|
||
fn main() {
|
||
let bindings = bindgen::builder()
|
||
// Файл, для которого создаются байндинги
|
||
.header("src/mylib.h")
|
||
// Перезапуск сборки при изменении переданных файлов
|
||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||
// Сгенерировать байндинги
|
||
.generate()
|
||
.expect("Unable to generate bindings");
|
||
|
||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||
bindings
|
||
// Записать получившиеся байндинги в файл OUT_DIR/bindgen.rs
|
||
.write_to_file(out_path.join("bindgen.rs"))
|
||
.expect("Couldn't write bindings!");
|
||
|
||
// Передать линкеру, что библиотеки нужно искать в нынешней папке
|
||
// Аналогичен флагу -L.
|
||
// Информация в cargo передается через stdout
|
||
println!("cargo::rustc-link-search=.");
|
||
// Передать линкеру, что нужно слинковать с библиотекой ./mylib.o
|
||
// Аналогичен флагу -l./mylib.o
|
||
println!("cargo::rustc-link-lib=./mylib.o");
|
||
}
|
||
```
|
||
Как вы тут заметили, вместо вызова cli тут используется вызов крейта bindgen. Это рекомендуемый способ использования bindgen, и cli на практике используется редко. Чтобы иметь возможность использовать его в сборочном скрипте, добавьте его в зависимости для сборки:
|
||
```bash
|
||
cargo add bindgen --build
|
||
```
|
||
Так же, вместо выхлопа bindgen.rs в папку src/ выходной файл будет находиться внутри папки {{OUT_DIR}}[https://doc.rust-lang.org/cargo/reference/environment-variables.html#:~:text=.exe.-,OUT_DIR,-%E2%80%94%20If%20the%20package]. Это именно та папка, куда build.rs должен ложить все свои выходные файлы. Ни в коем случае не надо ложить файлы в src! Так как выходные файлы не являются сами по себе частью проекта, и являются промежуточным результатом компиляции, то и находится они должны в одной из подпапок target, а именно в OUT_DIR.
|
||
Так как путь к bindgen.rs изменился, замените предыдущий include! в src/main.rs на такой:
|
||
```rust
|
||
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
||
```
|
||
Попробуем запустить все это:
|
||
```bash
|
||
cargo run
|
||
```
|
||
Байндинги автоматически были созданы и линковка тоже произошла, осталось добавить сборку C кода в библиотеку.
|
||
## Сброка C кода в Rust
|
||
ОР - Умеет использовать bindgen и cc для генерации Rust API из Си header файлов и сборки Си библиотеки (https://github.com/DaveGamble/cJSON).
|
||
Так же, как и для генерации байндингов существует bindgen, для компиляции библиотек существует крейт`cc`. Фактически, он под капотом вызывает компилятор C со всеми задаными флагами. Добавим его в зависимости для сборки:
|
||
```bash
|
||
cargo add cc --build
|
||
```
|
||
И обновим build.rs:
|
||
```rust
|
||
// build.rs
|
||
use bindgen;
|
||
use cc;
|
||
use std::{env, path::PathBuf};
|
||
|
||
fn main() {
|
||
let bindings = bindgen::builder()
|
||
// Файл, для которого создаются байндинги
|
||
.header("src/mylib.h")
|
||
// Перезапуск сборки при изменении переданных файлов
|
||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||
// Сгенерировать байндинги
|
||
.generate()
|
||
.expect("Unable to generate bindings");
|
||
|
||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||
bindings
|
||
// Записать получившиеся байндинги в файл OUT_DIR/bindgen.rs
|
||
.write_to_file(out_path.join("bindgen.rs"))
|
||
.expect("Couldn't write bindings!");
|
||
|
||
cc::Build::new()
|
||
// добавить src/mylib.c в выходную библиотеку
|
||
.file("src/mylib.c")
|
||
// скомпилировать C код как библиотеку libmylib.a в папке OUT_DIR
|
||
.compile("mylib");
|
||
}
|
||
```
|
||
Заметьте, что пропало явное обозначение, что нужно слинковать с библиотекой. cc сам передаст cargo название библиотеки, с которой нужно слинковать.
|
||
Перед запуском, для чистоты эксперимента, удалите старый файл:
|
||
```bash
|
||
rm mylib.o
|
||
```
|
||
Теперь сборку и запуск можно делать просто через `cargo run`, не выполняя лишних команд.
|
||
## Линковка с динамической библиотекой
|
||
До этого мы собирали только со статической библиотекой. Теперь, давайте слинкуем с динамической библиотекой. Для этого, сначала соберем ее:
|
||
```bash
|
||
clang src/mylib.c -shared -o libmylib.so
|
||
```
|
||
А теперь, в build.rs уберите использование cc и добавьте в конце функции main:
|
||
```rust
|
||
fn main() {
|
||
// Генерация байндингов
|
||
// ...
|
||
|
||
println!("cargo:rustc-link-search=.");
|
||
println!("cargo:rustc-link-lib=dylib=mylib");
|
||
}
|
||
```
|
||
В cargo:rustc-link-lib передано значение dylib=mylib. Оно позволяет явно указать, что нужно подгрузить динамическую библиотеку (dylib) с название mylib (у такой библиотеки будет название libmylib.so на примере linux). Значение \[тип=] опционально и может быть одним из:
|
||
- dylib - динамическая библиотека
|
||
- static - статическая библиотека
|
||
- framework - специфичный формат для MacOS, содержащий динамичекую библиотеку с дополнительными ресурсами
|
||
cc не поддерживает генерацию динамических библиотек, так как она не будет являться частью создаваемого cargo и rustc бинаря. Динамические библиотеки обычно являются частью системы (те же libc, zlib, openssl, присутствие которых бинари будут ожидать по стандартным путям). Установлены они могут быть, к примеру, через apt, pacman, установщики windows, и так далее. Либо создаются отдельно и поставляются с программой.
|
||
## Линковка в рантайме
|
||
ОР - Умеет линковать Си библиотеки к Rust коду (статически, динамически и в рантайме - все три варианта)
|
||
Последний способ линковки в рантайме, позволяет не тратить лишний раз оперативную память, загружая код лишь только тогда, когда он нужен. Для независимости от платформы будем использовать dlopen и dlsym.
|
||
- dlopen - подгружает нужную нам библиотеку
|
||
- dlsym - ищет в библиотеке нужный символ и выдает его адрес, и это может быть не только функция, но и, к примеру, константа, статическая переменая, и тд.
|
||
Для начала, удалите build.rs. Добавьте libc как зависимость.
|
||
Напишите в src/main.rs:
|
||
```rust
|
||
// src/main.rs
|
||
use libc::{dlopen, dlsym};
|
||
use std::ffi::c_int;
|
||
|
||
fn main() {
|
||
let mylib = unsafe { dlopen(c"./libmylib.so".as_ptr(), 0) };
|
||
let square: extern "C" fn(c_int) -> c_int =
|
||
unsafe { std::mem::transmute(dlsym(mylib, c"square".as_ptr())) };
|
||
println!("Squared eleven: {}", unsafe { square(11) });
|
||
}
|
||
```
|
||
Запустив это, получите ровно тот же результат, что и раньше, но теперь библиотеку мы подгружаем сами. Кстати, линкер для динамических библиотек делает то же самое, но подгружает библиотеки еще до вызова main, а адреса функций кладет в заранее заготовленную таблицу функций.
|
||
|
||
## Практика
|
||
Создайте новый проект, с таким содержимым в src/main.rs:
|
||
```rust
|
||
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
||
|
||
use std::ffi::{CStr, CString};
|
||
|
||
const TEST_JSON: &CStr = c"{
|
||
\"meaning_of_life\": 42
|
||
}";
|
||
|
||
fn main() {
|
||
let json: *mut cJSON = unsafe { cJSON_Parse(TEST_JSON.as_ptr()) };
|
||
|
||
let json_str = unsafe { cJSON_PrintUnformatted(json) };
|
||
let json_str = unsafe { CString::from_raw(json_str) };
|
||
let json_str = json_str.to_str().unwrap();
|
||
assert_eq!(json_str, r#"{"meaning_of_life":42}"#);
|
||
|
||
let meaning_of_life = unsafe { cJSON_GetObjectItem(json, c"meaning_of_life".as_ptr()) };
|
||
let meaning_of_life = unsafe { cJSON_GetNumberValue(meaning_of_life) };
|
||
println!("Meaning of life: {}", meaning_of_life);
|
||
assert_eq!(meaning_of_life, 42f64);
|
||
}
|
||
```
|
||
Создайте такой build.rs, чтобы этот код заработал. Используйте библиотеку [cJSON](https://github.com/DaveGamble/cJSON), ее можно склонировать прямо в корень проекта.
|
||
Подсказки:
|
||
Экспорт функций можно сгенерировать от cJSON.h используя bindgen
|
||
Чтобы была возможность использовать код из cJSON, соберите библиотеку через cc
|
||
Байндинги записываются в bindgen.rs в папке, указанной в переменной среды OUT_DIR
|
||
Чек-лист:
|
||
Линкуется cJSON как статическая библиотека
|
||
Файл bindgen.rs с байндингами находится в папке, указанной в переменной среды OUT_DIR
|
||
Тесты проходят
|
||
Решение:
|
||
```rust
|
||
use bindgen;
|
||
use cc;
|
||
use std::{env, path::PathBuf};
|
||
|
||
fn main() {
|
||
let bindings = bindgen::builder()
|
||
.header("cJSON/cJSON.h")
|
||
.generate()
|
||
.expect("Unable to generate bindings");
|
||
|
||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||
bindings
|
||
.write_to_file(out_path.join("bindgen.rs"))
|
||
.expect("Couldn't write bindings!");
|
||
|
||
cc::Build::new()
|
||
// добавить src/mylib.c в выходную библиотеку
|
||
.file("cJSON/cJSON.c")
|
||
// скомпилировать C код как библиотеку libmylib.a в папке OUT_DIR
|
||
.compile("cJSON");
|
||
}
|
||
```
|
||
|
||
## Rust как библиотека
|
||
ОР - Умеет собирать статические и динамические библиотеки из Rust кода, и экспортировать функции из них. А также проверять экспортируемые символы с помощью системных утилит (например, nm).
|
||
ОР - Понимает, как использовать Rust библиотеку в других языках (например, в python) и понимает, для чего это может быть полезно.
|
||
В течении всего урока мы использовали C код из Rust кода. А что если нужно будет использовать rust код из других языков, к примеру, того же C, Go, или Python? И это тоже возможно, через тот же C ABI, но на экспорт. Такая функция декларируется так:
|
||
```rust
|
||
#[unsafe(no_mangle)]
|
||
pub extern "C" fn doublefast(x: u32) -> u32 {
|
||
x << 1
|
||
}
|
||
```
|
||
- no_mangle - говорит rust, что название функции не надо преобразовывать
|
||
- extern "C" - использовать C ABI
|
||
- pub extern - экспорт функции
|
||
Создайте новый проект как библиотеку:
|
||
```bash
|
||
cargo new --lib mylib
|
||
```
|
||
В Cargo.toml добавьте:
|
||
```toml
|
||
[lib]
|
||
crate-type = ["cdylib"] # cdylib означает, что нужно собрать динамическую библиотеку
|
||
```
|
||
А в src/lib.rs внесите показанный ранее код:
|
||
```rust
|
||
#[unsafe(no_mangle)]
|
||
pub extern "C" fn doublefast(x: u32) -> u32 {
|
||
x << 1
|
||
}
|
||
```
|
||
Соберите библиотеку. Выходным файлом получится target/debug/libmylib.so (В зависимости от вашей ОС расширение .so, используемое в linux, может замениться на .dylib в macos или .dll в windows). Можем проверить наличие символа в библиотеке для функции через nm из пакета binutils:
|
||
```bash
|
||
# nm выведет весь список символов
|
||
# grep покажет только doublefast, если он есть
|
||
nm target/debug/libmylib.so | grep doublefast
|
||
```
|
||
Попробуем использовать нашу функцию, к примеру, такой код в Python:
|
||
```python
|
||
from ctypes import cdll
|
||
mylib = cdll.LoadLibrary("target/debug/libmylib.dylib")
|
||
print(mylib.doublefast(8))
|
||
```
|
||
Успешно выведет 16
|
||
*Информация о том, какие типы функция принимает и выдает не сохраняется в библиотеке, просто по дефолту python считает, что функция принимает и выдает i32*
|
||
Такое применение имеет практическую пользу: аналогичный код на rust выполняется в разы быстрее, чем код на python, поэтому много библиотек для python пишутся на C, а со становления rust популяным многие уже пишутся на rust.
|
||
### Сборка статической библиотеки
|
||
Что собрать то же самое, но в статическую библиотеку, нужно в `crate-type` изменить `cdylib` на `staticlib` (либо можно оставить и то и то, тогда будут собираться обе версии библиотеки):
|
||
```toml
|
||
[lib]
|
||
crate-type = ["staticlib"]
|
||
# или можно сделать такой вариант, тогда соберется
|
||
# два файла с разными расширениями
|
||
# crate-type = ["cdylib", "staticlib"]
|
||
```
|
||
Попробуем использовать статическую библиотеку, но теперь пример для C:
|
||
```c
|
||
// main.c
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
|
||
uint32_t doublefast(uint32_t);
|
||
|
||
int main() {
|
||
printf("Double six: %d\n", doublefast(6));
|
||
}
|
||
```
|
||
Соберем C код с использованием нашей библиотеки:
|
||
```bash
|
||
clang main.c target/debug/libmylib.a -o ./a.exe
|
||
```
|
||
При запуске ./a.exe будет получен ожидаемый результат.
|
||
Для crate-type возможны следующие значения:
|
||
- staticlib - сборка статической библиотеки
|
||
- dylib - сборка динамической библиотеки, предназначенной для использования в rust коде. Собираться использующий код и dylib должны одной и той же версией компилятора, так как у dylib нет стабильного интерфейса
|
||
- cdylib - сборка динамической библиотеки, но предназначеную для использования во всех языках, поэтому отличается от dylib:
|
||
- интерфейсы фукнций следуют c abi
|
||
- в библиотеку будет включена стандартная rust библиотека (rust-std)
|
||
- bin - сборка конечной программ
|
||
- lib - соберет библиотеку, вид которой будет выбран компилятором (стандартное значение для крейтов-библиотек)
|
||
- rlib - внутренний тип библиотек rust
|
||
- proc-macro - крейт, в котором экспортированы proc макросы
|
||
|
||
Квиз - одиночный выбор
|
||
Какое значение нужно указать в crate-type, чтобы использовать ваш rust код как динамическую библитеку в проекте на Go?
|
||
lib
|
||
Нет, lib соберет библиотеку на выбор компилятора
|
||
|
||
cdylib
|
||
Правильно, cdylib соберет динамическую библиотеку с C ABI, который подходит для Go
|
||
|
||
dylib
|
||
Нет, хоть dylib и соберет динамическую библиотеку, но полноценно подходить она будет только для rust кода
|
||
|
||
staticlib
|
||
Нет, staticlib соберет статическую библиотеку
|
||
|
||
|
||
// update
|
||
### cbindgen
|
||
Так же, как и для генерации rust байндингов из C есть библитека bindgen, для генерации C/C++/cython байндингов из rust кода есть cbindgen:
|
||
```bash
|
||
cargo install cbindgen
|
||
```
|
||
|
||
## Итоги
|
||
В этом уроке мы на практике разобрали, как взаимодействовать с кодом на C в проектах на rust, best practice этого взаимодействия, а так же узнали, как можно взаимодействовать с кодом на rust из других языков. Вы научились автоматически генерировать декларации внешних функций, а так же собирать статические библиотеки при сборке проекта на rust.
|
||
Далее вы узнаете, что означает ключевое слово unsafe, зачем оно нужно при взаимодействии с внешними библиотеками, а так же изучите best practice при написании unsafe кода. |