2

So I am trying to write some code that allows me to edit values in an array in a struct. This example uses "Status" as a possible value to alter, but this is just a simplification to try to get my intent across.

package main

import(
  "fmt"
)

type Parent struct {
  Children []Child
}

type Child struct {
  Status string
}

func (p *Parent) Add() *Child {
  var child Child
  child.Status = "1"
  p.Children = append(p.Children, child)
  return &p.Children[len(p.Children)-1]
}

func main() {
  var p Parent
  child := p.Add()
  child.Status = "2"
  fmt.Println(p)
  fmt.Println(child)
}

This doesn't feel "proper". How should I do this in golang? Certainly I could pass the value in as a parameter, but in my particular case I would like to edit function pointers that are inside the Child struct (not in this code to keep it short) after having added the child. That feels nicer, but maybe I just need to pass them as parameters to the Add method?

eg

func (p *Parent) Add(fn1 func(), fn2 func()) *Child {

Anyway just wondering if anybody has any thoughts on this type of situation.

MDrollette
  • 6,887
  • 1
  • 36
  • 49
  • What are the function pointers for? – Ainar-G Nov 13 '14 at 13:07
  • I am trying to build a simple menu system in opengl actually. So the Child in this case is going to be text, etc that a mouse hovers over, can be clicked on etc. I want the user to be able to define what happens via the function pointers. I just don't feel so great about having an api call for Add that has positioning on screen plus all of the other expected interactions tagged on. Maybe that is best though. –  Nov 13 '14 at 13:11
  • The simplistic design has a Parent type Menu struct which holds a slice of, in this case, Label objects. I am simply not sure how to give golang appropriate access to a Label once it has been added to the Menu. It is a minor thing and I don't have the code up on github yet for viewing. Hope my explanation is understandable. –  Nov 13 '14 at 13:15
  • It's understandable, although it'll be better if you update the original answer to contain that info. Going to answer in a minute. – Ainar-G Nov 13 '14 at 13:18
  • Note *any* append can reallocate the array underlying `Children`, and when one does, any existing `*Child` pointers will still point at the old array. That can be OK if you never hold onto a `*Child` across an append. Otherwise, you can either make the slice `[]*Child` or (if Child is smallish--think <10 words) append children by copying a `Child` value. – twotwotwo Nov 14 '14 at 02:56
  • There is a bit on `[]Foo` vs. `[]*Foo` in this answer: http://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values/23551970#23551970 (happy to try to clarify that if you comment with any q's there). – twotwotwo Nov 14 '14 at 03:00

3 Answers3

2

I think you should create the Child outside of the Add method and pass it in. If you want to manipulate the Child, do that before you passed it in. You might use methods on the Child struct to do that:

func (c *Child) Init(fn1 func(), fn2 func()) {
  c.Status = "1"
  ...
}

func (p *Parent) Add(c *Child) *Child {
  p.Children = append(p.Children, c)
  return c
}

func main() {
  var p Parent
  var child Child
  child.Init(...)       // <- pass something in there...
  p.Add(&child)
  child.Status = "2"
  fmt.Println(p)
  fmt.Println(child)
}
Swoogan
  • 5,298
  • 5
  • 35
  • 47
  • Gotcha I will have to think about that. I am trying to make a simple library with this and pushing the child declaration out of the Add method means a user of the library will have to do that on their own. That isn't the end of the world of course. Thanks for the suggestion! –  Nov 13 '14 at 13:20
1

I'd suggest doing this in the simplest way possible. Remember the KISS principle. One part of the system does one and only one thing.

Following this logic, your Add(c *Child) method should only add a child. Creating a Child should be done separately, same for giving a Child some unique properties.

For your OpenGL menu system, this approach fits nicely as well:

m := NewMenu()
t := NewText(text)
t.OnClick = someCallback
// Some other t initialisation.
m.Add(t)
// Some time later you can still change t if it's a pointer and m.Add
// doesn't copy.
t.OnHover = someOtherCallback

The decision on whether to keep your labels exported, or hide them and provide getters/setters is up to you, and depends solely on your system's design and consistency requirements.

Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • Ok thanks! This is what swoogan suggested so two votes for the same approach is convincing to me. I would upvote you if I could but my account is too new :P –  Nov 13 '14 at 13:23
1

Ok the following works for me. The code is changed again to try to focus more clearly on the particular issue I was having. It all comes down to properly using pointers. Thanks for the suggestions.

package main

import (
    "fmt"
)

type HairColor func() string
type Parent struct {
    Children []*Child
}

type Child struct {
    Age           int
    ShowHairColor HairColor
}

func (p *Parent) Add(c *Child) {
    p.Children = append(p.Children, c)
}

func main() {
    var parent Parent
    var child Child

    child.Age = 10
    parent.Add(&child)

    child.ShowHairColor = func() string {
        return "red"
    }
    fmt.Printf("%v\n", parent.Children[0])
    fmt.Printf("%v\n", child)

    fmt.Println(parent.Children[0].ShowHairColor())
}