Contracts Draft: why are method pointers allowed

501 views
Skip to first unread message

Ilia Choly

unread,
Jul 29, 2019, 10:05:14 AM7/29/19
to golang-nuts
When converting a non-pointer value to an interface, pointer methods cannot be used to satisfy the interface. Even though the compiler could add instructions to take the value's address, this is not allowed because it's error prone. Since pointer methods usually mutate the receiver, you don't want to be operating on a copy. https://play.golang.org/p/XDiki8_uHMs

When reading the contracts proposal, I'm confused by the seemingly contradictory decision to allow pointer methods. https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md#methods

In order to avoid worrying about the distinction between value methods and pointer methods, in a generic function body all method calls will be pointer method calls. If necessary, the function body will insert temporary variables, not seen by the user, in order to get an addressable variable to use to call the method ...This makes it easier to understand which types satisfy a contract, and how a contract may be used. It has the drawback that in some cases a pointer method that modifies the value to which the receiver points may be called on a temporary variable that is discarded after the method completes. It may be possible to add a vet warning for a case where a generic function uses a temporary variable for a method call and the function is instantiated with a type that has only a pointer method, not a value method.

The same example converted to use interfaces fails https://play.golang.org/p/FBbXQw7dKL6 What's the motivation for this design decision?

Ian Lance Taylor

unread,
Jul 29, 2019, 2:25:12 PM7/29/19
to Ilia Choly, golang-nuts
The motivation is to avoid requiring contracts to specify whether a
method is a pointer method or a value method, just as we do not
require interface types to specify whether a method is a pointer
method or a value method. It does make it possible to write certain
kinds of bugs, but it's not clear how much that arises in practice.

That said, it's likely that we will have to modify contracts to
require a pointer method in some cases, in order to separate defining
a variable of some type with invoking pointer methods on that
variable, so I would not be surprised if this aspect of the design
draft is changed.

Ian

Steven Blenkinsop

unread,
Jul 29, 2019, 8:49:31 PM7/29/19
to Ian Lance Taylor, Ilia Choly, golang-nuts
On Mon, Jul 29, 2019 at 2:25 PM, Ian Lance Taylor <ia...@golang.org> wrote:
The motivation is to avoid requiring contracts to specify whether a method is a pointer method or a value method, just as we do not require interface types to specify whether a method is a pointer method or a value method.

I'm not sure whether pointer method vs. value method is the distinction that needs to be made. For the general purpose of writing code that calls methods on arbitrary types, what we want to know is whether a type-parameterized function should assume it needs an addressable value or if it can use a non-addressable value of the parameter type to call a method. Any references/dereferences that need to be done can be done automatically as long as you know whether the value needs to be addressable and the code is written accordingly (i.e. without needing to take the address of an implicit variable). That might end up being represented in the language as though you're passing pointers into the methods, but I don't think that needs to map to whether the concrete method itself accepts a pointer or a value.

To illustrate, if you have 

    func Foo(type T ...)(slice []T) {
         for i := range slice {
             slice[i].DoStuff()
         }
    }

what sort of bound do you need on `T` so that it can handle each combination of:

1. `T` is passed a value type `Arg` or a pointer type `*Arg`, and
2. `DoStuff` is defined on `Arg` or on `*Arg`?

Why you sort of want is a constraint like

    HasDoStuff(T), HasDoStuff(*T)

where at least one or the other should capture the `DoStuff` method in each case, and thus the combined constraint should allow you to call methods on/involving addressable `T` values (potentially based on the fiction that the accepted type is `*T`). I'm not sure what the best way of write such a constraint is, though, or how common this kind of case is.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAOyqgcWgzkqS4GHH%2BbcQd6KNGJrRd1v2d0D2GU5LsomJF0SL%3Dw%40mail.gmail.com.

Ilia Choly

unread,
Jul 30, 2019, 9:12:11 AM7/30/19
to golang-nuts
This completely answers my question.


