Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: fmt: add %V format (like %v, but with deeper pointer de-referencing) #28141

Closed
hdu-hh opened this issue Oct 11, 2018 · 10 comments
Closed

proposal: fmt: add %V format (like %v, but with deeper pointer de-referencing) #28141

hdu-hh opened this issue Oct 11, 2018 · 10 comments

Comments

@hdu-hh
Copy link

@hdu-hh hdu-hh commented Oct 11, 2018

The current %v formatting of values is extremely useful. It already dereferences pointers up to one level deep. For types with more nesting levels it prints pointers though. E.g.
i,j:=42,53; fmt.Printf("%#v\n", []interface{}{&i,&j})
prints
[]interface{}{(*int)(0xc4000120d8]), (*int)(040c00160f0)}
that shows the int-pointers to be different. In some cases it may be more useful to show the actual value though:
[]interface{}{&int{42}, &int{53}}

The proposal is to add a %V format to indicate a preference for the value. It would allow deeper de-referencing: Just follow pointers (up to a reasonable limit, e.g. 4...8) until the actual value is reached and format it like %v. If the nesting is too deep it should fall back to %v formatting directly.

Problems with de-referencing such as invalid pointers or pointers to unmanaged memory should be treated the same as in the de-referencing done in %v formatting.

Since the rune %V is not used yet the proposed change is backwards compatible.

The formatting modifiers + and # match their %v counterparts, but the Go-syntax representation may only be an approximation if there is no way to instantiate such value directly in Go. E.g.
i:=7; p:=&i; fmt.Printf("%#V",&p)
could print it as
&(int(&int(7)))
For deeper levels something like
&...(int(7))
may be more readable.

@gopherbot gopherbot added this to the Proposal milestone Oct 11, 2018
@gopherbot gopherbot added the Proposal label Oct 11, 2018
@hdu-hh hdu-hh changed the title proposal: Go1/Go2 - add %V format (like %v but pointers are de-referenced) proposal: Go 1/2 - add %V format (like %v but pointers are de-referenced) Oct 11, 2018
@CAFxX
Copy link
Contributor

@CAFxX CAFxX commented Oct 11, 2018

I would recommend to at least specify in the proposal:

  • What should happen in the case of pointer-to-pointer (e.g. **T or ***T)?
  • What should happen if the pointer is invalid (somehow points to an invalid address/non-existing object)? Should it panic or print something specific?
  • What should happen if the pointer points to unmanaged memory (e.g. outside the go heap)?
@martisch
Copy link
Contributor

@martisch martisch commented Oct 11, 2018

Current fmt does already follow pointers but not past the first level because it is unclear what should happen in case of cycles? Depending on the answer it might also add alot of complexity to implement which might better be suitable for a different package.

@cznic
Copy link
Contributor

@cznic cznic commented Oct 11, 2018

What should this code print?

type T *T
var v T
v = &v
fmt.Printf("%V", v)
@bcmills bcmills changed the title proposal: Go 1/2 - add %V format (like %v but pointers are de-referenced) proposal: fmt: add %V format (like %v but pointers are de-referenced) Oct 11, 2018
@hdu-hh
Copy link
Author

@hdu-hh hdu-hh commented Oct 11, 2018

The %v format derefences one level deep already. The %V format would just indicate a preference for deeper dereferencing. Signs of trouble such as too deep nesting or cycles can be easily detected and formatting could fall back to its %v behavior then.

With that said here are answers to the questions above.

What should happen in the case of pointer-to-pointer (e.g. **T or ***T)?

It could print as them as &(&T{}) or &(&(&T{}))

What should happen if the pointer is invalid (somehow points to an invalid address/non-existing object)? Should it panic or print something specific?
What should happen if the pointer points to unmanaged memory (e.g. outside the go heap)?

It should follow the example of the one-deep dereferencing for %v

Current fmt does already follow pointers but not past the first level because it is unclear what should happen in case of cycles?
...
What should this code print?

It should detect a too deep (e.g. >= 5) nesting level and fall back to formatting as %v

Depending on the answer it might also add alot of complexity to implement which might better be suitable for a different package

Having it in the core would be nice already for logging, for matting of error messages and test results.

@CAFxX
Copy link
Contributor

@CAFxX CAFxX commented Oct 12, 2018

You may want to update the proposal to include (and expand on) all these details. Otherwise, people will have to read the whole thread to understand what you are proposing.

@hdu-hh hdu-hh changed the title proposal: fmt: add %V format (like %v but pointers are de-referenced) proposal: fmt: add %V format (like %v, but with more pointer de-referencing) Oct 12, 2018
@hdu-hh
Copy link
Author

@hdu-hh hdu-hh commented Oct 12, 2018

Good point. I updated the proposal accordingly.

@deanveloper
Copy link

@deanveloper deanveloper commented Oct 15, 2018

I personally like the idea of using %*v since * is used for dereferencing. Although doing that we get into trouble with %+*v vs %*+v and such. I like this though, although I'm not sure.

Other considerations:

  • Allow telling how deep you want the dereferencing to go, so you may still print deep pointers (%(*6)V or something? That looks gross but it gets the message across of what I'm illustrating)
  • Instead of falling back to %v, perhaps print an error (ie %!V(TOODEEP=iterationnum))
@hdu-hh
Copy link
Author

@hdu-hh hdu-hh commented Oct 16, 2018

I personally like the idea of using %*v since * is used for dereferencing.

The * is already taken for setting formatting precision and width indirectly.

  • Allow telling how deep you want the dereferencing to go, so you may still print deep pointers (%(*6)V or something? That looks gross but it gets the message across of what I'm illustrating)

The * is already taken, but I like the idea of making it configurable, e.g. using the precision field of %V to specify the maximum depth, i.e.. %.6V would dereference up to six levels.

  • Instead of falling back to %v, perhaps print an error (ie %!V(TOODEEP=iterationnum))

I like that idea, thanks!

So it's time to update the proposal with the two points:

  • specifying maximum depth
    • either using the precision field
    • or keeping the %v precision semantic and set a fixed depth
  • handling too deep nesting
    • either falling back to %v formatting
    • or printing an error %!V(TOODEEP=iterationnum)
      All points have their merits, so I'd like to have something like a poll. Not sure how to do it in this issue tracker though.
@rsc rsc changed the title proposal: fmt: add %V format (like %v, but with more pointer de-referencing) proposal: fmt: add %#V format (like %#v, but with more pointer de-referencing) Oct 17, 2018
@rsc
Copy link
Contributor

@rsc rsc commented Oct 17, 2018

Since the rune %V is not used yet the proposed change is backwards compatible.

This is not strictly true. Other implementations of fmt.Formatter may be implementing %V.
This seems very special-case for taking up a whole separate letter. It's unclear where this ends.
%#v prints Go syntax. There's no Go syntax for the *int case other than what's already printed.

There are many ways the default printing might not satisfy one use case or another.
We can't attempt to satisfy them all in the print package.
Sorry.

@rsc rsc closed this Oct 17, 2018
@hdu-hh
Copy link
Author

@hdu-hh hdu-hh commented Oct 18, 2018

Since the rune %V is not used yet the proposed change is backwards compatible.

This is not strictly true. Other implementations of fmt.Formatter may be implementing %V.

I don't agree with the non-compatibility argument: A type with an existing fmt.Formatter would use it the same as before (like all other builtin runes, e.g. v):

type MyInt int
func (my MyInt) Format(f fmt.State, c rune) { fmt.Fprintf(f,"%d",100-int(my)) }
func main() {
        my := MyInt(7)
        fmt.Printf("%v vs %v\n", my, int(my))
}

prints 93 vs 7, i.e. a fmt.Formatter overrules the builtin.

This seems very special-case for taking up a whole separate letter. It's unclear where this ends.
%#v prints Go syntax. There's no Go syntax for the *int case other than what's already printed.

The V would indicate that the value is much more important than any transient info such as a pointer. The concrete type information is already available via %T formatting. We could also decide not to support #V formatting for now.

Logging pointers is almost worthless in cloud deployments (i.e. multiple running instances that are restarted aplenty) whereas logging object values is really useful for analyzing problems across many runs.

There are many ways the default printing might not satisfy one use case or another.
We can't attempt to satisfy them all in the print package.
Sorry.

Fair enough. Thanks for considering the proposal.

@hdu-hh hdu-hh changed the title proposal: fmt: add %#V format (like %#v, but with more pointer de-referencing) proposal: fmt: add %V format (like %v, but with deeper pointer de-referencing) Oct 19, 2018
@golang golang locked and limited conversation to collaborators Oct 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
7 participants
You can’t perform that action at this time.