#recursion #reflection #struct #run-time #object

nightly kyomu

Allows recursive reflection of types, mapping runtime values to that recursive struct and constructing objects generically

20 releases (6 breaking)

Uses new Rust 2024

0.7.0 Feb 14, 2026
0.6.1 Feb 14, 2026
0.5.1 Feb 13, 2026
0.4.0 Jan 30, 2026
0.1.0 Jan 28, 2026

#62 in #recursion


Used in kyomu-json

MIT/Apache

36KB
758 lines

kyomu

Allows recursive reflection of types and mapping runtime values to that recursive struct.

--- WARNING: PROTOTYPICAL WORK ---

This currently is more of a science project then something useful. I plan to add features as the compile time reflection feature progresses.

Supported things so far

  • Primitives
  • &str
  • &dyn Trait
  • &[T]
  • [T; N]
  • (1, 2.0)
  • &T
  • object construction
  • object inspection
  • structs
  • enums
  • unions

Example Usage

use kyomu::{
    build_constructor, build_inspector,
    constructor::{ConstructorConfig, FloatOutput, IntOutput},
    inspector::{FieldReflectFn, InspectorConfig},
};

#[derive(PartialEq, Debug)]
pub struct Test {
    pub a: u8,
    pub b: TestB,
}

#[derive(PartialEq, Debug)]
pub struct TestB {
    pub c: (f64, String),
}

fn main() {
    assert_eq!(to_json(&String::from("hi")), "\"hi\"");
    assert_eq!(to_json(&false), "false");
    assert_eq!(to_json(&'d'), "\"d\"");
    assert_eq!(to_json(&"lol"), "\"lol\"");

    assert_eq!(
        to_json(&Test {
            a: 2,
            b: TestB {
                c: (1.0, 2.to_string())
            }
        }),
        "{\"a\": 2, \"b\": {\"c\": [1.0, \"2\"]}}"
    );

    assert_eq!(to_json(&[1, 2]), "[1, 2]");

    assert_eq!(from_json::<u8>("1"), 1);
    assert_eq!(from_json::<[u8; 2]>("[1,2]"), [1, 2]);
    assert_eq!(from_json::<String>("\"hi\""), "hi");
    assert_eq!(
        from_json::<TestB>("{\"c\": [1.1, \"2\"]}"),
        TestB {
            c: (1.1, 2.to_string())
        }
    );
    assert_eq!(
        from_json::<Test>("{\"a\": 4, \"b\": {\"c\": [1.1, \"2\"]}}"),
        Test {
            a: 4,
            b: TestB {
                c: (1.1, 2.to_string())
            }
        }
    );
    assert!(!from_json::<bool>("false"));
}

fn from_json<T: 'static>(n: &'static str) -> T {
    let fp = const {
        build_constructor::<&str, T>(ConstructorConfig {
            int: |i| {
                let n = take_number(i).parse::<i128>().unwrap();
                IntOutput::new(n)
            },
            fl: |i| {
                let n = take_number(i).parse::<f64>().unwrap();
                FloatOutput::new(n)
            },
            str: |i| take_string(i),
            array_start: |i| {
                eat(i, '[');
            },
            array_after_input: |i| {
                skip_ws(i);
                if i.starts_with(',') {
                    *i = &i[1..];
                }
            },
            array_end: |i| {
                skip_ws(i);
                eat(i, ']');
            },
            tuple_start: |i| eat(i, '['),
            tuple_after_input: |i, idx, len| {
                skip_ws(i);
                if idx + 1 < len {
                    eat(i, ',');
                }
            },
            tuple_end: |i| eat(i, ']'),
            struct_start: |i| eat(i, '{'),

            struct_before_input: |i, _field| {
                skip_ws(i);
                let _key = take_string(i);
                skip_ws(i);
                eat(i, ':');
            },

            struct_after_input: |i, _, idx, len| {
                skip_ws(i);
                if idx + 1 < len {
                    eat(i, ',');
                }
            },

            struct_end: |i| eat(i, '}'),

            bool: |i| {
                skip_ws(i);
                if i.starts_with("true") {
                    *i = &i[4..];
                    true
                } else {
                    assert!(i.starts_with("false"));
                    *i = &i[5..];
                    false
                }
            },
        })
    };

    fp(n)
}

fn to_json<T: 'static>(a: &T) -> String {
    let fnptr = const {
        build_inspector(InspectorConfig {
            int: |int| format!("{int:?}"),
            re: |inner_reflect| inner_reflect(),
            fl: |fl| format!("{fl:?}"),
            array: build_multi::<false>,
            struc: build_multi::<true>,
            tup: build_multi::<false>,
            str: |string| format!("{string:?}"),
            bool: |b| b.to_string(),
            char: |c| format!("\"{c}\""),
        })
    };
    fnptr(a)
}

fn build_multi<const IS_STRUCT: bool>(fns: &[FieldReflectFn<String>]) -> String {
    let mut result = String::from(match IS_STRUCT {
        true => "{",
        false => "[",
    });

    for (i, inner) in fns.iter().enumerate() {
        if i != 0 {
            result += ", ";
        }
        if IS_STRUCT {
            result = result + "\"" + inner.name + "\": ";
        }
        result += &inner();
    }

    result
        + match IS_STRUCT {
            true => "}",
            false => "]",
        }
}

fn skip_ws(i: &mut &str) {
    *i = i.trim_start();
}

fn eat(i: &mut &str, ch: char) {
    skip_ws(i);
    assert!(i.starts_with(ch));
    *i = &i[ch.len_utf8()..];
}

fn take_number<'a>(i: &'a mut &str) -> &'a str {
    skip_ws(i);

    let end = i
        .find(|c: char| !matches!(c, '0'..='9' | '-' | '+' | '.' | 'e' | 'E'))
        .unwrap_or(i.len());

    let (num, rest) = i.split_at(end);
    *i = rest;
    num
}

fn take_string(i: &mut &str) -> String {
    skip_ws(i);
    assert!(i.starts_with('"'));
    *i = &i[1..];

    let mut out = String::new();
    let mut escape = false;

    for (idx, ch) in i.char_indices() {
        if escape {
            out.push(match ch {
                '"' => '"',
                '\\' => '\\',
                '/' => '/',
                'b' => '\u{0008}',
                'f' => '\u{000C}',
                'n' => '\n',
                'r' => '\r',
                't' => '\t',
                _ => ch,
            });
            escape = false;
            continue;
        }

        match ch {
            '\\' => escape = true,
            '"' => {
                *i = &i[idx + 1..];
                return out;
            }
            _ => out.push(ch),
        }
    }

    panic!("unterminated string");
}

No runtime deps