Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 { ... }] generates services: Vec<Service> with Service as an independent struct.
  • Nested [Rule { ... }] inside Service generates another Vec<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

  1. Module wrapping: All types go into a module named __TypeName to avoid name collisions. Everything is re-exported with use __TypeName::*.

  2. Automatic derives: Debug and Clone are always added. Your custom #[derive(...)] macros are appended.

  3. Default implementation: If no fields have custom defaults → #[derive(Default)]. If any field has = value → manual impl Default { ... }.

  4. Helper macros: For each type, a __auto_TypeName! macro is generated. These are macro_rules! macros that the auto! proc-macro calls to resolve field types — particularly anonymous struct/enum names.

  5. Super imports: use super::* brings the outer scope into the module, which is why external types need super:: prefix when referenced.

Module naming convention

InputModule 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, …