Unexpected pointer dereference behavior

136 views
Skip to first unread message

Mikhail Mazurskiy

unread,
Apr 22, 2021, 7:15:54 AM4/22/21
to golang-nuts
Hi all,

I stumbled upon this strange looking piece of code [1]. I thought it dereferences the pointer, creating a copy of the mutex, and then calls RLock() on the copy (i.e. a nop). But it does not work this way. I experimented with it [2] and [3] and it looks like my reading of code is wrong. Could someone explain how dereferencing a pointer works in this case please? Language spec is not very explicit about it (unless I missed it) [4].

Thank you.
Mikhail.

Jan Mercl

unread,
Apr 22, 2021, 7:27:56 AM4/22/21
to Mikhail Mazurskiy, golang-nuts
On Thu, Apr 22, 2021 at 1:15 PM Mikhail Mazurskiy
<mikhail....@gmail.com> wrote:

> I stumbled upon this strange looking piece of code [1]. I thought it dereferences the pointer, creating a copy of the mutex, and then calls RLock() on the copy (i.e. a nop). But it does not work this way. I experimented with it [2] and [3] and it looks like my reading of code is wrong. Could someone explain how dereferencing a pointer works in this case please? Language spec is not very explicit about it (unless I missed it) [4].

See https://golang.org/ref/spec#Selectors

""""
3. As an exception, if the type of x is a defined pointer type and
(*x).f is a valid selector expression denoting a field (but not a
method), x.f is shorthand for (*x).f.
""""

In this case '(*observer).Lock()' and 'observer.Lock()' have the same
semantics. Like here: https://play.golang.org/p/bPj-ZfPwRWc

Mikhail Mazurskiy

unread,
Apr 22, 2021, 7:40:05 AM4/22/21
to Jan Mercl, golang-nuts
Thanks for the response, I didn't see this section. However, I'm still confused as it says the exception in 3. only applies to fields, not to methods. RLock() is obviously a method, not a field.

Jan Mercl

unread,
Apr 22, 2021, 7:49:46 AM4/22/21
to Mikhail Mazurskiy, golang-nuts
On Thu, Apr 22, 2021 at 1:39 PM Mikhail Mazurskiy
<mikhail....@gmail.com> wrote:

> Thanks for the response, I didn't see this section. However, I'm still confused as it says the exception in 3. only applies to fields, not to methods. RLock() is obviously a method, not a field.

You're right. And because the playground link seems to work for
methods as well, I'm now pretty confused if there's a compiler bug or
specs bug or I don't understand what's going on at all.

Axel Wagner

unread,
Apr 22, 2021, 8:19:15 AM4/22/21
to Jan Mercl, Mikhail Mazurskiy, golang-nuts
I think what is going on here is that it is wrong to say that `(*observer)` creates a copy:

For an operand x of pointer type *T, the pointer indirection *x denotes the variable of type T pointed to by x.

That variable is addressable and contains a `sync.RWMutex`. It's method set contains `Lock`, as methods on a pointer receiver are promoted to value receivers. And "If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()".

Thus, `(*observer).Lock()` is a shorthand for `(&(*observer)).Lock()` which is the same as `observer.Lock()`.
 
If, OTOH, you do
lock := (*observer)
lock.Lock()
you *would* create a copy - you'd create a new variable and assign to it the value in the variable pointed at by observer.

Either way, I think that code should be changed. It's pretty messy and obviously not clear, even if it's correct.

--
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/CAA40n-WdkuXq2u1NYfPWL2pJYq6m1pBrhEOA-qsToNUeVkFz2Q%40mail.gmail.com.

Nail Islamov

unread,
Apr 22, 2021, 7:36:26 PM4/22/21
to golang-nuts
Answers above don't explain the difference in behavior:

Why did deadlock happen in [1], despite creating a variable?
If I change the order ([2]), I do avoid a deadlock (meaning that I have two separate mutexes).

Obviously both examples are not for production use, just trying to understand what's happening there.

Nail Islamov

unread,
Apr 22, 2021, 8:21:11 PM4/22/21
to golang-nuts
Sorry, ignore that. In [1], I make a copy of a locked mutex, which creates a new locked mutex.
Basically the only non-obvious behavior here is that `&(*m) == m` by the rules of Go, the rest was explained above.

Axel Wagner

unread,
Apr 23, 2021, 2:53:42 AM4/23/21
to Nail Islamov, golang-nuts
On Fri, Apr 23, 2021 at 1:36 AM Nail Islamov <nil...@gmail.com> wrote:
Answers above don't explain the difference in behavior:

Why did deadlock happen in [1], despite creating a variable?
If I change the order ([2]), I do avoid a deadlock (meaning that I have two separate mutexes).

In [1], you copy a locked mutex and try to lock that. In [2], you copy an unlocked Mutex (really, the zero value of a Mutex) and try to lock that (which works).
Note that in case [1] the behavior actually is undefined: https://golang.org/pkg/sync/#Mutex
A Mutex must not be copied after first use.
In case [2] you copy it before first use, which is fine (as I said, you are copying the zero value of a Mutex).
 
Reply all
Reply to author
Forward
0 new messages