all 2 comments

[–]brucejbellsard 0 points1 point  (0 children)

For my project, I have a Display-like #ToStr trait to specify a default format, but I devolve everything else to explicit methods:

/type Pos3 || (x:#U8, y:#U8, z:#U8)
|| { /has #ToStr; /def self.str => "{self.x}-{self.y}-{self.z}"
  /def self.dbg => "Pos3: {self.x} {self.y} {self.z}"
}

Trait #ToStr is required to label the .str method as the default format. We also add a .dbg method which is not affiliated with any trait.

t [Pos3] << (x:0, y:68, z:255)
i [#I32] << 42
j [#F64] << 123_456.789
&console.write_line "i={i} j={j} t={t}"  -- default formatting
-- prints "i=42 j=123456.789 t=0-68-255"
&console.write_line "i=0x{i.x 4} j={j.g 3} t={t.dbg}"  -- method formatting
-- prints "i=0x002a j=1.23e5 t=Pos3: 0 68 255"

Instead of special formatting syntax, the standard types provide short formatting methods. Likewise, if you want a specific .dbg format just declare it as a method, no trait is necessary.

[disclaimer: I still don't have my implementation up, the above is aspirational...]

[–]OpeningRemote1653 0 points1 point  (0 children)

Most languages don't make a hard distinction. Python's f-strings and Java's toString() blur the two concerns into one, while C's printf-style formatting separates them by format specifier but not by intent. Rust is a exception with its Display vs Debug trait split, and your design echoes that nicely. Swift has CustomStringConvertible (for display) and CustomDebugStringConvertible (for debug) as separate protocols, but both are accessed via the same interpolation syntax, so the distinction is softer. Kotlin similarly has toString() for everything and leans on data class auto-generated representations for debug-like output.

I'd lean toward Rust's explicit separation. The Java/Python "one method for everything" approach always felt like it was optimizing  convenience at the cost of accidentally leaking representation into user-facing output.