The general solution is using reflection as shown in Cerise Limón's answer. The cons of using reflection is that you have to export the fields, and that it's slower than it should or could be.
In your example although it is completely fine and sufficient for the function to take a value of type *s1
, as all types that embed s1
has a value of s1
(explicitly). The unqualified type name (without package name) acts as the field name for embedded fields:
s2 := s2{s1: s1{"A", "B"}}
fmt.Println(s2)
modifyStruct(&s2.s1)
fmt.Println(s2)
Output (try it on the Go Playground):
{{A B} }
{{B A} }
If you still want it to accept values of "any" (certain) types (so you don't have to refer to the embedded s1
field), but don't want to export the fields, then you may use interfaces for this. Using interfaces you keep the good parts of both solutions (remains fast, flexible and you do not have to export the fields):
type S1 interface {
AB() (string, string)
SetAB(a, b string)
}
type s1 struct {
a string `json:"a"`
b string `json:"b"`
}
func (s s1) AB() (string, string) { return s.a, s.b }
func (s *s1) SetAB(a, b string) { s.a, s.b = a, b }
func modifyStruct(s S1) {
a, b := s.AB()
s.SetAB(b, a)
}
Testing it:
s2 := s2{s1: s1{"A", "B"}}
fmt.Println(s2)
modifyStruct(&s2)
fmt.Println(s2)
Output is the same (try it on the Go Playground):
{{A B} }
{{B A} }
Note that (besides the *s1
type itself) any struct type (and also any pointer to struct type) that embeds *s1
automatically (implicitly) implements the S1
interface, and similarly any pointer to struct type that embeds s1
also implements S1
(and therefore *s2
and *s3
in your example).