golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.06k stars 17.68k forks source link

Proposal: Add a new type-safe tag system #27597

Closed theduke closed 6 years ago

theduke commented 6 years ago

EDIT: this is very similar to https://github.com/golang/go/issues/23637, so maybe it should be closed.

There is recent discussion around Go2 and some larger language changes.

With this, I thought about how one of the brittle and cumbersome features of Go might be improved upon, namely: struct tags.

A note beforehand: this is not a fleshed out proposal and I'm not involved with Go development and have no knowledge about constraints or limitations for a possible implementation. So this is from the perspective of the end-user.

I'm sure I've missed a lot of potential problems, but I'm these will turn up in discussion.

Problem statement

What are the problems with struct tags:

Tags are none the less a very useful feature and important for the ecosystem.

So the question is, how can the situation be improved?

Design Overview

I propose a new tagging system that allows typed tagging of both structs and struct fields.

It is designed to:

A simple example of how the system would work:

package json

type Tag struct {
  Name string
  OmitEmpty bool
  Skip bool
  Required bool
}

// A tag is defined as a regular Go function which may take and return arbitrary arguments/values.
// The tag functions are **executed on program startup** (think: package init() function).
// The returned value is later available at runtime via the **reflection** system.
func Json(tag: Tag) JsonTag {
  // The function can optionally modify the passed in configuration.
  tag.Name = strings.TrimSpace(tag.Name)
  return tag
}

// Tag functions do not need to take any arguments and also may have 
// no return type. 
// In this case they are just simple markers.
func Skip() {}
package users

type User struct {
  @json.Json(json.Tag{Required: true, Name: "user_name"})
  Username string

  @json.Skip()
  InternalField string
}

// Tags can also be applied to structs.

func TableName(table: string) string {
  return strings.TrimSpace(table)
}

@TableName("users")
type UserTable struct {...}

// Reading of values.

func getTableName(tableStruct interface{}) string {
  s := reflect.TypeOf(UserTable{})
  for _, tag := range s.TypedTags {
    if tag.Method.Name == "TableName" && tag.Method.PkgPath == "users" {
      val := tag.Values[0].Interface().(string)
      return val
    }
  }
  return ""
}

So, it works like this:

Implementation

I think this could be implemented in a backwards compatible way and with a relatively low amount of work.

reflect API

A rough draft of the changes to the reflect API:


// Contains the information for a single tag.
type TypedTag struct {
  // The tag method
  Method Method
  // The values returned by the invoked method
  Values []Value
}

type Type struct {
  ...
  // Field returns a struct type's i'th field.
  StructTags() []TypedTag
}

type StructField struct {
  ...
  TypedTags []TypedTag
}

Summary

This proposal suggests a new, backwards compatible tagging system for both structs and and struct fields, allowing for type safe annotation.

This would make the tag system considerably more convenient and safe to use, while not requiring a large amount of language changes.

theduke commented 6 years ago

I just found this proposal: https://github.com/golang/go/issues/23637

Which is very similar to this one.

ianlancetaylor commented 6 years ago

@theduke Is this proposal sufficiently different from #23637, or should it be closed as a dup? You can always comment on #23637.

theduke commented 6 years ago

Yeah I think this can be closed.