DEV Community

Mario García
Mario García

Posted on

Integrar Rust a tus proyectos Python

Uno de los aspectos que siempre me ha parecido interesante de Rust, además de las ventajas y características por las que desarrolladores y empresas están apostando por este, es lo bien que se integra con otros lenguajes y tecnologías, como con Python usando PyO3 o Node.js gracias a Neon.

Empecé a aprender Rust a principios de 2016, en aquella época ya desarrollaba con Python y enseñaba programación en una universidad local. Desde entonces tenía en mi lista de pendientes aprender sobre su integración con otras tecnologías.

Hace una semana estuve trabajando en una nueva interfaz de usuario (UI) y realizando correcciones y mejoras al código de una aplicación que estoy desarrollando con Rust y Rocket, cuya primera versión estaba escrita en Python y de la cual hablé en un artículo anterior.

Con la idea de retomar un proyecto en el que estuve trabajando a finales de 2017, una aplicación desarrollada con Python y Flask, empecé a redactar el plan para hacer la migración a Rust, sin embargo, existen algunas bibliotecas para las que no existe una crate que la reemplace.

La idea era migrar la aplicación a Rust y reescribir todo el código, pero al encontrar este problema, vi la oportunidad de aprender a integrar ambos lenguajes usando PyO3, y asi mantener parte del código en Python y reescribir el resto del código con Rust.

En este breve artículo contaré lo que aprendí el fin de semana sobre como usar PyO3 para integrar Rust con tus proyectos Python.

PyO3

PyO3 es un crate que permite la integración entre Python y Rust. Puede usarse para escribir módulos de Python, asi como para ejecutar e interactuar con código de Python desde Rust. PyO3 funciona con la versión Nightly de Rust.

Uso

De acuerdo al repositorio de GitHub, PyO3 soporta Python 3.5 en adelante. La versión mínima de Rust requerida es la 1.34.0 (Nightly) del 6 de Febrero.

Se puede escribir un módulo nativo de Python con Rust o usar Python desde un binario de Rust.

En algunos sistemas operativos se necesita instalar algunos paquetes adicionales, como en Ubuntu que se instalan los siguientes:

$ sudo apt install python3-dev python-dev
Enter fullscreen mode Exit fullscreen mode

Configuración

  • Instalar Rust Nightly con rustup si no se tiene instalado.
$ rustup install nightly
Enter fullscreen mode Exit fullscreen mode
  • Crear un nuevo proyecto con Cargo.
$ cargo new hello_python
Enter fullscreen mode Exit fullscreen mode
  • Asignar Nightly al proyecto creado.
$ cd hello_python
$ rustup override set nightly
Enter fullscreen mode Exit fullscreen mode

Usando Rust desde Python

PyO3 puede usarse para generar un módulo nativo de Python. El siguiente ejemplo corresponde a una función que dará formato a la suma de dos números como cadena.

Editar el archivo Cargo.toml, agregar PyO3 como dependencia e indicarle a Cargo que se está creando una biblioteca.

Cargo.toml

[package]
name = "string-sum"
version = "0.1.0"
edition = "2018"

[lib]
name = "string_sum"
crate-type = ["cdylib"]

[dependencies.pyo3]
version = "0.6.0-alpha.4"
features = ["extension-module"]
Enter fullscreen mode Exit fullscreen mode

src/lib.rs

Crear el archivo lib.rs y colocar el código correspondiente.

// Not required when using Rust 2018
extern crate pyo3;

use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

#[pyfunction]
/// Formats the sum of two numbers as string
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

/// This module is a python module implemented in Rust.
#[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(sum_as_string))?;

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Se importa el crate de PyO3 mediante extern crate pyo3;. Esta línea es opcional si se está usando la edición 2018 de Rust.

Se indican las funciones y macros que se usarán.

Se declara la función sum_as_string que realizará la suma de dos números y convertirá a cadena el resultado.

Se declara la función string_sum que corresponde al módulo de Python y dentro de esta se indica que la función sum_as_string podrá ser llamada una vez que el módulo sea importado dentro del script de Python.

En Windows y GNU/Linux se puede compilar normalmente con cargo build --release. En MacOS se deben indicar argumentos adicionales. Una opción es compilar con cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup, la otra es crear el archivo .cargo/config con el siguiente contenido:

[target.x86_64-apple-darwin]
rustflags = [
  "-C", "link-arg=-undefined",
  "-C", "link-arg=dynamic_lookup",
]
Enter fullscreen mode Exit fullscreen mode

Una vez que termina la compilación, se copia y renombra la biblioteca compartida desde el directorio target. En MacOS, renombrar libstring_sum.dylib a string_sum.so, en Windows libstring_sum.dll a string_sum.pyd y en GNU/Linux libstring_sum.so a string_sum.so.

En el directorio donde se ha copiado la biblioteca, se puede ejecutar una shell interactiva de Python e importar el módulo con import string_sum.

>>> import string_sum
>>> a = 15
>>> b = 17
>>> s = string_sum.sum_as_string(a, b)
>>> print(s)
32
Enter fullscreen mode Exit fullscreen mode

Para compilar, probar y publicar el crate como módulo de Python, se puede usar pyo3-pack o setuptools-rust.

sum.py

El módulo también puede usarse desde un script de Python.

import string_sum
a = 15
b = 17
s = string_sum.sum_as_string(a, b)
print(s)
Enter fullscreen mode Exit fullscreen mode
$ python sum.py
32
Enter fullscreen mode Exit fullscreen mode

Usando Python desde Rust

El siguiente ejemplo muestra el valor de sys.version.

Editar el archivo Cargo.toml y agregar PyO3 como dependencia

Cargo.toml

...

[dependencies]
pyo3 = "0.6.0-alpha.4"
Enter fullscreen mode Exit fullscreen mode

src/main.rs

Editar el archivo main.rs y colocar el código correspondiente que mostrará el valor de sys.version.

// Not required when using Rust 2018
extern crate pyo3;

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

fn main() -> PyResult<()> {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let sys = py.import("sys")?;
    let version: String = sys.get("version")?.extract()?;
    let locals = [("os", py.import("os")?)].into_py_dict(py);
    let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
    let user: String = py.eval(code, None, Some(&locals))?.extract()?;
    println!("Hello {}, I'm Python {}", user, version);
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Ejecutar la aplicación con cargo run.

Exportar SVG a PDF usando CairoSVG

Para el proyecto en el que estoy trabajando, debo exportar una imagen en formato SVG a PDF, usando CairoSVG, que solo está disponible para Python.

El siguiente código muestra como usar CairoSVG desde Rust.

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

fn main() -> PyResult<()> {
    let gil = Python::acquire_gil();
    let py = gil.python();

    let svg_file = "img.svg";
    let pdf_file = "doc.pdf";

    let locals = [("cairosvg", py.import("cairosvg")?)].into_py_dict(py);
    let code = "cairosvg.svg2pdf(url='".to_owned()+svg_file+"', write_to='"+pdf_file+"')";
    py.eval(&code, None, Some(&locals))?;

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Espero esta breve introducción sea de utilidad. Contaré en un próximo artículo cual ha sido mi experiencia usando PyO3 en la aplicación que estoy desarrollando.

Top comments (1)

Collapse
 
viktorvillalobos profile image
Víctor Villalobos

Interesante, gracias por compartir..