Skip to content

proposal: Go 2: introduce "grant" keyword to set access to variables and functions #31632

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

Closed
StephanVerbeeck opened this issue Apr 23, 2019 · 13 comments
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@StephanVerbeeck
Copy link

StephanVerbeeck commented Apr 23, 2019

About: read-only, private, public, protected, const

Proposal: introduce Grant keyword

This would fixes all these issues #27975 #20443 #22876 #22048 without introducing compatibility issues of any kind. These issues are currently all open and requiring decision that alter the behavior of existing code while my proposal solves all these issues without any impact on existing code.

Introduction to "grant".

the keyword "grant" comes from the SQL language where it is used to assign privilege to use a particular item in a particular way to a particular user or group of users.

In established programming languages (C++, C#, JAVA) this functionality is accomplished by using keywords like:

  • private
  • public
  • const
  • protected
  • friend

However these keywords introduce complexity that we do NOT WANT in GO.

So my proposal is to introduce an optional keyword that does the same but BETTER, FASTER, STRICTER and more ACCURATE and more READABLE.

The new grant keyword is only used during compile time and does not generate any run-time code.

In SQL we have privileges like INSERT, UPDATE, DELETE, CREATE, DROP, ...
In GO we have privileges GET, SET, USE

In SQL we have users and groups of users (because SQL database requires login-to-database with user credentials) so grants are made to a user.
In GO we do not know who the programmer or user is so the grant is made BETWEEN items (where an item is a type or method or variable or package)

Because "grant" is optional the default is that all privileges are granted between all items (not changing the local/public flag set by a name starting with lower-case or upper-case rune).

The effect of the grant keyword is restricted to the package in which it occurs.
Multiple grants (e.g. in different sources about the same item) are cumulative.
There is no "revoke" (like in SQL) that could undo a grant. To revoke a right you simply change the source code line that does the Grant.

Examples

situation 1: I have a structure with a counter that everybody may read but only a single function should be allowed to increment or decrement.

grant set (
	Data.counter Increment Decrement // write access to this variable limited to these 2 methods
)

grant get (
	Data.counter // read access to everybody (because nobody specified so that means everybody)
)

grant (
	Data // all actions allowed by everybody (this statement is not needed because it is the default)
)
	
type Data struct {
	a,b,c string
	counter int
}

func (data *Data) Increment() {
	data.counter++
}

func (data *Data) Decrement() {
	data.counter--
}

situation 2: I want to limit all access to function Aaa() to only function Bbbb() (undoing the default grant all to all)

grant use (
	Aaaa Bbbb // Only Bbbb() may call Aaaa()
)

situation 3: I have a variable that needs initialization but only the function that initializes it may write to it (everywhere else all members of the struct are immutable)

grant set a,b,c MyInitFunction

var (
	a,b,c int
)

func MyInitFunction(){
	a = 1 // this compiles
	b = 2
	c = 3
}

func MyOtherFunction(){
	a = 1 // this does not compile, generates error "set 'a' not granted for 'MyOtherFunction'"
	b = 2
	c = 3
}

situation 4: I have dot-imported a package but want to restrict the use of this "type wildcard" to a few functions

package main

import (
	"github.com/lxn/walk"
	. "github.com/lxn/walk/declarative"
)

grant (
	declarative RunProgramDialog
)

func RunProgramDialog(program *Program) (int, error) {

	var (
		dataBinder *walk.DataBinder
		dlg        *walk.Dialog
		acceptPB   *walk.PushButton
		cancelPB   *walk.PushButton
		fileName   *walk.ComboBox
	)
	return Dialog{
		... // to much code lines to include in this example
	}.Run(mainWindow)
}
@gopherbot gopherbot added this to the Proposal milestone Apr 23, 2019
@StephanVerbeeck
Copy link
Author

StephanVerbeeck commented Apr 23, 2019

The grant keyword can PRECEDE the definition of the items (both who gets the rights and to what) but granted items that are not declared at the end of the same source code should be seen and faulty grant statements.

an idea (but this might be harder to implement)

  • The proposed grant keyword can occur inside functions but this is only syntactical in that it adds the name of the method in which it occurs to the name of the items to which the access is restricted.
  • The proposed grant keyword can occur inside struct {...} but this is only syntactical in that it adds the name of the struct in which it occurs to the name of the items to which the access is restricted.

@gertcuykens
Copy link
Contributor

gertcuykens commented Apr 23, 2019

I am not the person qualified to know if this is a good idea or not. But once we would go this route we can't go back if it turns out to be to verbose or more chore for other users to work on other projects. One of the strong points of Go is it allows you to quickly poke around a code base. Somehow I believe this proposal sounds good on paper but reality will shoot us in the foot because it prevent us to quickly print or modify some internals for debugging we are not suppose to have access on. At the moment I think Go has a few more important problems like generics that we need to iron out first before considering this.

@StephanVerbeeck
Copy link
Author

StephanVerbeeck commented Apr 23, 2019

Somehow I believe this sounds good on paper but reality will shoot us in the foot because it prevent us to quickly print or modify some internals for debugging we are not suppose to have access on.

The grant statement was part of a proprietary RAD scripting language called Formula for several years.
So the test-faze has already been concluded. If you compare the grant statement to how C++ handles private/protected/public/friend then you have to conclude that it is not "intrusive" because you only define limits to what needs to be limited. While all existing programming languages have implemented it wrong (in that you always have to define it for every variable and function).

At the moment I think Go has a few more important problems like generics that we need to iron out first before considering this.

Agreed but postponing this due to low priority tells me that you are not grasping the current lack of functionality. GOlang is steadily evolving from a RAD to full-fledged all purpose commercial application language. While you have now the luxure to play around with these features, that luxure will be GONE in a few years from now! Once that everybody started deploying commercial applications you will not be so keen as to change anything at all (you'll see).

The grant statement is also not adding something that is not needed because it does not only tell the compiler what the restrictions are but it also works as DOCUMENTATION in telling the reader of the program source what the intended usage of the data structure or function or package is.
And does it in a more functional way then adding a comment line "you should not do this ...."

@gertcuykens
Copy link
Contributor

But isn't it going to be confusing if you use capital letters to export it and then use grant to not allow it usage? So for me it sounds that for grant to be consistent then the current capital export rules need to go out of the language.

@StephanVerbeeck
Copy link
Author

But isn't it going to be confusing if you use capital letters to export it and then use grant to not allow it usage? So for me it sounds that for grant to be consistent then the current capital export rules need to go out of the language.

How is that?
you would mostly use this to restrict your own local functions.
Your public functions are not restricted in other packages as whatever grant you write in your package only works in your package!
So one is not an alternative for the other although I do see the reason why you assumed it would.
If somebody has some example code to which I could add the related grant statement that would make it more insightful ( I can not copy my code here as it is commercial ).

@bradfitz bradfitz added the LanguageChange Suggested changes to the Go language label Apr 23, 2019
@rsc rsc added the v2 An incompatible library change label Apr 24, 2019
@dpinela
Copy link
Contributor

dpinela commented Apr 24, 2019

If it can't be used on function parameters, this proposal doesn't solve one of the most important issues that immutability or read-only types fix, which is guaranteeing at compile-time that a function does not modify the contents of a map or slice it's passed (or, with immutability, that they're also not changed under its feet by another goroutine). And if it can, how would it work syntactically? Presumably it would have to go inside the function body, since the parameters are only in scope there. But then you can't see if a parameter is read-only or immutable by just looking at a signature (in godoc for example).

Also, since a grant only affects the package it's declared in, you can't use one to export an immutable map or slice, which is another common use case for read-only types.

@ianlancetaylor ianlancetaylor changed the title Proposal: introduce "Grant" keyword to fix issues #27975 #20443 #22876 #22048 proposal: Go 2: introduce "grant" keyword to set access to variables and functions Apr 24, 2019
@StephanVerbeeck
Copy link
Author

StephanVerbeeck commented Apr 25, 2019

type Point struct {
	x,y float32
	grant get x,y
	grant set x,y NewPoint
}

func MyExampleFunction(a,b,c string, at Point) string {
	grant get a
	...
}

func NewPoint(x,y float32) Point {
	return Point{x:x, y:y}	
}

If it can't be used on function parameters, this proposal doesn't solve one of the most important issues that immutability or read-only types fix, which is guaranteeing at compile-time that a function does not modify the contents of a map or slice it's passed (or, with immutability, that they're also not changed under its feet by another goroutine). And if it can, how would it work syntactically? Presumably it would have to go inside the function body, since the parameters are only in scope there. But then you can't see if a parameter is read-only or immutable by just looking at a signature (in godoc for example).

