csharp-rs
Generate C# type definitions from Rust structs and enums via #[derive(CSharp)].
Heavily inspired by the excellent ts-rs crate.
Get Started
Add the derive macro to your Rust types:
use CSharp;
Run cargo test and get a generated .cs file:
// <auto-generated/>
using System;
using System.Text.Json.Serialization;
namespace Generated;
public sealed record Player
{
[JsonPropertyName("name")]
public string Name { get; init; }
[JsonPropertyName("level")]
public int Level { get; init; }
[JsonPropertyName("active")]
public bool Active { get; init; }
[JsonPropertyName("inventory")]
public List<string> Inventory { get; init; }
}
About
This crate was entirely vibe-coded as a side project. It was born from a Unity game that needed to share types with a Rust HTTP backend, and the lack of a Rust-to-C# type generation tool at the time.
✨ Features
- Derive macro on structs and enums —
#[derive(CSharp)] - Serde attribute support —
rename_all,rename,skip,skip_serializing,skip_serializing_if,flatten, and all 4 tagged enum representations - Dual serializer —
System.Text.Json(default) andNewtonsoft.Json - C# version targeting — Unity, C# 9, 10, 11, 12
- Tagged enums — auto-generated
JsonConverter<T>for all serde tagging modes, with native[JsonPolymorphic]support for C# 11+ - Optional type integrations —
chrono,uuid,serde_jsonvia feature flags - Export at test time —
#[csharp(export)]generates.csfiles when runningcargo test - Runtime configuration —
Configbuilder pattern with environment variable support
📦 Installation
Optional features
| Feature | Description |
|---|---|
chrono-impl |
NaiveDate → DateOnly, DateTime<Utc> → DateTimeOffset, NaiveTime → TimeOnly, Duration → TimeSpan |
uuid-impl |
Uuid → Guid |
serde-json-impl |
serde_json::Value → JsonElement (STJ) / JToken (Newtonsoft) |
🚀 Quick Start
1. Derive and annotate your types
use CSharp;
// export to default directory
// override default namespace
2. Configure the output
Use the builder pattern:
use ;
let cfg = default
.with_serializer
.with_target
.with_namespace
.with_export_dir;
Or set environment variables for CI:
CSHARP_RS_SERIALIZER=newtonsoft # "stj" or "newtonsoft"
CSHARP_RS_TARGET=11 # "unity", "9", "10", "11", "12"
CSHARP_RS_NAMESPACE=MyGame.Shared
CSHARP_RS_EXPORT_DIR=./generated
#[csharp(namespace = "...")]on a type overrides the globalConfignamespace.
3. Generate
C# files are written to the configured export directory (default: ./csharp-bindings).
🔧 Serde Attributes
| Attribute | Effect |
|---|---|
#[serde(rename_all = "...")] |
Apply naming convention to all fields/variants |
#[serde(rename = "...")] |
Rename an individual field or variant |
#[serde(skip)] |
Omit field/variant from C# output |
#[serde(skip_serializing)] |
Include field as output-only |
#[serde(skip_serializing_if = "...")] |
Conditional serialization |
#[serde(flatten)] |
Inline nested struct fields or HashMap as extension data |
#[serde(tag = "...")] |
Internally tagged enum |
#[serde(tag = "...", content = "...")] |
Adjacently tagged enum |
#[serde(untagged)] |
Untagged enum |
🔄 Serializer Comparison
The same Rust struct generates different C# attributes depending on the serializer:
System.Text.Json (default):
using System.Text.Json.Serialization;
public sealed record Player
{
[JsonPropertyName("name")]
public string Name { get; init; }
}
Newtonsoft.Json:
using Newtonsoft.Json;
public sealed record Player
{
[JsonProperty("name")]
public string Name { get; init; }
}
Unity mode generates
sealed classwith{ get; set; }properties instead of records.
🎯 C# Version Targeting
| Version | Records | File-scoped NS | required |
[JsonPolymorphic] |
|---|---|---|---|---|
| Unity | No | No | No | No |
| C# 9 | Yes | No | No | No |
| C# 10 | Yes | Yes | No | No |
| C# 11 | Yes | Yes | Yes | Yes (STJ only) |
| C# 12 | Yes | Yes | Yes | Yes (STJ only) |
Unity mode uses
sealed classwith{ get; set; }properties instead of records.
🏷️ Enums
Simple enums
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Color
{
Red,
Green,
Blue,
}
Tagged enums
[JsonConverter(typeof(MessageConverter))]
public abstract record Message;
public sealed record Request(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("method")] string Method
) : Message;
public sealed record Quit : Message;
// + auto-generated MessageConverter class
All four serde tagging modes are supported: internally tagged, adjacently tagged, externally tagged, and untagged.
⚠️ Limitations
- Tuple variants in tagged enums are rejected with a compile error
- Tuple structs are not supported (only named-field structs)
- Generic Rust types with type parameters are not yet supported
🙏 Acknowledgements
This project is heavily inspired by ts-rs, which generates TypeScript type definitions from Rust types.
csharp-rs follows the same derive-and-export pattern and draws from ts-rs's runtime Config design (introduced in ts-rs v12).
📄 License
MIT — see LICENSE for details.