1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
# Remove `r_print!` and `r_println!` Macros
`r_print!` and `r_println!` are important macros in Mingling for use inside `#[help]` and `#[renderer]` functions, but their implementation is not clean: they implicitly introduce a `__renderer_inner_result` field. While this might look elegant at the API level, it is **incorrect** and even **objectionable**.
## Why **Objectionable**?
Because you can't define declarative macros with `macro_rules` that wrap them.
This is because `r_println!` depends on the implicit variable `__renderer_inner_result` injected by the `#[renderer]` proc macro into the function body. However, when a `macro_rules` declarative macro expands, **its internal code is placed in the caller's context**, which does not contain `__renderer_inner_result` — that variable only exists within the direct scope of the function body processed by `#[renderer]`.
Let's look at some code to see why:
```rust
// Suppose you want to write a wrapper macro:
macro_rules! my_println {
($($arg:tt)*) => {
// When expanded here, the context is the call site of my_println!,
// not the location where the renderer function's injected variables live.
// So __renderer_inner_result is NOT visible here!
r_println!("Custom: {}", format!($($arg)*));
};
}
#[renderer]
fn render_something(_p: ResultSomething) {
// Although this function body has __renderer_inner_result injected,
// the code from my_println! does NOT expand "inside this function body" —
// macro_rules expansion is essentially text replacement. The replaced code
// lives at the line where my_println! is called, and any variables referenced
// inside that macro must resolve to identifiers accessible at the call site.
// __renderer_inner_result is not a public, path-accessible variable;
// it's a hygienic local variable generated by the `#[renderer]` macro,
// and external macros cannot directly access it by name.
my_println!("{}", box_val); // Compile error: cannot find __renderer_inner_result
}
```
## Deeper Issues
I have to admit, this is an early design flaw. After re-examining the code, I found the problem goes beyond "can't be wrapped".
This isn't just a "can't wrap" issue — it reflects that `r_println!`'s design fundamentally violates Rust's macro hygiene principles:
- **Implicit dependency**: Users of the macro must know that a variable named `__renderer_inner_result` exists — but this variable is neither part of the public API nor explicitly documented anywhere.
- **Scope leakage**: Variables injected by a proc macro should be confined to the scope processed by that macro. But `r_println!` attempts to make that variable accessible across macro calls, which effectively breaks Rust's identifier hygiene.
- **Non-composable**: Any attempt to wrap `r_println!` will fail, because declarative macros cannot "pass through" access to implicit variables. Even using a proc macro to wrap it would encounter similar hygiene issues.
## Desired New Syntax
I've designed two alternative approaches and will choose based on actual needs.
### Option 1: Explicit Return
```rust
#[renderer]
fn render_something(prev: ResultSomething) -> RenderResult {
let mut result = RenderResult::new();
result.println(prev.to_string());
// or
write!(result, "{}", prev.to_string());
result // return here
}
```
Clear boundaries — the entire rendering process is confined within the function body decorated by `#[help]` or `#[renderer]`, without introducing extra out-of-scope dependencies. The trade-off is slightly more boilerplate compared to the original approach.
### Option 2: Resource Injection
```rust
#[renderer]
fn render_something(prev: ResultSomething, result: &mut ResRenderResult) {
result.println(prev.to_string());
// or
write!(result, "{}", prev.to_string());
result // return here
}
```
More flexible, but blurs the boundary between logic functions like `#[chain]` and rendering functions like `#[help]`.
### Preferred Direction
I lean toward **Option 1 (Explicit Return)**. There's no need to turn `RenderResult` into `ResRenderResult` as a global resource.
As for rendering in logic functions like `#[chain]`, that should be handled by a separate system — not discussed here.
|