Repository: go
Issue: encoding: add ScalarMarshaler and ScalarUnmarshaler
Labels: Proposal,Proposal-Accepted

There are often types that are fundamentally opaque wrappers around a scalar type. One such example is `atomic.Bool` and friends. In order to get these types to operate well with serialization libraries, they would need to implement one of the `Marshaler` or `Unmarshaler` interfaces (see #54582).

The current interfaces in the "encoding" package fall short:
* `encoding.TextMarshaler` and `encoding.TextUnmarshaler` only treat the type as if it were a string, and so can't natively represent a boolean or number.
* `json.Marshaler` and `json.Unmarshaler` is specific to JSON, which means that only "encoding/json" benefits and not other encoding libraries (in stdlib such as "encoding/xml" or third-party packages).
* The `MarshalText` or `MarshalJSON` always allocates, which is rather heavy-weight for something that is encoding a scalar.

To work around these detriments, I propose the addition of the following interface into the "encoding" package:
```go
type ScalarMarshaler[T bool | int64 | uint64 | float64 | complex128] interface {
    MarshalScalar() (T, error)
}
type ScalarUnmarshaler[T bool | int64 | uint64 | float64 | complex128] interface {
    UnmarshalScalar(T) error
}
```
The type list specifies exactly 5 types, which matches the superset kind of all the scalar kinds in Go:
* int8, int16, int32, int, etc. are not included since they can be represented as int64. When unmarshaling, the implementation can return an error if the provided int64 is out of range.
* We do not declare the constraint as using the underlying kind (i.e., not `~bool | ~int64 | ~uint64 | ~float64 | ~complex128`) to keep the possible set of concrete interfaces a finite set that encoding packages can actually check for.
* string is not included because 1) a string is technically not a "scalar" type in that it can represent an infinite number of values, while scalar pertains to a finite set of possible values (i.e., following some scale), and 2) because we already provide `TextMarshaler` and `TextUnmarshaler`, which are more or less the same thing.

Many serialization formats have first-class support booleans and various numeric types, so this interface provides a way to express that fact. 

It is a feature (not a bug) that the 5 possible representations of this interface all use the same method name. That is, for a given type, it can only choose to implement scalar representation for one of the scalar kinds. We do not need to worry what happens when a type implements both `MarshalScalar() (int64, error)` and `MarshalScalar() (float64, error)` at the same time.

If `MarshalJSON` and `MarshalText` and `MarshalScalar` are all present, it is the discretion of the encoding package to choose the precedence order. I recommend `MarshalJSON` taking precedence over `MarshalText`, taking precedence over `MarshalScalar`.

It is up to an encoding package to decide which of the scalar kinds it wants to support. For example, "encoding/json" would support scalar marshaling for bool, int64, uint64, and float64, but reject complex128 since JSON has no representation of complex numbers. This matches the existing "encoding/json" behavior where it can't serialize complex128.

\cc @johanbrandhorst @mvdan