Ведение
Сейчас Rust "на хайпе" и вместе с этим появляются много библиотек и инструментария для Python написанные на Rust. Год назад стал появляться Ruff в моем инфополе, а до этого pydantic выпустил версию 2 с ядром на Rust. Сейчас уже есть uv, который потихоньку теснит Poetry.
На этом фоне я несколько месяцев назад увлекся этим языком. В свободное время изучаю Rust-код в проектах, которые я упоминал выше и не только. А в данной статье покажу, как создать простенькую библиотеку кодирования данных в Base 64.
Подготовка
Устанавливаем Rust
Скрипт для установки (Linux/macOS):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
После этого у вас установятся инструментарий для работы с Rust.
Создаем проект и окружение
mkdir pyrsbase64
cd pyrsbase64
python -m venv .venv
source .venv/bin/activate
После создания окружения нам нужно будет установить утилиту для сборки Python пакетов написанных на Rust - Maturin:
pip install maturin
И с помощью него инициализировать проект:
maturin init -b pyo3
Опция -b pyo3 означает что мы будем использовать библиотеку PyO3 для создания расширения (Maturin также поддерживает создание расширений на Си).
Проект будет иметь следующую структуру:
├── Cargo.lock # Файл заморозки Rust зависимостей
├── Cargo.toml # Конфигурация Rust пакета
├── pyproject.toml # Конфигурация Python пакета
└── src # Папка с Rust-кодом
└── lib.rs # Главный файл пакета Rust (аналог __init__.py Python)
Файл lib.rs уже будет содержать пример кода на Rust:
use pyo3::prelude::*;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust.
#[pymodule]
fn pyrsbase64(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
Вы можете скомпилировать проект и попробовать вызвать функцию:
## Компиляция проекта и установка в окружение .venv
maturin develop
## Вызов python-скрипта
python main.py
Код main.py:
import pyrsbase64
assert '3' == pyrsbase64.sum_as_string(1, 2)
Пишем функцию на Rust
Так как мы пишем библиотеку для работы с Base 64, то давайте установим пакет для кодирования в Base 64 на Rust (написание своей реализации было бы темой для другой статьи).
cargo add base64
Это установит крейт base64. Крейт (от англ. crate - "ящик") - пакеты (библиотеки) в Rust.
Очистим lib.rs и добавим следующий код
use pyo3::prelude::{pyfunction, PyResult};
use base64::engine::general_purpose::STANDARD as base64_standard;
#[pyfunction]
fn b64encode(s: &[u8]) -> PyResult<String> {
return Ok(base64_standard.encode(s))
}
Разбор
Здесь можно сказать, что мы просто написали "прокси"-функцию, которая вызывает функцию из другого пакета Rust.
use pyo3::prelude::*- импортируем, все из модуляpreludeпакетаPyO3#[pyfunction]- применение макроса на функциюb64encode. Применение этого макроса сделает функцию видимой для Python. Макросы в Rust - это инструмент для метапрограммирования (код пишущий другой код).-
fn b64encode(s: &[u8]) -> PyResult<String>fn b64encode- объявление функцииs: &[u8]- функция с параметром в виде ссылки на массив байт. Это позволит нам принимать объект типаbytesиз Python.-> PyResult<String>- функция возвращает результат в видео строки.PyResult- это обертка над типомResultиз Rust. Так как в Rust нетtry-exceptконструкций , то для возврата ошибок используют этот самый типResult
Добавим определение Python-модуля
Для того, чтобы экспортировать функцию из Rust-пакета, которую мы написали ранее, нам нужно будет определить наш модуль и добавить нашу функцию в нее.
При инициализации проекта Maturin позаботился и создал определение нашего модуля в lib.rs. Немного видоизменим его
use pyo3::prelude::pymodule;
#[pymodule]
fn pyrsbase64(m: &Bound<'_, PyModule>) -> PyResult<()>; {
m.add_function(wrap_pyfunction!(b64encode, m)?)?;
Ok(())
}
#[pymodule]- еще один макрос из PyO3 для объявления функцииpyrsbase64, как модуля.m- это сам объект модуляwrap_function- это еще один макрос, в который PyO3 требует обернуть нашу функцию аннотированнуюpyfunction
Собираем проект
На этом наша простая библиотека кодирования в Base 64 готова к использованию в Python. Можем собрать наш проект и попробовать вызывать ее.
Измените main.py
import pyrsbase64
assert "SGVsbG8sIFdvcmxkIQ==" == pyrsbase64.b64encode(b"Hello, World!")
Запускаем скрипт
maturin develop && python main.py
Заключение
Потихоньку многие open-source проекты на Си будут переписываться на Rust, а экосистема Rust в Python будет еще стремительней развиваться. Вскоре Rust войдет в CPython, как язык для встроенных в стандартную библиотеку расширений (см. Pre-PEP). Я надеюсь, что это статья вас увлечет в этой теме и вы будете создавать свои расширения для Python на Rust.
Ресурсы
Библиотека для Python расширений на Rust - PyO3
Мой скромный пет-проект Python расширение на Rust для работы с Base 64.
-
Другие проекты на Rust для Python:
Cryptography для работы с криптографическими алгоритмами;
Polars - альтернатива pandas;
Pydantic Core - ядро Pydantic.
Nuflyn
Связка maturin с PyO3 очень хорошо сделана. И они на очень зрелой стадии разработки и бойлерплейта с обеих сторон не сильно много получается и консольного шаманства.