On Monday, July 29, 2019 at 8:49:31 PM UTC-4, Steven Blenkinsop wrote:
On Mon, Jul 29, 2019 at 2:25 PM, Ian Lance Taylor <ia...@golang.org> wrote:
The motivation is to avoid requiring contracts to specify whether a method is a pointer method or a value method, just as we do not require interface types to specify whether a method is a pointer method or a value method.

I'm not sure whether pointer method vs. value method is the distinction that needs to be made. For the general purpose of writing code that calls methods on arbitrary types, what we want to know is whether a type-parameterized function should assume it needs an addressable value or if it can use a non-addressable value of the parameter type to call a method. Any references/dereferences that need to be done can be done automatically as long as you know whether the value needs to be addressable and the code is written accordingly (i.e. without needing to take the address of an implicit variable). That might end up being represented in the language as though you're passing pointers into the methods, but I don't think that needs to map to whether the concrete method itself accepts a pointer or a value.

To illustrate, if you have 

    func Foo(type T ...)(slice []T) {
         for i := range slice {
             slice[i].DoStuff()
         }
    }

what sort of bound do you need on `T` so that it can handle each combination of:

1. `T` is passed a value type `Arg` or a pointer type `*Arg`, and
2. `DoStuff` is defined on `Arg` or on `*Arg`?

Why you sort of want is a constraint like

    HasDoStuff(T), HasDoStuff(*T)

where at least one or the other should capture the `DoStuff` method in each case, and thus the combined constraint should allow you to call methods on/involving addressable `T` values (potentially based on the fiction that the accepted type is `*T`). I'm not sure what the best way of write such a constraint is, though, or how common this kind of case is.

On Mon, Jul 29, 2019 at 2:25 PM, Ian Lance Taylor <ia...@golang.org> wrote:
On Mon, Jul 29, 2019 at 7:05 AM Ilia Choly <ilia...@gmail.com> wrote:
>
> When converting a non-pointer value to an interface, pointer methods cannot be used to satisfy the interface. Even though the compiler could add instructions to take the value's address, this is not allowed because it's error prone. Since pointer methods usually mutate the receiver, you don't want to be operating on a copy. https://play.golang.org/p/XDiki8_uHMs
>
> When reading the contracts proposal, I'm confused by the seemingly contradictory decision to allow pointer methods. https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md#methods
>
>> In order to avoid worrying about the distinction between value methods and pointer methods, in a generic function body all method calls will be pointer method calls. If necessary, the function body will insert temporary variables, not seen by the user, in order to get an addressable variable to use to call the method ...This makes it easier to understand which types satisfy a contract, and how a contract may be used. It has the drawback that in some cases a pointer method that modifies the value to which the receiver points may be called on a temporary variable that is discarded after the method completes. It may be possible to add a vet warning for a case where a generic function uses a temporary variable for a method call and the function is instantiated with a type that has only a pointer method, not a value method.
>
>
> The same example converted to use interfaces fails https://play.golang.org/p/FBbXQw7dKL6 What's the motivation for this design decision?

The motivation is to avoid requiring contracts to specify whether a
method is a pointer method or a value method, just as we do not
require interface types to specify whether a method is a pointer
method or a value method.  It does make it possible to write certain
kinds of bugs, but it's not clear how much that arises in practice.

That said, it's likely that we will have to modify contracts to
require a pointer method in some cases, in order to separate defining
a variable of some type with invoking pointer methods on that
variable, so I would not be surprised if this aspect of the design
draft is changed.

Ian

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.

alan...@gmail.com

unread,
Jul 30, 2019, 9:58:27 AM7/30/19
to golang-nuts
One way of avoiding this muddle between pointer and value methods would be to require that any type parameter (T say) which is subject to a contract cannot be replaced by a pointer type.

The contract could then be absolutely explicit about whether a method's receiver were T or *T and any generic function using this contract wouldn't compile if the method were called inappropriately.

This,of course, wouldn't prevent you from using ordinary/return parameters of type *T or some aggregate type based on *T. Nor would it prevent type parameters which were not subject to a contract from being replaced by pointer types.

