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

encoding: provide append-like variants #53693

Closed
dsnet opened this issue Jul 5, 2022 · 9 comments
Closed

encoding: provide append-like variants #53693

dsnet opened this issue Jul 5, 2022 · 9 comments

Comments

@dsnet
Copy link
Member

dsnet commented Jul 5, 2022

There's been a shift in recent years to provide Append-like variants of APIs:

as these are generally easier to work with than Put-like APIs where you need to know the size of the buffer beforehand, prepare a buffer of the right size, and then call the Put-like operation.

Such APIs are missing for the encoding/hex, encoding/base32, and encoding/base64 packages. I propose we add the following:

package hex
func AppendEncoded(dst, src []byte) []byte
func AppendDecoded(dst, src []byte) ([]byte, error)
package base32
func (*Encoding) AppendEncoded(dst, src []byte) []byte
func (*Encoding) AppendDecoded(dst, src []byte) ([]byte, error)
package base64
func (*Encoding) AppendEncoded(dst, src []byte) []byte
func (*Encoding) AppendDecoded(dst, src []byte) ([]byte, error)

This would simplify many callers of these packages, where the only reason to call the Len method is to obtain the size of the expected output buffer, do some manual memory management, and then call the Encode or Decode equivalent.


For example, this logic in the json package:

e.WriteByte('"')
encodedLen := base64.StdEncoding.EncodedLen(len(s))
if encodedLen <= len(e.scratch) {
// If the encoded bytes fit in e.scratch, avoid an extra
// allocation and use the cheaper Encoding.Encode.
dst := e.scratch[:encodedLen]
base64.StdEncoding.Encode(dst, s)
e.Write(dst)
} else if encodedLen <= 1024 {
// The encoded bytes are short enough to allocate for, and
// Encoding.Encode is still cheaper.
dst := make([]byte, encodedLen)
base64.StdEncoding.Encode(dst, s)
e.Write(dst)
} else {
// The encoded bytes are too long to cheaply allocate, and
// Encoding.Encode is no longer noticeably cheaper.
enc := base64.NewEncoder(base64.StdEncoding, e)
enc.Write(s)
enc.Close()
}
e.WriteByte('"')

could be simplified as:

b := e.AvailableBuffer()
b = append(b, '"')
b = base64.StdEncoding.AppendEncoded(b, s)
b = append(b, '"')
e.Write(b)

This assumes we had some way to unify bytes.Buffer with append-like APIs; see #53685 (comment).

The resulting code is both simpler and more performant since we can append directly into the underlying bytes.Buffer without going through an intermediate encodeState.scratch buffer. Also, the caller doesn't have to do manual memory management checking whether the encoded output fits within the encodeState.scratch.

@dsnet dsnet added the Proposal label Jul 5, 2022
@gopherbot gopherbot added this to the Proposal milestone Jul 5, 2022
@mvdan
Copy link
Member

mvdan commented Jul 5, 2022

As far as names go, we could also consider EncodeAppend and DecodeAppend, even if they aren't as consistent with the usual "verb then object" method names like MarshalJSON or AppendUint.

@rsc
Copy link
Contributor

rsc commented Jun 7, 2023

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Jun 14, 2023

Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Jun 21, 2023

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

@rsc rsc changed the title proposal: encoding: provide append-like variants encoding: provide append-like variants Jun 21, 2023
@rsc rsc modified the milestones: Proposal, Backlog Jun 21, 2023
@gopherbot
Copy link

Change https://go.dev/cl/504884 mentions this issue: encoding: add AppendEncode and AppendDecode

@dmitshur dmitshur modified the milestones: Backlog, Go1.22 Aug 17, 2023
@gopherbot
Copy link

Change https://go.dev/cl/520755 mentions this issue: encoding/json: use base64.Encoding.AppendEncode

gopherbot pushed a commit that referenced this issue Aug 18, 2023
For #53693

Change-Id: I6a428a4a10a2e2efa03296f539e190f0743c1f46
Reviewed-on: https://go-review.googlesource.com/c/go/+/520755
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Andy Pan <panjf2000@gmail.com>
cellularmitosis pushed a commit to cellularmitosis/go that referenced this issue Aug 24, 2023
For golang#53693

Change-Id: I6a428a4a10a2e2efa03296f539e190f0743c1f46
Reviewed-on: https://go-review.googlesource.com/c/go/+/520755
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Andy Pan <panjf2000@gmail.com>
@mitar
Copy link
Contributor

mitar commented Nov 5, 2023

@dsnet: Thank you for adding those. I was also looking into how to append encoded and now I see it will be out in upcoming 1.22. Great!

But I am still curious how this interacts with bytes.Buffer.

To me, the following works (using what is available with 1.21):

	v := []byte{'a', 'b'}
	b := &bytes.Buffer{}
	data := make([]byte, base64.StdEncoding.EncodedLen(len(v)))
	base64.StdEncoding.Encode(data, v)
	b.Write(data)

But if I do:

	v := []byte{'a', 'b'}
	b := &bytes.Buffer{}
	b.Grow(base64.StdEncoding.EncodedLen(len(v)))
	base64.StdEncoding.Encode(b.AvailableBuffer(), v)
	fmt.Println(b.String())

if fails with a panic. I must say I do not understand why based on discussion in #53685, it seems this should also work?

@mitar
Copy link
Contributor

mitar commented Nov 5, 2023

OK, I made it work:

	v := []byte{'a', 'b'}
	buf := &bytes.Buffer{}
	l := base64.StdEncoding.EncodedLen(len(v))
	buf.Grow(l)
	b := buf.AvailableBuffer()[:l]
	base64.StdEncoding.Encode(b, v)
	buf.Write(b)
	fmt.Println(buf.String())

No I understand why AppendEncoded is needed. :-)

@gopherbot
Copy link

Change https://go.dev/cl/547755 mentions this issue: doc: add release notes for changes to encoding packages

gopherbot pushed a commit that referenced this issue Dec 5, 2023
For #53693.

Change-Id: I360f5cb9caf5fa77267a100eebcc282955677abe
Reviewed-on: https://go-review.googlesource.com/c/go/+/547755
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
TryBot-Bypass: Robert Griesemer <gri@google.com>
ezz-no pushed a commit to ezz-no/go-ezzno that referenced this issue Feb 18, 2024
For golang#53693.

Change-Id: I360f5cb9caf5fa77267a100eebcc282955677abe
Reviewed-on: https://go-review.googlesource.com/c/go/+/547755
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
TryBot-Bypass: Robert Griesemer <gri@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Accepted
Development

No branches or pull requests

6 participants