Go: type of capacity and length

Created on 24 Jun 2019  Â·  15Comments  Â·  Source: golang/go

What version of Go are you using (go version)?

$ go version
go version go1.12.1 darwin/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output

$ go env

GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/regina/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/opt/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/Cellar/go/1.12.1/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.12.1/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/kz/gbh5zf3x4gq_l8wjbhdgdq440000gq/T/go-build689347121=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

define func (my_own_little []struct) Len() uint { return cap(my_own_little) }

What did you expect to see?

not an error

What did you see instead?

go "corrects" me that the return type of cap is int, even though the spec says that both the capacity and length of a slice are ≥0.

FrozenDueToAge

Most helpful comment

@isomorphisms This general idea has been discussed several times before, for example at https://groups.google.com/forum/#!msg/golang-nuts/jJWAAMdquwQ/jhWhxJJbzVYJ .

All 15 comments

Same problem with Swap and Less (needed for the sort package). Go expects the type of nonnegative integers to be int rather than uint.

The spec also says:

The built-in functions len and cap take arguments of various types and return a result of type int. The implementation guarantees that the result always fits into an int.

Swap and Less takes in a int cause slice and array sizes are limited to the max of int so it's easy to check if the index overflowed.

@AlexRouSg I don’t understand your comment. Doesn’t the result fit into a uint as well? And wouldn’t it be just as “easy” to check slice and array limits if they were limited to max uint?

bufio ScanBytes, ScanLines, and ScanRunes advance int should also seemingly be a uint, since bufio explicitly disallows ErrNegativeAdvance.

As noted the Go spec clarifies that len and cap have return type int.
https://golang.org/ref/spec#Length_and_capacity

The positive value range of an int is not the same as an uint and Go requires explicit type conversions between int and uint types. https://golang.org/ref/spec#Conversions

@martisch You didn’t explain why this is the right choice.

math/rand has the same problem: returns a uint with return type int.

https://golang.org/pkg/math/rand/#Intn
https://golang.org/pkg/math/rand/#Perm
https://golang.org/pkg/math/rand/#Shuffle
.
.
.
etc

@martisch You didn’t explain why this is the right choice.

The report was about "go" complaining that the return type of cap is int and that there should be no error instead. This is the right behavior because the language specification says that cap and len have return type int and there is no automatic conversion. As this was not a proposal to change language to the "right choice" I did not include reasons why this might be better.

For proposals to change the language to discuss of pros and cons see: https://github.com/golang/proposal

One reason why int can be better is that it can e.g. be easier to iterate over all but the last indices/element of a slice:

for i:=0; i < len(slice)-1; i++ {
....
}

or in reverse order:

for i:= len(slice); i >= 0; i-- {
....
}

vs if len and cap would be uint (and all else in the language equal) that would need to be written as something like:

if len(slice) > 1 {
  for i:=uint(0); i < len(slice)-1; i++ {
  ....
  }
}

or in reverse order:

for i := len(slice); ; i-- {
  ....
  if i == 0 {
    break
  }
}

@isomorphisms This general idea has been discussed several times before, for example at https://groups.google.com/forum/#!msg/golang-nuts/jJWAAMdquwQ/jhWhxJJbzVYJ .

@ianlancetaylor Thanks. If I’m understanding your response on google groups correctly, that sounds like a problem with how the uint type handles --. (You could just as well return 0 - 1 = 0 for that type.)

https://research.swtch.com/dogma

Remember that none of the decisions in Go are infallible; they’re just our best attempts at the time we made them, not wisdom received on stone tablets. If someone asks why Go does X instead of Y, please try to present the engineering reasons fairly, including for Y, and avoid argument solely by appeal to authority. It’s too easy to fall into the “well that’s just not how it’s done here” trap.

You are suggesting that uint subtraction should be done using saturating arithmetic? That is an interesting idea that I have not encountered before. But it would cause very odd behavior for people who write

    var uint i = 0
    j := i - 1
    k := j + 1
    // Now k is 1 rather than 0?

@ianlancetaylor You could also make - : (uint, uint) -> int. This isn’t too weird, as datetimes follow a similar pattern: -: (timestamp, timestamp) -> possibly negative duration.

Here’s another example from the standard library:

type Hash interface {
    Write(p []byte) (n int, err error)
    Sum(b []byte) []byte
    Reset()
    Size() int
    BlockSize() int
}

Block sizes cannot be negative.

In Go arithmetic operations on two values of the same type return a value of that type. There are no current exceptions. So that would be a pretty big change to the language.

The fact that a block size cannot be negative does not imply that BlockSize should return uint, for the reasons mentioned at https://groups.google.com/forum/#!msg/golang-nuts/jJWAAMdquwQ/jhWhxJJbzVYJ .

This issue is closed and there isn't much point to further conversation on the topic.

Was this page helpful?
0 / 5 - 0 ratings