Alan

roger peppe

unread,
Jul 30, 2019, 11:54:44 AM7/30/19
to alanfo, golang-nuts
On Tue, 30 Jul 2019 at 14:58, <alan...@gmail.com> wrote:
One way of avoiding this muddle between pointer and value methods would be to require that any type parameter (T say) which is subject to a contract cannot be replaced by a pointer type.

What do you mean by this? Do you mean you can't use a pointer type as a type parameter? That would seem rather restrictive to me.
For example, AFAICS it would mean you couldn't have a single function that is able to call a String method on both `*big.Int` and `reflect.Type`.

alan...@gmail.com

unread,
Jul 30, 2019, 2:31:24 PM7/30/19
to golang-nuts
My suggestion was that you can't use a pointer type as a type parameter if the latter is subject to a contract.

In the case you mention, the contract could be expressed as a disjunction of value and pointer methods:

contract stringer(T) {
   T String() string, *T String() string
}

However, if T were used as an ordinary/return parameter type, you would need two functions - one for T and one for *T - and so may be it is too restrictive.

On the other hand and more generally, not knowing whether the type parameter represented a pointer or a value might lead to some awkward coding. For example, you wouldn't be able to de-reference the type argument as it might not be a pointer.

It's clearly an area where some more thought is needed as Ian intimated earlier.

Alan

Axel Wagner

unread,
Jul 30, 2019, 2:47:05 PM7/30/19
to alan...@gmail.com, golang-nuts
On Tue, Jul 30, 2019 at 8:31 PM <alan...@gmail.com> wrote:
My suggestion was that you can't use a pointer type as a type parameter if the latter is subject to a contract.

I'm not sure I understand you. Wouldn't that preclude using a generic map with pointers as keys?
 
In the case you mention, the contract could be expressed as a disjunction of value and pointer methods:

contract stringer(T) {
   T String() string, *T String() string
}

Currently, Disjunctions only apply to a single type. You can't form expressions like this.
IMO that's a good restriction to maintain. Because the more powerful the contract language becomes, the harder it'll be to make it useful.
 
On the other hand and more generally, not knowing whether the type parameter represented a pointer or a value might lead to some awkward coding. For example, you wouldn't be able to de-reference the type argument as it might not be a pointer.

If a generic function wants to de-reference an argument, it should specify that as a pointer: func f(type T) (p *T)
This is the same as with slices, maps, channels, functions or any composite type - you can't express "type parameter T should be a slice of some kind", because you are instead expected to just specify []T if you want a slice.
 

It's clearly an area where some more thought is needed as Ian intimated earlier.

Alan

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/302c6d33-a8ea-4e8a-b02b-7cff1b3de1c5%40googlegroups.com.

alan...@gmail.com

unread,
Jul 30, 2019, 3:58:02 PM7/30/19
to golang-nuts
 
On Tuesday, July 30, 2019 at 7:47:05 PM UTC+1, Axel Wagner wrote:
On Tue, Jul 30, 2019 at 8:31 PM <alan...@gmail.com> wrote:
My suggestion was that you can't use a pointer type as a type parameter if the latter is subject to a contract.

I'm not sure I understand you. Wouldn't that preclude using a generic map with pointers as keys?

No, it wouldn't preclude that but the key would need to expressed as a *K rather than a K, if K were subject to a contract. As a pointer type it would automatically follow that *K was comparable.
 
In the case you mention, the contract could be expressed as a disjunction of value and pointer methods:

contract stringer(T) {
   T String() string, *T String() string
}

Currently, Disjunctions only apply to a single type. You can't form expressions like this.
IMO that's a good restriction to maintain. Because the more powerful the contract language becomes, the harder it'll be to make it useful.

Well, currently you can't use *T as a method receiver type in a contract so this would be a necessary exception to that rule if my suggestion were adopted.

However, I agree with your general point that the restriction should be maintained in all other circumstances.
 
On the other hand and more generally, not knowing whether the type parameter represented a pointer or a value might lead to some awkward coding. For example, you wouldn't be able to de-reference the type argument as it might not be a pointer.

