The request body on the client do a lot of other things than reading the body once (an io.Reader can only be read once).
There's Content-Length, and there's also the need to read it multiple times in case a redirect happens (so the same body need to be sent again when being redirected).
As a result, the implementation in stdlib would check a few common io.Reader implementations (bytes.Buffer, bytes.Reader, strings.Reader) and make sure it stores something that can be read multiple times (if it's none of the 3, it's read fully into memory and stored instead).
This is the same basic reply as the other one but my thoughts are roughly the same. The only comment I have aside from what I replied on the sibling comment (this just being another case of wrappers not being transparent biting us in the ass) is that they could've done this in a more generic way than they did, at the downside of requiring more interfaces.
Yea I saw your other reply later and agree on most of it. But I'd say there's a balance between simplicity of the API and more specific cases. For example they can make an optional api to io.Reader to provide size info, and maybe another optional api to io.Reader to make it able to be read more than once, etc.. But at the same time, if you have all those info, that _usually_ means you already have either a []byte or string, and you would most likely use one of the 3 types to convert that into an io.Reader, so that special handling is enough without adding more public apis, and the go team is notoriously conservative when adding new public apis.
There's Content-Length, and there's also the need to read it multiple times in case a redirect happens (so the same body need to be sent again when being redirected).
As a result, the implementation in stdlib would check a few common io.Reader implementations (bytes.Buffer, bytes.Reader, strings.Reader) and make sure it stores something that can be read multiple times (if it's none of the 3, it's read fully into memory and stored instead).