Examples
Real-world usage examples and an explanation of the code generated by yuuka macros.
Language Pack (i18n)
A typical use case: defining a nested language pack structure that maps directly to JSON files. Supports serialization/deserialization with serde.
use anyhow::Result;
use serde::{Serialize, Deserialize};
use yuuka::{derive_struct, auto};
fn main() -> Result<()> {
derive_struct!(
#[derive(PartialEq, Serialize, Deserialize)]
LanguagePack {
是: String,
否: String,
确认: String,
取消: String,
保存: String,
主页: {
启动: String,
设置: String,
},
设置: {
虚拟机路径: String,
程序本体路径: String,
网络配置: {
网络配置: String,
是否启用代理: String,
代理地址: String,
是否启用IPV6: String,
},
},
}
);
let config = auto!(LanguagePack {
是: "Yes".to_string(),
否: "No".to_string(),
确认: "Confirm".to_string(),
取消: "Cancel".to_string(),
保存: "Save".to_string(),
主页: {
启动: "Start".to_string(),
设置: "Settings".to_string(),
},
设置: {
虚拟机路径: "VM Path".to_string(),
程序本体路径: "Program Path".to_string(),
网络配置: {
网络配置: "Network Config".to_string(),
是否启用代理: "Enable Proxy".to_string(),
代理地址: "Proxy Address".to_string(),
是否启用IPV6: "Enable IPV6".to_string(),
},
},
});
// Deserialize from JSON
let json_raw = r#"
{
"是": "Yes", "否": "No", "确认": "Confirm",
"取消": "Cancel", "保存": "Save",
"主页": { "启动": "Start", "设置": "Settings" },
"设置": {
"虚拟机路径": "VM Path",
"程序本体路径": "Program Path",
"网络配置": {
"网络配置": "Network Config",
"是否启用代理": "Enable Proxy",
"代理地址": "Proxy Address",
"是否启用IPV6": "Enable IPV6"
}
}
}"#;
let config_from_json = serde_json::from_str::<LanguagePack>(json_raw)?;
assert_eq!(config, config_from_json);
assert_eq!(config.设置.网络配置.代理地址, "Proxy Address");
Ok(())
}
Key takeaways:
- Field names can be non-ASCII (Chinese characters, etc.) — they work as both Rust identifiers and JSON keys.
- The
auto!macro handles anonymous sub-struct construction (主页, 设置, 网络配置) seamlessly. - The generated types are fully serde-compatible for round-trip JSON serialization.
Server Router Configuration
A more complex example modeling a reverse proxy / server router configuration with nested arrays and inline enums.
use anyhow::Result;
use serde::{Serialize, Deserialize};
use yuuka::{derive_struct, auto};
fn main() -> Result<()> {
derive_struct!(
#[derive(PartialEq, Serialize, Deserialize)]
Config {
port: u16,
services: [Service {
domain: Vec<String>,
rules: [Rule {
pattern: String,
method: enum Method {
Redirect { url: String },
Proxy { host: String },
StaticFile { path: String },
StaticDir { path: String },
},
}],
}],
}
);
let config = auto!(Config {
port: 8080,
services: vec![Service {
domain: vec!["example.com".to_string()],
rules: vec![
Rule {
pattern: "^/$".to_string(),
method: Method::Redirect {
url: "https://example.com/index.html".to_string(),
},
},
Rule {
pattern: "^/api".to_string(),
method: Method::Proxy {
host: "http://localhost:8081".to_string(),
},
},
Rule {
pattern: "^/static".to_string(),
method: Method::StaticDir {
path: "/var/www/static".to_string(),
},
},
],
}],
});
// This structure maps directly to/from JSON
let json = serde_json::to_string_pretty(&config)?;
let config_from_json: Config = serde_json::from_str(&json)?;
assert_eq!(config, config_from_json);
Ok(())
}
Key takeaways:
[Service { ... }]generatesservices: Vec<Service>withServiceas an independent struct.- Nested
[Rule { ... }]inside Service generates anotherVec<Rule>with inline Rule struct. enum Method { ... }defines an enum inline, with struct-like variants for different routing methods.- The entire configuration can be loaded from / saved to JSON.
Generated Code Structure
Understanding what yuuka generates helps debug and work effectively with the library.
When you write:
#![allow(unused)]
fn main() {
derive_struct!(
#[derive(Serialize)]
pub Root {
name: String,
child: Child {
value: i32,
},
}
);
}
The macro generates approximately:
#![allow(unused)]
fn main() {
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
pub mod __Root {
use super::*;
#[derive(Debug, Clone, Serialize, Default)]
pub struct Root {
pub name: String,
pub child: Child,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct Child {
pub value: i32,
}
// Helper macros for auto!
macro_rules! __auto_Root {
(name $($tt:tt)*) => { $($tt)* };
(child { $($tt:tt)* }) => { ::yuuka::auto!(Child { $($tt)* }) };
(child $($tt:tt)*) => { $($tt)* };
// ... more rules for each field
}
macro_rules! __auto_Child {
(value $($tt:tt)*) => { $($tt)* };
// ... more rules for each field
}
}
pub use __Root::*;
}
Key aspects
-
Module wrapping: All types go into a module named
__TypeNameto avoid name collisions. Everything is re-exported withuse __TypeName::*. -
Automatic derives:
DebugandCloneare always added. Your custom#[derive(...)]macros are appended. -
Default implementation: If no fields have custom defaults →
#[derive(Default)]. If any field has= value→ manualimpl Default { ... }. -
Helper macros: For each type, a
__auto_TypeName!macro is generated. These aremacro_rules!macros that theauto!proc-macro calls to resolve field types — particularly anonymous struct/enum names. -
Super imports:
use super::*brings the outer scope into the module, which is why external types needsuper::prefix when referenced.
Module naming convention
| Input | Module name |
|---|---|
Root { ... } | __Root |
Config { ... } | __Config |
| Anonymous field in Root | _Root_0_anonymous, _Root_1_anonymous, … |
| Anonymous field in enum A | _A_0_anonymous, _A_1_anonymous, … |