garaga
  • Garaga documentation
  • Installation
    • Python package
    • Rust Crate
    • Npm package
  • Developer setup & guides
    • Working with auto-generated Cairo Code
    • garaga-rs crate
      • Rust -> Python bindings
      • Rust -> Wasm bidings
  • Using garaga libraries in your Cairo project
    • ECDSA & Schnorr Signatures
    • Hashing functions
  • Deploy your own SNARK verifier on Starknet
    • Groth16
      • Generate and deploy your verifier contract
      • Generating calldata from a proof and using your deployed contract
        • Using Python/Garaga CLI
        • Using Rust
        • Using Typescript
    • Noir
  • Maintained Smart Contracts
    • RiscZero
    • SP1
    • Drand
  • Support
  • Updating these docs
Powered by GitBook
On this page
  • Overview
  • Adding a new binding
  • Type Conversions
  • Common Type Conversions
  • Error Handling
  • Common Patterns
  • Example Implementation
  • Registering Your Function
  • Using the Bindings
  • Testing
  • Common Pitfalls
  • Available Utilities
  • Building and Testing
  • Best Practices

Was this helpful?

  1. Developer setup & guides
  2. garaga-rs crate

Rust -> Python bindings

This guide explains how to add new Python bindings for Rust functions in the garaga_rs crate.

Overview

The Python bindings are implemented using PyO3, which provides a safe and ergonomic way to create Python extensions in Rust. The bindings are located in the tools/garaga_rs/src/python_bindings directory.

Adding a new binding

  1. Create a new file in tools/garaga_rs/src/python_bindings/ for your binding

  2. Add the module to mod.rs

  3. Implement your binding function

  4. Register the function in the garaga_rs module

Type Conversions

Common Type Conversions

  1. Python Int -> Rust BigUint

let py_int: &Bound<'_, PyInt> = ...;
let biguint: BigUint = py_int.extract()?;
  1. Rust BigUint -> Python Int

let biguint: BigUint = ...;
Ok(biguint.into_pyobject(py)?.into())
  1. Python List -> Rust Vec

let py_list: &Bound<'_, PyList> = ...;
let values: Vec<BigUint> = py_list
    .into_iter()
    .map(|x| x.extract())
    .collect::<Result<Vec<BigUint>, _>>()?;
  1. Python Bytes -> Rust Bytes

let py_bytes: &Bound<'_, PyBytes> = ...;
let bytes = py_bytes.as_bytes();
  1. Field Elements

// Python Int -> Field Element
let fe = element_from_biguint::<YourField>(&biguint);

// Field Element -> Python Int
let biguint = element_to_biguint(&fe);

Error Handling

  1. Converting Rust Errors to Python

// For custom errors
.map_err(PyErr::new::<pyo3::exceptions::PyValueError, _>)?;

// For simple error propagation
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?
  1. Enum Conversion

let value = YourEnum::try_from(py_value)
    .map_err(PyErr::new::<pyo3::exceptions::PyValueError, _>)?;

Common Patterns

  1. Function Declaration

#[pyfunction]
#[allow(clippy::too_many_arguments)]  // If needed for complex functions
pub fn your_function(
    py: Python,
    // ... parameters ...
) -> PyResult<PyObject> {
    // Implementation
}
  1. Returning Results

// For single values
Ok(result.into_pyobject(py)?.into())

// For lists
let py_list = PyList::new(py, result);
Ok(py_list?.into())

// For tuples
Ok(PyTuple::new(py, &[x, y]).into())

Example Implementation

Here's a complete example showing common patterns:

use pyo3::prelude::*;
use pyo3::types::{PyInt, PyList, PyBytes};
use num_bigint::BigUint;

#[pyfunction]
pub fn example_function(
    py: Python,
    input_int: &Bound<'_, PyInt>,
    input_list: &Bound<'_, PyList>,
    input_bytes: &Bound<'_, PyBytes>,
) -> PyResult<PyObject> {
    // Convert Python int to BigUint
    let value: BigUint = input_int.extract()?;

    // Convert Python list to Vec
    let values: Vec<BigUint> = input_list
        .into_iter()
        .map(|x| x.extract())
        .collect::<Result<Vec<BigUint>, _>>()?;

    // Get bytes
    let bytes = input_bytes.as_bytes();

    // Do your computation
    let result = process_data(&value, &values, bytes)
        .map_err(PyErr::new::<pyo3::exceptions::PyValueError, _>)?;

    // Return as Python list
    let py_list = PyList::new(py, result);
    Ok(py_list?.into())
}

Registering Your Function

Add your function to the garaga_rs module in mod.rs:

#[pymodule]
fn garaga_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(your_function, m)?)?;
    Ok(())
}

Using the Bindings

After implementing and registering your binding, you can use it in Python like this:

from garaga import garaga_rs

# For simple integer operations
result = garaga_rs.example_function(
    input_int=42,
    input_list=[1, 2, 3],
    input_bytes=b"some bytes"
)

Testing

  1. Add Python tests in tests/hydra/

  2. Test both successful and error cases

  3. Test edge cases (empty lists, zero values)

Common Pitfalls

  1. Always use PyResult for error handling

  2. Remember to propagate errors with ?

  3. Handle edge cases (e.g., zero values, maximum values)

  4. Be careful with memory management for large data structures

Available Utilities

The following utilities are available in the codebase:

  • element_from_biguint: Convert BigUint to Field Element

  • element_to_biguint: Convert Field Element to BigUint

  • biguint_to_u256: Convert BigUint to u256

Building and Testing

  1. Build/Update the Rust extension:

maturin develop --release
  1. Run tests:

pytest -xs

Best Practices

  1. Keep bindings simple and focused

  2. Use appropriate error handling

  3. Test edge cases thoroughly

  4. Follow existing patterns in the codebase

Previousgaraga-rs crateNextRust -> Wasm bidings

Last updated 1 month ago

Was this helpful?