If a generic function wants to de-reference an argument, it should specify that as a pointer: func f(type T) (p *T)
This is the same as with slices, maps, channels, functions or any composite type - you can't express "type parameter T should be a slice of some kind", because you are instead expected to just specify []T if you want a slice.

Yes, but if T happened to be a pointer to some type, then *T would be a double pointer to that type. As the design currently stands, you'd have no way of knowing whether T was a pointer or not unless the contract specified that it was one of the predefined types.

What I was trying to suggest here is that it would be helpful in some circumstances to know whether T was or was not a pointer type which would be a by-product of my suggestion.

Alan
 

Henrik Johansson

unread,
Jul 30, 2019, 5:21:05 PM7/30/19
to alan...@gmail.com, golang-nuts
Why make any distinction between pointers and non pointers? Isn't the (usual) point to allow the caller to decide upon instantiation?
We define a contract in terms of an unknown set of types and then let whoever uses it fullfil the contract however they want?

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Axel Wagner

unread,
Jul 30, 2019, 6:07:32 PM7/30/19
to alan...@gmail.com, golang-nuts
Ah, thanks, I think I understand now. AIUI this would essentially require that the author of a generic function or type would have to write a contract to specifically allow or disallow pointer/value receivers to satisfy the contract?

I personally don't really like the idea of putting that decision on the implementer of a generic type or function - for pretty much the same reason I don't want to know whether an interface-implementation uses pointer or value receivers. All I (the implementer of a function taking the interface) care is that they satisfy the API I require. I can't tell you (the implement of that interface) whether you should do that via a pointer, a basic type, an empty struct or a slice.

So IMO it's fine, for all operations I could wish to do with a pointer (channel, slice, map…) to explicitly put that in my function signature. Method calls are kind of an exception to that though - I still only care about being able to call that method, just like with interfaces, but to do that I *need* to know whether it's a pointer or value receiver. That's why I think the exception from the design for that case is kind of justified (even if a bit unfortunate) - specifically because it allows the implementer of the generic function to ignore a distinction that is only meaningful to its caller.

I do kind of agree with OP though, that it would seem more logical to just require the caller to pass a pointer, when a pointer is required, just as we do with interfaces. But weak opinions :)

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Max

unread,
Aug 1, 2019, 6:35:14 AM8/1/19
to golang-nuts
I think that a single syntax allowing both pointer receivers and value receivers - with no mechanism to distinguish them - creates unnecessary ambiguity, and in some cases it can concretely be a problem.

Consider the following example, adapted from 'math/big.Int`:
```
contract Comparable(T) {
  // return -1 if receiver is lesser than 'other'
  // return +1 if receiver is greater than 'other'
  // return 0 if they are equal
  T Cmp(other T) int
}
```
it seems perfectly reasonable yet, depending on the exact semantics of contracts, `math/big.Int` may have troubles satisfying it, because its method 'Cmp' has the signature
```
func (recv *big.Int) Cmp(other *big.Int) int
```
Thus it would require 'T' to be a pointer - namely '*big.Int' - to match.
It would mean that the receiver itself of the contract 'T Cmp(other T) int' is a pointer. Is it a case allowed by contracts?
And how does it interact with the rule that both pointer receivers and value receivers are allowed?


If you ignore for a moment the ability of contracts to specify a union of types,
I prefer my proposal https://github.com/cosmos72/gomacro#generics where contracts are just generic interfaces,
and they can *optionally* specify the receiver type - used in case you want to *remove* the ambiguity between pointer and value receiver types.
They clearly require the possibility to specify multiple contracts in a generic type or function signature, while the current proposal
allows only a single contract in a in a generic type or function signature, thus requiring contracts to be able to specify the methods of several unrelated types.

Steven Blenkinsop

