Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Pointerized structs #23162

Closed
jaekwon opened this issue Dec 17, 2017 · 4 comments
Closed

Proposal: Pointerized structs #23162

jaekwon opened this issue Dec 17, 2017 · 4 comments

Comments

@jaekwon
Copy link

jaekwon commented Dec 17, 2017

Here's the proposed feature:

type Node struct* {
  Left Node
  Right Node
}

var n Node = nil // not the same as a zero value.
n.Left = ... // runtime-panic
n = Node{}
n.Left = ...
n.Right = ...

var n2 = n
n2.Left = ... // This won't affect `n`.
n2.Left.Left = ... // compile-time error, n2.Left is not addressable.

n.Left = n // circular references are OK.

Without this proposal, performance often dictates whether I refer to a struct by value or reference, even when the struct is meant to be immutable. With this proposal, the only thing I would worry about is the security model.

The * comes after struct, so as to not conflict with *struct{...} which is a pointer type. struct*{...} is a (pointerized) struct type, and reflect would mostly treat it the same as a struct{...}, but reflect.ValueOf(struct*{...}).IsPointerized() == false. A pointerized struct type could still be mutable... var a *struct*{...} = &struct*{...}.

Here's one way to mix pointerized structs w/ mutable structs & mutexes.

type foo struct* {
  Value interface{}
}
func (f foo) GetValue() interface{} {...}

type Foo struct {
  mtx sync.RWMutex
  foo
}
func (f *Foo) SetValue(interface{}) {...} // Lock/Unlock
func (f *Foo) GetValue() interface{} {...} // RLock/RUnlock

f = Foo{...}
f.SetValue(...) // ok, f is addressable
g := f
g.SetValue(...) // ok, g is addressable
func cp(f Foo) Foo { return f } // returns a non-addressable copy
cp(g).SetValue(...) // compile-error, not addressable.
cp(g).GetValue(...) // calls foo.GetValue, mtx not needed

In the current behavior of Golang, the last line would not compile because type Foo hides the foo.GetValue method. I'm not sure what the consequences are of allowing this in general, but it seems useful for this purpose.

Context

You might have seen the rule of thumb for values-vs-pointers: if your struct has few fields, and you don’t need to mutate it, then you don’t need to use pointers. On the other hand, if the struct has too many fields, then you might want to use pointers. I find it difficult to always tell ahead of time whether structs ought to be pointers or not, but recently I discovered a nice way to work around the performance problem:

type BigStructWrapper interface {}
type BigStruct struct {...}

// cpy is immutable but this is slow.
// I've tested this w/ benchmarks and very large
// structs that are nested many levels.
{
  var str BigStruct = BigStruct{...}
  var cpy BigStruct = str
}

// cpy is still immutable and this is fast.
{
  var str BigStructWrapper = BigStruct{...}
  var cpy BigStructWrapper = str
}

Interface values are like pointers, but the value of an interface is not addressable so you can't mutate an interface value's shallow fields unless it's a pointer.

This shows that Golang can already optimize the copying/assignment of immutable structs by allocating them on the heap. The downside is that the code gets ugly (e.g. bigStruct.Field, not bigStructWrapper.(BigStruct).Field). Thus this proposal.

This is part of a set of proposals to improve Golang's security model

@ianlancetaylor
Copy link
Contributor

In Go, any reference counts would have to be manipulated using atomic instructions. Although atomic instructions aren't especially slow on modern processors, they are definitely slower than ordinary memory accesses. It's not obvious to me that reference counting is better than ordinary garbage collection.

@jaekwon
Copy link
Author

jaekwon commented Dec 18, 2017

I see, I was mistaken. I don't know enough about which is faster.

Structs cannot embed each other in a cycle without using pointers, so ref-counting could be done, but as ianlancetaylor pointed out, it's not necessarily better.

https://www.quora.com/How-do-reference-counting-and-garbage-collection-compare/answer/Jon-Harrop-1

@jaekwon jaekwon changed the title Proposal: Compiler optimizations for immutable struct assignment Proposal: Pointerized structs Jan 9, 2018
@jaekwon
Copy link
Author

jaekwon commented Jan 9, 2018

@OneOfOne Please review, you gave me a thumbs up but I updated the proposal with what I think is a better one.

@rsc
Copy link
Contributor

rsc commented Jan 22, 2018

This proposal does not say what problem you are solving, nor define what the feature is trying to do. It seems to be some kind of auto-dereferenced pointer, except when it's not (n = nil).

We're exceedingly unlikely to adopt something like this. It adds an enormous amount of complexity for little benefit. If you need these exact semantics, it would be better to write them in the program that needs them, using ordinary pointers.

@rsc rsc closed this as completed Jan 22, 2018
@golang golang locked and limited conversation to collaborators Jan 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants