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

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

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

theduke opened this issue Sep 10, 2018 · 3 comments
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@theduke
Copy link

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
@theduke theduke changed the title Proposal: Add typed struct tags Proposal: Add new type-safe tag system Sep 10, 2018
@theduke theduke changed the title Proposal: Add new type-safe tag system Proposal: Add a new type-safe tag system Sep 10, 2018
@theduke
Copy link
Author

theduke commented Sep 10, 2018

I just found this proposal: #23637

Which is very similar to this one.

@mvdan mvdan added LanguageChange Suggested changes to the Go language v2 An incompatible library change labels Sep 11, 2018
@ianlancetaylor
Copy link
Member

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

@theduke
Copy link
Author

theduke commented Sep 12, 2018

Yeah I think this can be closed.

@theduke theduke closed this as completed Sep 12, 2018
@golang golang locked and limited conversation to collaborators Sep 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

4 participants