unread,
Aug 1, 2019, 12:03:47 PM8/1/19
to Max, golang-nuts
On Thu, Aug 1, 2019 at 6:35 AM, Max <massimilia...@gmail.com> wrote:
Thus it would require 'T' to be a pointer - namely '*big.Int' - to match.
It would mean that the receiver itself of the contract 'T Cmp(other T) int' is a pointer. Is it a case allowed by contracts?

Yes, this is something which is supported be contracts. But this does raise the point that the ability to try and fudge over whether `T` or `*T` (where `T` is a contract parameter) satisfies a method constraint is limited by the fact that constraint parameters can appear in multiple positions in a given method signature, and only one of those places (the receiver) has conventionally allowed auto-referencing. Also, it doesn't matter much that `Cmp` can be in the method set of both `A` and `*A` (where `A` is a concrete value type) when only one of those types will be identical to the type the `other` method parameter, so only one type can satisfy the contract.

Personally, I think the implication of this is that contract method constraints should be based on method sets, i.e. `A` can't satisfy a contract using a method defined on `*A`, just like with interfaces. 

This does rule out being able to write the generic function I showed above, where we want to be able to accept a slice of either `A` or `*A`, and call `*A` methods without needing to know which type is represented by the type parameter. I'm not sure how strong the desire to support this sort of use case is. Forcing you to use a `[]*A` in this case would be slightly unfortunate (or `[]A` and `*T DoStuff()`, but that would require that `T` be a value type).

This case could technically be handled with some rather more involved uses of contracts, like

contract Slice(S, Elem, Base) {
    S []Elem
    S Index(int) *Base
    *Base DoStuff()
}

You'd just need two helper types to wrap your slice, one where `Elem` and `Base` are identical, and one where `Elem` is identical to *Base.
 
It's unfortunate that you'd need to specify `Elem` and `Base` in the signature of any function using this contract, given that they can be inferred from `S`, but I'm not sure whether there'd be any willingness to add associated types to the design, i.e.

contract Slice(S) {
    type S.Elem
    type S.ElemBase
    S []S.Elem
    S Index(int) *S.ElemBase
    *S.ElemBase DoStuff()
}

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

alan...@gmail.com

unread,
Aug 5, 2019, 7:55:19 AM8/5/19
to golang-nuts
For those who haven't already noticed, I thought I'd point out that the draft design has now been changed (as Ian intimated it might be) so that contracts may now require a pointer method in some cases i.e. if the type parameter is T one can now specify that *T has a certain method.

In particular, this will be needed if an implementing function is declaring a variable of type T but you then need to invoke a method on it where the receiver type is *T. To understand why this is so, one needs to re-read the design paper which has some examples of why the previous situation didn't work.

Looked at overall I think this is a better idea than what I was proposing earlier, though it might be difficult for folks to get their heads around the circumstances when and why a pointer method is needed. However, as it's probably a situation which will arise infrequently in practice, it should not be a major concern.

Alan

Steven Blenkinsop

unread,
Aug 6, 2019, 9:45:02 PM8/6/19
to alan...@gmail.com, golang-nuts
This text seems somewhat contradictory:

The function Init cannot be instantiated with the type MyInt, as that type does not have a method Set; only *MyInt has Set.

...

If a method is listed in a contract with a plain T rather than *T, then it may be either a pointer method or a value method of T.

The intent seems to be that a contract that constrains *T is more specific than a contract that constrains T. It requires the conditions needed for initialization, on both the caller and callee side.

It's the inability to make a sufficiently specific contract that makes the current choice to allow calling pointer methods on values based on a T constraint unfortunate. Initialization is an important use case, but so is updating, and being able to rely on consistent behaviour when it comes to methods that update the receiver—for both the caller and generic callee—makes code easier to reason about. Interfaces get this right and it would be a shame to lose it with generics.

The use case I posted earlier—of being able to update values in a slice using a pointer method—would be nice to have supported as well, but not at the expense of the more common case. I'm not sure what the answer is. Perhaps contracts could allow you to use a pseudo-type like &T denoting addressable T values, and let the compiler figure out matching up levels of indirection between generic code and the methods it calls, but this might add more complexity to the design than it's worth.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages