Description
In my own experience echo
is used most heavily for debugging purposes. It is part of the hello world program, and then it is continuously used to explore the programming language and long the behavior of the program. It is also used to put values into error messages, for example in raise newException(KeyError, "key not found: " & $key
). But some types are just not printable with $
. This usually comes as a surprise, especially in generic code, as the majority of types do already have a $
overload.
The solution to the problem is to provide a generic solution of $
for every type that the Nim language allows, so that echo x
and "key not found: " & $key
will always compile, no matter what the value is.
After all these overloads would be need an addition:
ref
/ptr
types
ref
types are the natuarl thing to use in Nim, when the code is inteded to be executed on a javascript vm, as this is what the backend naturally supports and doesn't need to be emulated. But the big disadvantage of using a ref
type is that it isn't printable with echo
. My suggestion is to provide the following implementation:
proc `$`*[T: ref | ptr](arg: T): string =
if arg == nil:
"nil"
else:
$arg[]
This implementation is able to print nil
when required, but otherwise the output won't make it obvious that it prints a pointer value. Sometimes people care about the pointer values and they want to see them. But pointer values are at least 16 characters in hex and therefore very verbose in the output. To my experience people usually really don't care about the pointer value. As long as it points to something valid, it should print just that, not some hexadecimal string.
Dealing with Cycles
The current implementation of $
on objects iterates all printable fields recursively. This is problematic, as with this RFC it will run into infinite recursion if the value forms a pointer circle. An because $
is located in system.nim
and no further imports are allowed, a hash map to store previously visited nodes can't be used.
However there is already a nice static analyser built into the compiler to detect if a type is even able to form cycles: canFormAcycle
. Pointers to such recursive types could then be printed as just ...
like non printable types are represented right now. This isn't very helpful, but still much more helpful that printing all ptr
/ref
types as "...".
Distinct Types
It is easy to implement $
for distinct types:
proc `$`*[T: distinct](arg: T): string =
$distintcBase(T)(arg)
The question though remains, should distinctness simply be shaved off here? After all the programmer explicitly asked for a new incompatible type. @Araq mentioned this could be dangerous because of SQL injection. But I don't think this is really a problem. After all a distinct string remains a distinct string and it is not automatically converted to string
. It is only converted in echo
because it explicitly attaches a $
conversion to every argument. Maybe a distinct string should be quoted and not just printed. Or it should be altered in some other way. But that is still open to discussion. But I do think something should be printed.
Pointer
The type pointer
doesn't have any content it can print. The only thing that it can print is the value of the pointer itself. Earlier I argued printing actual pointer values is verbose, but here there is nothing else that can be printed. So it should be done. After all fields that are of type pointer
are rare.
Callbacks
Unfortunately I don't have a good solution for proc
value. I can check if they are nil and print "nil" if they are nil. But unfortunately I cannot print anything useful in the other case. A cdecl
callback can be cast to a pointer
and the address can be printed. But that doesn't work for closures. gdb
can print even the name of a cdecl callback, but I don't know how to do that in Nim.
I have a PR where this RFC has been implemented. It also shows how code would be simplified in the standard library and the compiler when after all a $
is available for everything.