Actually you can bind *standard-output*
directly:
(defun test-im-vs-ex-plicit ()
(values
(with-output-to-string (*standard-output*) ; here
(implicit))
(with-output-to-string (stream)
(explicit stream))))
There is no real simple answer. My advice:
Use stream variables, this makes debugging easier. They appear on the argument lists and are easier to spot in the backtrace. Otherwise you would need to see in the backtrace where there is a dynamic rebinding of a stream variable.
a) Nothing to pass?
(defun print-me (&optional (stream *standard-output*))
...)
b) One or more fixed arg:
(defun print-me-and-you (me you &optional (stream *standard-output*))
...)
c) One or more fixed args and multiple optional args:
(defun print-me (me
&key
(style *standard-style*)
(font *standard-font*)
(stream *standard-output*))
...)
Note also this:
Now assume (implicit)
has an error and we get a break loop, a debugging repl. What's the value of standard-output in this break loop?
CL-USER 4 > (defun test ()
(flet ((implicit ()
(write-line "foo")
(cerror "go on" "just a break")
(write-line "bar")))
(with-output-to-string (stream)
(let ((*standard-output* stream))
(implicit)))))
TEST
CL-USER 5 > (compile 'test)
TEST
NIL
NIL
CL-USER 6 > (test)
Error: just a break
1 (continue) go on
2 (abort) Return to level 0.
3 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 7 : 1 > *standard-output*
#<SYSTEM::STRING-OUTPUT-STREAM 40E06AD80B>
CL-USER 8 : 1 > (write-line "baz")
"baz"
CL-USER 9 : 1 > :c 1
"foo
baz
bar
"
Above is what you see in LispWorks or SBCL. Here you have access to the real program's binding, but using output functions during debugging will have effects on this stream.
In other implementations *standard-output*
will be rebound to actual terminal io - for example in Clozure CL and CLISP.
If your program does not rebind *standard-output*
there is less confusion in those cases. If I write code, I often think about what would be more useful in a REPL environment - which is different from languages, where there is less interactive debugging on REPLs and break loops...