Correct, I did not originally opt for restricting access to function arguments as it is not clear WHO you give the rights to. However, considering your argument, I conclude that indeed this is not an issue as the WHO is in this case always the function itself. But there is no use case for this as function arguments are either already a local copy (e.g. int,string,bool) or a data structure (in which case you should have done the grant behind the "type MyType struct {...}"

Also, since a grant only affects the package it's declared in, you can't use one to export an immutable map or slice, which is another common use case for read-only types.

Correct, it is not an alternative for #22048 because I did not take into account what happens if you make a map or list with restricted access public. However making a data item public makes it public (point final, I would not change that behavior).

@dpinela
Copy link
Contributor

dpinela commented Apr 25, 2019

But there is no use case for this as function arguments are either already a local copy (e.g. int,string,bool) or a data structure (in which case you should have done the grant behind the "type MyType struct {...}"

There might be no such type declaration. Consider a typical Write method: func (...) Write(p []byte) (int, error) { ... }. How would you declare p as a read-only slice without introducing an extra type declaration for p? Introducing such a type would be too verbose; not only at the declaration site, but also because every call site would require an explicit conversion to your new type. And it would have to be a different type for every single package that has a Write implementation in it, since the grant to make it read-only affects only that package.

Which, in turn, would make it impossible to create a version of io.Writer that takes a read-only slice, because the implementations could never use the same parameter type as the interface, or even each other.

@beoran
Copy link

beoran commented Apr 26, 2019

This reminds me of "programming by contract" as available in ADA . I think that in stead of having a specific grant feature, programming by contract would be more generally useful to make Go more suitable for life critical software. No idea about the syntax, though.

@StephanVerbeeck
Copy link
Author

How would you declare p as a read-only slice without introducing an extra type declaration for p? Introducing such a type would be too verbose; not only at the declaration site, but also because every call site would require an explicit conversion to your new type. And it would have to be a different type for every single package that has a Write implementation in it, since the grant to make it read-only affects only that package.

Sorry but there is no correlation to the use case you describe and my GRANT proposal.
Grant does not introduce new types ( I do not follow what conversions you had in mind ).
It only generates compilation errors when you use a method/type/package in a manner that was not intended and that only local in your own package (it can not affect anybody else who uses your package).

@StephanVerbeeck
Copy link
Author

This reminds me of "programming by contract" as available in [ADA]
Grant is the opposite of "interface" in an interface you describe by contract what functionality MUST be implemented while with "grant" you describe by contract HOW that functionality must be used.
Beware that GOlang does not support GET and SET functions (think of how this works in JAVA/C#/Kotlin/...) !!!
However there is a convention that you name your get and set functions e.g.:

  • func (point *Point) X() int { return X }
  • func (point *Point) SetX(x int) { point.x = x }

However there is NOTHING that prevents a programmer from directly doing

  • point.x = 10
  • a:= point.x

except when you could do:

  • grant set Point.x Point.SetX
  • grant get Point.x Point.X

I admit that it adds more labor but that is also why it is optional and you would only grant access to variables of which wrong usage would cause havoc (e.g. increment/decrement counters via functions with locking)

@QiuYulong
Copy link

by my opinion, it looks too complex and not match with the benefits it can bring. if changing a variable is a must, and it is not granted with access, then I will choose to change the grant definition, instead of follow the grant restriction. developers in dedicated project are smart enough to know how and when to access things, so no need to put a stone there.

@ianlancetaylor
Copy link
Contributor

One of the main features that people want from this kind of access controls is to be able to say "this function does not modify any memory using this pointer." This proposal doesn't seem to address that use case.

In general access to a package scope variable can be controlled by making it unexported and providing exported accessor functions, or, perhaps, use an internal package. It's sometimes useful to be able to restrict access to variables across packages. It's rarely useful to restrict access within a single package. If package gets so large that access is unclear, it should likely be broken up anyhow.

As you say grant is a mechanism from the database world, where everything is global. In languages like Go you can remove access by not exporting.

@golang golang locked and limited conversation to collaborators May 13, 2020
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

9 participants