Ведение
Сейчас 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.
