inline_csharp
Embed C# directly in Rust — evaluated at program runtime (csharp!, csharp_fn!) or at
compile time (ct_csharp!).
Prerequisites
.NET 8 (or later) SDK with dotnet on PATH.
Quick start
# Cargo.toml
[]
= "0.1.0"
csharp! — runtime, no parameters
Compiles and runs C# each time the surrounding Rust code executes. Expands
to Result<T, inline_csharp::CsharpError>.
use csharp;
// No type annotation needed — the macro infers `i32` from `static int Run()`
let x = csharp! .unwrap;
csharp_fn! — runtime, with parameters
Like csharp!, but Run(...) may declare parameters. Expands to a Rust
function value fn(P1, P2, …) -> Result<T, CsharpError>. Parameters are
serialised by Rust and piped to the C# process over stdin.
use csharp_fn;
// Single parameter — return type inferred from `static int Run()`
let doubled = csharp_fn! .unwrap;
// Multiple parameters
let msg: String = csharp_fn! .unwrap;
// Nullable parameter
let result: = csharp_fn! .unwrap;
ct_csharp! — compile time
Runs C# during rustc macro expansion and splices the result as a Rust
literal at the call site. No parameters are allowed (values must be
compile-time constants).
use ct_csharp;
const PI: f64 = ct_csharp! ;
// Arrays work too — result is a Rust array literal baked into the binary
const PRIMES: = ct_csharp! ;
Supported parameter types (csharp_fn!)
Declare parameters in the C# Run(...) signature; Rust receives them with
the mapped types below.
| C# parameter type | Rust parameter type |
|---|---|
sbyte |
i8 |
byte |
u8 |
short |
i16 |
ushort |
u16 |
int |
i32 |
uint |
u32 |
long |
i64 |
ulong |
u64 |
float |
f32 |
double |
f64 |
bool |
bool |
char |
char |
string |
&str |
T[] / List<T> |
&[T] |
T? |
Option<T> |
Supported return types
| C# return type | Rust return type |
|---|---|
sbyte |
i8 |
byte |
u8 |
short |
i16 |
ushort |
u16 |
int |
i32 |
uint |
u32 |
long |
i64 |
ulong |
u64 |
float |
f32 |
double |
f64 |
bool |
bool |
char |
char |
string |
String |
T[] / List<T> |
Vec<T> |
T? |
Option<T> |
Types can be nested arbitrarily: List<string>[] → Vec<Vec<String>>,
int?[] → Vec<Option<i32>>, etc.
Options
The following optional key = "value" pairs may appear before the C# body, separated by
commas:
build = "<args>"— extra arguments passed todotnet build.run = "<args>"— extra arguments passed todotnet <dll>at runtime.reference = "<path>"— path to a DLL to reference (repeatable).
use csharp;
let result: i32 = csharp! .unwrap;
Cache directory
Compiled assemblies are cached so that unchanged C# code is not recompiled on every run. The cache root is resolved in this order:
| Priority | Location |
|---|---|
| 1 | INLINE_CSHARP_CACHE_DIR environment variable (if set and non-empty) |
| 2 | Platform cache directory — ~/.cache/inline_csharp on Linux, ~/Library/Caches/inline_csharp on macOS, %LOCALAPPDATA%\inline_csharp on Windows |
| 3 | <system temp>/inline_csharp (fallback if the platform cache dir is unavailable) |
Each compiled assembly gets its own subdirectory named
<ClassName>_<hash>/, where the hash covers the C# source, the
expanded build flags, the current working directory, the raw run
flags, the reference DLL paths, and the maximum modification time of
any .cs, .dll, .nupkg, or .zip file found under directories
(or individual files) listed in reference. This means rebuilding a
referenced library automatically triggers a fresh compilation.
To force recompilation regardless of the hash (e.g. after editing files
outside the tracked paths), set INLINE_CSHARP_CACHE_INVALIDATE=true
(also accepts 1 or yes). This removes the cache entry for the
current class before compiling, so a clean build always runs:
INLINE_CSHARP_CACHE_INVALIDATE=true
Using project C# source files / namespaces
Use using or namespace directives together with reference = "..." to call into your own C# code:
use csharp;
// using style
let s: String = csharp! .unwrap;
use csharp;
// namespace style — the generated class becomes part of the named namespace
let s: String = csharp! .unwrap;
Refactoring use case
inline_csharp is particularly well-suited for incremental C# → Rust
migrations. The typical workflow is:
- Keep the original C# logic intact.
- Write the replacement in Rust.
- Use
csharp_fn!to call the original C# with the same inputs and assert that both implementations produce identical outputs.
use csharp_fn;
parity_with_csharp;
Crate layout
| Crate | Purpose |
|---|---|
inline_csharp |
Public API — re-exports macros and core types |
inline_csharp_macros |
Proc-macro implementation (csharp!, csharp_fn!, ct_csharp!) |
inline_csharp_core |
Runtime helpers (run_csharp, CsharpError) |
inline_csharp_demo |
Demo binary |