Expand description
§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
[dependencies]
inline_csharp = "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 inline_csharp::csharp;
// No type annotation needed — the macro infers `i32` from `static int Run()`
let x = csharp! {
static int Run() {
return 42;
}
}.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 inline_csharp::csharp_fn;
// Single parameter — return type inferred from `static int Run()`
let doubled = csharp_fn! {
static int Run(int n) {
return n * 2;
}
}(21).unwrap();
// Multiple parameters
let msg: String = csharp_fn! {
static string Run(string greeting, string target) {
return greeting + ", " + target + "!";
}
}("Hello", "World").unwrap();
// Nullable parameter
let result: Option<i32> = csharp_fn! {
static int? Run(int? val) {
return val.HasValue ? val * 2 : null;
}
}(Some(21)).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 inline_csharp::ct_csharp;
const PI: f64 = ct_csharp! {
static double Run() {
return System.Math.PI;
}
};
// Arrays work too — result is a Rust array literal baked into the binary
const PRIMES: [i32; 5] = ct_csharp! {
static int[] Run() {
return new int[] { 2, 3, 5, 7, 11 };
}
};§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 inline_csharp::csharp;
let result: i32 = csharp! {
build = "--no-restore",
reference = "../../libs/Foo.dll",
static int Run() {
return Foo.Value;
}
}.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, and the reference DLL paths. Changing any of those inputs
automatically triggers a fresh compilation.
§Using project C# source files / namespaces
Use using or namespace directives together with reference = "..." to call into your own C# code:
use inline_csharp::csharp;
// using style
let s: String = csharp! {
using MyNamespace;
static string Run() {
return new MyClass().Greet();
}
}.unwrap();use inline_csharp::csharp;
// namespace style — the generated class becomes part of the named namespace
let s: String = csharp! {
namespace MyNamespace;
static string Run() {
return new MyClass().Greet();
}
}.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 inline_csharp::csharp_fn;
fn my_rust_impl(n: i32) -> i32 {
// … new Rust code …
n * 2
}
fn parity_with_csharp() {
let csharp_impl = csharp_fn! {
static int Run(int n) {
// original C# logic, verbatim
return n * 2;
}
};
for n in [0, 1, -1, 42, i32::MAX / 2] {
let expected = csharp_impl(n).unwrap();
assert_eq!(my_rust_impl(n), expected, "diverged for n={n}");
}
}
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 |
Macros§
- csharp
- Re-export the proc macros so users only need to depend on this crate. Compile and run zero-argument C# code at program runtime.
- csharp_
fn - Re-export the proc macros so users only need to depend on this crate. Return a typed Rust function that compiles and runs C# at program runtime.
- ct_
csharp - Re-export the proc macros so users only need to depend on this crate. Run C# at compile time and splice its return value as a Rust literal.
Enums§
- Csharp
Error - Re-export the core error type and runtime helpers.
All errors that
csharp!andcsharp_fn!can return at runtime (and thatct_csharp!maps tocompile_error!diagnostics at build time).
Functions§
- expand_
dotnet_ args - Re-export the core error type and runtime helpers.
Shell-expand
raw(expanding env vars and~), then split into individual arguments (respecting quotes). Returns an empty vec ifrawis empty. - run_
csharp - Re-export the core error type and runtime helpers. Compile (if needed) and run a generated C# class, returning raw stdout bytes.