Writing Rust Libraries for Other Languages: Expanding Rust's Reach

Learn how to create Rust libraries that can be used from other programming languages, expanding the reach of Rust code with examples for Python, Node.js, and C#.

15.2. Writing Rust Libraries for Other Languages

Creating Rust libraries that can be used from other programming languages is a powerful way to leverage Rust’s performance and safety features across different ecosystems. This section will guide you through the process of exposing Rust functions and types to other languages, with examples for Python, Node.js, and C#. We will also discuss data conversion, packaging, distribution, and the performance benefits and limitations of using Rust in this manner.

Introduction

Rust is known for its memory safety, concurrency, and performance. These features make it an attractive choice for building libraries that can be used in other programming languages. By writing Rust libraries for other languages, you can:

  • Leverage Rust’s performance in computationally intensive tasks.
  • Ensure memory safety in applications that require high reliability.
  • Expand Rust’s reach by integrating it into existing ecosystems.

Let’s explore how to create Rust libraries for Python, Node.js, and C#.

Exposing Rust Functions and Types

To expose Rust functions and types to other languages, we need to create bindings that allow the target language to interact with Rust code. This involves:

  1. Defining a Foreign Function Interface (FFI): This is a way for a program written in one language to call functions or use services written in another language.
  2. Handling Data Conversion: Converting data types between Rust and the target language.
  3. Packaging and Distribution: Creating a package that can be easily distributed and used in the target language.

Rust and Python: Using PyO3

PyO3 is a library that allows you to write Python bindings for Rust code. It provides a way to call Rust functions from Python and vice versa.

Example: Creating a Rust Library for Python

Let’s create a simple Rust library that provides a function to add two numbers.

 1use pyo3::prelude::*;
 2
 3/// A Python module implemented in Rust.
 4#[pymodule]
 5fn my_rust_lib(py: Python, m: &PyModule) -> PyResult<()> {
 6    #[pyfn(m, "add")]
 7    fn add_py(a: i32, b: i32) -> PyResult<i32> {
 8        Ok(add(a, b))
 9    }
10    Ok(())
11}
12
13/// Add two numbers.
14fn add(a: i32, b: i32) -> i32 {
15    a + b
16}

Building and Using the Library

  1. Add PyO3 to your Cargo.toml:

    1[dependencies]
    2pyo3 = { version = "0.15", features = ["extension-module"] }
    
  2. Build the library:

    Use maturin or setuptools-rust to build the library as a Python extension module.

  3. Use the library in Python:

    1import my_rust_lib
    2
    3result = my_rust_lib.add(3, 4)
    4print(f"The result is {result}")
    

Data Conversion

PyO3 handles data conversion between Python and Rust automatically for many types. However, for complex types, you may need to implement custom conversion logic.

Rust and Node.js: Using Neon

Neon is a library for writing Node.js bindings in Rust. It allows you to create high-performance Node.js modules with Rust.

Example: Creating a Rust Library for Node.js

Let’s create a simple Rust library that provides a function to multiply two numbers.

 1use neon::prelude::*;
 2
 3fn multiply(mut cx: FunctionContext) -> JsResult<JsNumber> {
 4    let a = cx.argument::<JsNumber>(0)?.value(&mut cx);
 5    let b = cx.argument::<JsNumber>(1)?.value(&mut cx);
 6    Ok(cx.number(a * b))
 7}
 8
 9register_module!(mut cx, {
10    cx.export_function("multiply", multiply)
11});

Building and Using the Library

  1. Add Neon to your Cargo.toml:

    1[dependencies]
    2neon = "0.9"
    
  2. Build the library:

    Use neon build to compile the library as a Node.js addon.

  3. Use the library in Node.js:

    1const myRustLib = require('./my_rust_lib');
    2
    3const result = myRustLib.multiply(3, 4);
    4console.log(`The result is ${result}`);
    

Data Conversion

Neon provides utilities for converting between JavaScript and Rust types. For complex types, you may need to implement custom conversion logic.

Rust and C#: Using Unmanaged Code

To use Rust libraries in C#, you can expose Rust functions as C-style functions and use P/Invoke to call them from C#.

Example: Creating a Rust Library for C#

Let’s create a simple Rust library that provides a function to subtract two numbers.

1#[no_mangle]
2pub extern "C" fn subtract(a: i32, b: i32) -> i32 {
3    a - b
4}

Building and Using the Library

  1. Build the library:

    Use cargo build --release to compile the library as a shared library (.dll on Windows, .so on Linux, .dylib on macOS).

  2. Use the library in C#:

     1using System;
     2using System.Runtime.InteropServices;
     3
     4class Program
     5{
     6    [DllImport("my_rust_lib")]
     7    private static extern int subtract(int a, int b);
     8
     9    static void Main()
    10    {
    11        int result = subtract(7, 4);
    12        Console.WriteLine($"The result is {result}");
    13    }
    14}
    

Data Conversion

When using P/Invoke, you need to ensure that the data types match between Rust and C#. For complex types, you may need to use struct or class to represent the data in C#.

Packaging and Distribution

Once you have created your Rust library, you need to package it for distribution. This involves:

  • Creating a package that includes the compiled library and any necessary metadata.
  • Distributing the package through a package manager or as a standalone download.

For Python, you can use maturin or setuptools-rust to create a Python package. For Node.js, you can publish the library to npm. For C#, you can distribute the library as a NuGet package.

Performance Benefits and Limitations

Using Rust libraries in other languages can provide significant performance benefits, especially for computationally intensive tasks. However, there are some limitations to consider:

  • Overhead: There is some overhead associated with calling Rust functions from other languages, especially if data conversion is required.
  • Complexity: Writing and maintaining bindings can be complex, especially for large libraries.
  • Compatibility: Not all Rust features are easily exposed to other languages, and some languages may have limitations that affect how Rust code can be used.

External Frameworks

Try It Yourself

Experiment with the examples provided by modifying the functions to perform different operations or by adding new functions. Try creating a Rust library that provides a more complex functionality, such as a data processing algorithm, and expose it to Python, Node.js, or C#.

Visualizing Rust’s Interaction with Other Languages

    graph TD;
	    A["Rust Library"] --> B["Python (PyO3)"];
	    A --> C["Node.js (Neon)"];
	    A --> D["C# (P/Invoke)"];
	    B --> E["Python Application"];
	    C --> F["Node.js Application"];
	    D --> G["C# Application"];

Diagram Description: This diagram illustrates how a Rust library can interact with different languages like Python, Node.js, and C#, enabling applications in these languages to leverage Rust’s capabilities.

Knowledge Check

  • What are the key steps in creating a Rust library for another language?
  • How does PyO3 facilitate Rust and Python interoperability?
  • What are the performance considerations when using Rust libraries in other languages?

Summary

Writing Rust libraries for other languages allows you to leverage Rust’s performance and safety features across different ecosystems. By understanding how to expose Rust functions and types, handle data conversion, and package your library for distribution, you can create powerful, cross-language applications.

Remember, this is just the beginning. As you progress, you’ll be able to create more complex libraries and integrate Rust into a wider range of applications. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026