I’ve been on a Rust learning journey lately, and it’s had me thinking about how I can consume Rust libraries from existing .NET applications. The .NET team has done much work regarding interoperability during the .NET 6 to .NET 8 era, and .NET 9 seems poised to continue that trend.
In this post, we’ll create a Rust library and consume it from a .NET application. This post assumes you have installed the .NET SDK and Rust SDK (cargo).
A Simple Rust Library
After creating a .NET Solution, I first created a new Rust library using cargo
. The command is relatively
straightforward.
cargo init --lib calculator
This creates a new calculator
library folder with the most critical files: Cargo.toml
and lib.rs
. Let’s update
the Cargo.toml
file to produce an artifact that our .NET application can consume, a dynamic library.
I’ve also included the rand
dependency for my Rust code later. Running the cargo b
command will now produce a
library we can copy into our .NET application.
Let’s write some Rust code we’ll consume from our .NET application later.
Notable elements of this Rust code include the following.
- The
extern
keyword adds to the list of functions and types in the foreign functions interfaces (FFI). -
no_mangle
tells the Rust compiler not to mangle the function’s name so that it can be referenced externally by a .NET application or similar external consumer. - The
"C"
value afterextern
tells the Rust compiler to compile to something C-compatible. There are other options here as well. - The
repr
attribute onPoint
states that this structure should be stored in memory in a C-compatible way. - All functions should use references to elements, which allows us to marshal information from one technology stack to
another with little to no overhead. You need to be careful not to introduce memory leaks here, hence the use
of
unsafe
.
Now let’s modify our .NET application for some Rust fun.
Building Rust from a .NET Project
I’ve used this trick several times across tools, and it works like a charm here with Rust. You can use the MSBuild task
of Exec
to execute commands.
You’ll also need to set the AllowUnsafeBlocks
element to true
, or you won’t be able to access any library. Every
time we compile, we’ll build our Rust library and copy it to the project root. From there, we’ll copy the library to our
output directory for each build. Depending on your project, feel free to change when the Rust build occurs.
Let’s write some C# code.
Calling Rust from C#
Now, it’s just a matter of giving our C# code something to call. We can do this using .NET’s LibraryImportAttribute
.
Your interface must match the expectations set by the Rust library. Things like structures, types, and references must match, or you’ll likely get an error code of 139, and the executing application will halt.
LibraryImport
works with the DllImportAttribute
and is a source generator that correctly handles common memory
management issues with marshaling types like string
or reference types. You should use LibraryImport
instead of
writing the DllImport
code yourself.
The results are as you’d expect.
There you have it. We could call Rust code from a .NET Application with different levels of complexity. That’s pretty cool!
Conclusion
I want to thank Jeremy Mill, who wrote a blog post 2017 that helped me learn and experiment with this sample. I hope you found this post helpful, and be sure to share it with friends and colleagues. As always, thanks and cheers.