Ведение

Сейчас 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.

Комментарии (1)


  1. Nuflyn
    27.11.2025 07:42

    Связка maturin с PyO3 очень хорошо сделана. И они на очень зрелой стадии разработки и бойлерплейта с обеих сторон не сильно много получается и консольного шаманства.