I've discovered that std.format's hasToString detection mechanism is fundamentally flawed, forcing developers to use unintuitive workarounds. This needs to be fixed at the language/library level.

**The Problem: hasToString Can't Detect Valid toString Methods**

When you implement toString(Writer, Char) that calls formatValue internally (a very common pattern), std.format fails to detect it:

---
```
import std.format, std.array, std.variant;

    struct FmtVariant
    {
        private Variant value;

void toString(Writer, Char)(ref Writer writer, scope const ref FormatSpec!Char fmtSpec)
        {
            if (value.type == typeid(int))
formatValue(writer, value.get!int, fmtSpec); // Recursively format
        }
    }

    void main()
    {
        auto v = FmtVariant(Variant(42));
writeln(format("%s", v)); // FAILS: Uses default formatting, NOT your toString!
    }
```
---

**Root Cause: Broken Detection Logic**

The hasToString template in std.format uses a dummy lambda to test if toString exists:

 ---
 ```
    enum bool hasToString(T, Char) =
        __traits(compiles,
        {
            T val = T.init;
val.toString((const(char)[] s){}, f); // Dummy lambda writer
        });
 ```
 ---

This creates a catch-22:
1. Dummy lambda only accepts strings, not ints/floats/etc
2. Your toString calls formatValue(dummy, int_value, ...)
3. This FAILS to compile (can't format int into string-only lambda)
4. hasToString returns false
5. Your toString is IGNORED

**The Forced Workaround**

You MUST add this hack to make it work:

 ---
 ```
void toString(Writer, Char)(ref Writer writer, scope const ref FormatSpec!Char fmtSpec)
    {
        // REQUIRED HACK: Filter out the dummy lambda
static if (__traits(compiles, { Writer w; w.put("test"); }))
        {
            // Your actual code here
            if (value.type == typeid(int))
                formatValue(writer, value.get!int, fmtSpec);
        }
    }
 ```
 ---

Why this works:
- Dummy lambda has NO put method → guard fails → body skipped → signature still compiles → hasToString succeeds
- Real writers HAVE put method → guard succeeds → code executes

**Why This is a Language/Library Design Flaw**

1. **Non-obvious**: Newcomers have no idea why their toString isn't called 2. **Unintuitive**: The workaround makes no semantic sense without deep knowledge
3. **Boilerplate**: Every recursive toString needs this guard
4. **Fragile**: Breaks if hasToString implementation changes
5. **Poor diagnostics**: No error message explains what's wrong

**This Should Not Require Workarounds**

The hasToString check should handle recursive formatting naturally.


**TL;DR**
```
std.format's hasToString detection is broken for recursive formatters. You must add:
static if (__traits(compiles, { Writer w; w.put("test"); }))
to every toString that calls formatValue. This is a design flaw that needs fixing.
```
Thoughts? Should I report this as a bug or enhancement request?

Reply via email to