Sets up a new Rust/Python hybrid library project using maturin and uv. Creates project structure with PyO3 bindings, installs dev dependencies, and optionally configures git remote. Use when the user wants to initialise a Python library with Rust extensions.
This skill scaffolds a Rust/Python hybrid library project using maturin and uv in the current working directory. It creates a mixed layout where Rust code lives in src/ and Python code lives in python/.
Ask the user for:
Verify prerequisites are installed (auto-install maturin if missing):
command -v maturin >/dev/null || pipx install maturin
command -v uv >/dev/null || echo "uv not found"
command -v cargo >/dev/null || echo "cargo not found"
Get the project name from the current directory and check it's not a reserved Rust keyword:
PROJECT_NAME=$(basename "$(pwd)")
Reserved names that cannot be used: test, main, std, core, self, super, crate, Self, proc_macro
If the directory name is reserved, use AskUserQuestion to prompt for an alternative project name. The alternative name will be used for the package while keeping the directory name unchanged.
Use a temp directory to scaffold (maturin refuses to create in existing directories):
TEMP_DIR=$(mktemp -d)
maturin new "$TEMP_DIR/$PROJECT_NAME" --bindings pyo3 --name "$PROJECT_NAME"
cp -r "$TEMP_DIR/$PROJECT_NAME"/. .
rm -rf "$TEMP_DIR"
This creates:
Cargo.toml - Rust project configurationpyproject.toml - Python project configuration with maturin build backendsrc/lib.rs - Initial Rust code with PyO3 bindingsUpdate pyproject.toml to support a mixed Python/Rust layout:
[project]
name = "<project-name>"
version = "0.1.0"
description = "<description if provided>"
requires-python = ">= 3.<min-version>"
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[tool.maturin]
python-source = "python"
module-name = "<project_name>._<project_name>"
features = ["pyo3/extension-module"]
The module-name setting puts the Rust extension at <package>._<package> so it doesn't conflict with the Python package.
Update Cargo.toml with proper abi3 configuration:
[package]
name = "<project-name>"
version = "0.1.0"
edition = "2021"
[lib]
name = "_<project_name>"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23", features = ["abi3-py3<min-version>", "extension-module"] }
The underscore prefix on the lib name matches the module-name in pyproject.toml.
Create the Python package that wraps the Rust extension:
PROJECT_NAME=$(basename "$(pwd)")
PACKAGE_NAME=$(echo "$PROJECT_NAME" | tr '-' '_')
mkdir -p "python/$PACKAGE_NAME"
Create python/<package>/__init__.py:
"""<project-name> - A Rust/Python hybrid library."""
from ._<package_name> import *
__all__ = [] # Populate with exported names from Rust
Update src/lib.rs with the matching module name:
use pyo3::prelude::*;
/// A simple example function.
#[pyfunction]
fn hello() -> String {
"Hello from Rust!".to_string()
}
/// The Python module definition.
#[pymodule]
fn _<package_name>(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(hello, m)?)?;
Ok(())
}
Note: Do NOT run uv init - maturin already created pyproject.toml. Just use uv add directly:
uv add --dev maturin ipython jupyterlab ruff pytest pytest-cov
Adding maturin as a dev dependency allows uv run maturin develop.
Create directories:
mkdir -p tests
mkdir -p nbs
mkdir -p scratch/scripts
mkdir -p scratch/nbs
touch tests/__init__.py
tests/ - test filesnbs/ - notebooks (tracked in git)scratch/scripts/ - throwaway scripts (ignored)scratch/nbs/ - throwaway notebooks (ignored)Add to .gitignore:
cat >> .gitignore << 'EOF'
scratch/
target/
*.so
*.pyd
EOF
Create a minimal test file tests/test_placeholder.py:
def test_placeholder():
"""Placeholder test to verify pytest works."""
assert True
Create a minimal README using the directory name as the project name:
# <directory-name>
<description if provided>
A Rust/Python hybrid library using maturin and PyO3.
## Development
```bash
just dev # Build and install the extension
just test # Run tests
just check # Format and lint
### Step 10: Create CLAUDE.md
Create a `CLAUDE.md` with project context:
```markdown
# <project-name>
Rust/Python hybrid library using maturin for building and uv for Python dependency management.
## Commands
Run `just` to see available commands.
Key commands:
- `just dev` - Build and install the Rust extension for development
- `just test` - Run tests (runs `dev` first)
- `just check` - Format and lint both Python and Rust code
## Project Structure
- `src/` - Rust source code (the extension module)
- `python/<package>/` - Python source code (wraps Rust extension)
- `tests/` - test files
- `nbs/` - notebooks (tracked)
- `scratch/` - throwaway scripts and notebooks (gitignored)
## Dependencies
- Add Python deps: `uv add <package>`
- Add dev deps: `uv add --dev <package>`
- Add Rust deps: Edit `Cargo.toml`
## Workflow
1. Edit Rust code in `src/lib.rs`
2. Run `just dev` to rebuild the extension
3. Import in Python: `from <package> import hello`
The Rust extension is built as `<package>._<package>` and re-exported from `<package>/__init__.py`.
Create a justfile with development commands:
# List available commands