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: Add a new type-safe tag system #27597

Closed
theduke opened this Issue Sep 10, 2018 · 3 comments

Comments

Projects
None yet
4 participants
@theduke

theduke commented Sep 10, 2018

EDIT: this is very similar to #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:

  • They are just strings and can easily be mistyped and are therefore brittle and often a source for bugs.
  • They are completely outside the realm of compiler checks. The only way to verify them would be to use a some custom linter.
  • They are arguably really misused for all kinds of things like JSON customization, validation, DB ORM setup, etc.
  • Tags can only be applied to struct members, not to a full struct or other language items like functions.

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:

  • be intuitive and simple
  • be type safe and allow for compile time checking
  • introduce the minimum amount of new concepts
  • not require any large compiler changes

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:

  • Tags are just regular functions which take and return arbitrary values
  • Tags are applied to structs and struct fields with the @pkg.Fn(...) notation
  • Tag function invocations are checked at compile time just like regular invocations at compile time, before the init() function
  • Presence of a tag and the returned values are available via the reflect package (see implementation concept in next section)

Implementation

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

  • The @ symbol is currently forbidden as a hard compile time error, so it can be used for this purpose

  • Tags are just regular functions and function invocations, so apart from the lexer/parser/AST changes this should be relatively straight-forward to implement. The is no complicated logic necessary.

  • Tag functions must be executed on package initialization and the values stored in the static type information available to the reflection system

  • The reflect API must be extended to allow access to the tag information.

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.

@gopherbot gopherbot added this to the Proposal milestone Sep 10, 2018

@gopherbot gopherbot added the Proposal label Sep 10, 2018

@theduke theduke changed the title from Proposal: Add typed struct tags to Proposal: Add new type-safe tag system Sep 10, 2018

@theduke theduke changed the title from Proposal: Add new type-safe tag system to Proposal: Add a new type-safe tag system Sep 10, 2018

@theduke

This comment has been minimized.

Show comment
Hide comment
@theduke

theduke Sep 10, 2018

I just found this proposal: #23637

Which is very similar to this one.

theduke commented Sep 10, 2018

I just found this proposal: #23637

Which is very similar to this one.

@ianlancetaylor

This comment has been minimized.

Show comment
Hide comment
@ianlancetaylor

ianlancetaylor Sep 12, 2018

Contributor

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

Contributor

ianlancetaylor commented Sep 12, 2018

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

@theduke

This comment has been minimized.

Show comment
Hide comment
@theduke

theduke Sep 12, 2018

Yeah I think this can be closed.

theduke commented Sep 12, 2018

Yeah I think this can be closed.

@theduke theduke closed this Sep 12, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment