Skip to main content

Crate inline_csharp

Crate inline_csharp 

Source
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 typeRust parameter type
sbytei8
byteu8
shorti16
ushortu16
inti32
uintu32
longi64
ulongu64
floatf32
doublef64
boolbool
charchar
string&str
T[] / List<T>&[T]
T?Option<T>

§Supported return types

C# return typeRust return type
sbytei8
byteu8
shorti16
ushortu16
inti32
uintu32
longi64
ulongu64
floatf32
doublef64
boolbool
charchar
stringString
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 to dotnet build.
  • run = "<args>" — extra arguments passed to dotnet <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:

PriorityLocation
1INLINE_CSHARP_CACHE_DIR environment variable (if set and non-empty)
2Platform 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:

  1. Keep the original C# logic intact.
  2. Write the replacement in Rust.
  3. 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

CratePurpose
inline_csharpPublic API — re-exports macros and core types
inline_csharp_macrosProc-macro implementation (csharp!, csharp_fn!, ct_csharp!)
inline_csharp_coreRuntime helpers (run_csharp, CsharpError)
inline_csharp_demoDemo 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§

CsharpError
Re-export the core error type and runtime helpers. All errors that csharp! and csharp_fn! can return at runtime (and that ct_csharp! maps to compile_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 if raw is 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.