[go-nuts] cannot use a type parameter as RHS in type declaration

2024-07-02 Thread Diego Augusto Molina
Hi everyone! I wanted to ask about the error in the title: "cannot use a 
type parameter as RHS in type declaration".

My use case: I am implementing range queries on a single dimension, and use 
a common type, e.g. int64. There is a method that I have to implement that 
is Contains(v int64) bool. For space efficiency, if for any reason a range 
of int64 numbers contains a single value, then I make let's say two 
implementations, one for 32 and one for 64 bytes. If the only value in the 
range can be represented with an int32, then I use it. Please, bear in mind 
that this is a simplification of my use case, but it correctly captures my 
concern.

My attempt:

type oneValue[T interface{int32 | int64}] T
func (x oneValue[T]) Contains(v int64) bool { return v == int64(x) }

As that causes the aformentioned error, my current approach is:

type oneValue[T interface{int32 | int64}] [1]T
func (x oneValue[T]) Contains(v int64) bool { return v == int64(x[0]) }

Of course, this can also be expressed as:

type oneValue[T interface{int32 | int64}] struct {
Value T
}
func (x oneValue[T]) Contains(v int64) bool { return v == int64(x.Value) }

I found that the two approaches are functionally equivalent for the most 
part (maybe I'm missing something). Some key points:
1. There doesn't seem to be any struct alignment, space use, or performance 
difference in neither of the three.
2. For generic versions, in general, a custom (un)marshaler may be needed 
(a dumb one  pointing the (un)marshaler is enough). For JSON encoding, for 
example, the first generic version creates a JSON Array with a single 
element; and the second a JSON Object with a single `Value` member.

Considering the three approaches: (A) non-generic; (B) generic with struct; 
(C) generic with an array of one element:
1. Can you explain why this error happens ? what problems it would create 
that was allowed? Any oneliner pointing to docs or just "look for x" is 
great! I may be missing something too obvious.
2. Are there other atlternatives you can think of, even dumb ones, and how 
would they compare to the three above?
3. Are there other differences or concerns you can think of, that were not 
addressed above?

Thank you!

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/c34e29b7-242b-41f1-8ca4-4cca574fec7bn%40googlegroups.com.


[go-nuts] Add string Parser to x/tools/stringer

2020-06-07 Thread Diego Augusto Molina
Very often I use the stringer command to generate lists of enumerated 
values, which is great btw.
But one thing that I see myself also writing after this is a Parse method 
or function to validate strings that are received.
The use case is that I have a list of possible values for a controlled 
field in a RESTful API. I receive and deliver string values from the API by 
I have a tight control inside the API itself.
I declare a new type (generally an uint8 alias) and then declare a bunch of 
auto incremental constants, starting with the zero value that I always set 
to "Invalid" which is used as a control value.
The stringer command makes the dull work but immediately after I write a 
Parse function or method. The following is a full example:
//go:generate stringer -linecomment -output=enum_string.go -type=MyEnum
package enum

type MyEnum uint8

const (
// Reserved for internal use
MyEnumInvalid MyEnum = iota // Invalid

MyEnumValue1 // Value #1
MyEnumValue2 // Value #2
MyEnumValue3 // Value #3
MyEnumValue4 // Value #4
MyEnumValue5 // Value #5
)

func (e *MyEnum) Parse(value string) MyEnum {
for lenIdx, lenVal, i := len(_MyEnum_index), uint8(len(value)), 1; i < 
lenIdx; i++ {
if _MyEnum_index[i]-_MyEnum_index[i-1] == lenVal && // Maybe too 
small optimization to be perceptible/useful(?)
_MyEnum_name[_MyEnum_index[i-1]:_MyEnum_index[i]] == value {
*e = MyEnum(i - 1)
return *e
}
}
return MyEnum(0)
}

So this couples a lot to my workflow, of course. This way I can do 
something like:
receivedVal = new(MyEnum).Parse(receivedStr)
Or:
var val *MyEnum
for _, strVal := range strValues {
if val.Parse(strVal) == MyEnumInvalid {
return fmt.Errorf("Invalid value for enum: %v", strVal)
}
}

So I started to write this function within the stringer command, which I 
modified to do so. So I don't know if this is for some use to the 
community. I know that in order for it to be useful to everyone it should 
be more general. So I'm looking at alternatives and, of course, if it's 
worth working on that. I it would be of some use there are two alternatives 
where this could live:

   - As an option to the stringer command
   - As a separate command (because it would exceed the stringer interface 
   and would be misleading, since "Parse" is not part of the "Stringer" 
   interface)

And I'm only using here the stringer command to create series of 
consecutive numbers, so I would need to write extra code to fulfill the 
sparsiness of values (cases where stringer creates up to 10 different 
slices and after that just creates a map). It wouldn't be much work, 
actually, just read the code in the command.
And instead of writing a new method for the type (breaking the idea of the 
"stringer" interface), I thought it could just be a "ParseT" function.

Thoughts? Comments? Should I keep it for myself (hahaha)?

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/8b1d284c-9874-4e36-8883-a61c8eb218e7o%40googlegroups.com.


[go-nuts] [generics] Typo in draft

2020-06-28 Thread Diego Augusto Molina
Hi, I think I spotted a small typo in the draft. In Examples >
Containers we have:

// Find returns the value associated with a key, or zero if not present.
// The bool result reports whether the key was found.
func (m *Map(K, V)) Find(k K) (V, bool) {
pn := m.find(k)
if *pn == nil {
var zero val // see the discussion of zero values, above
return zero, false
}
return (*pn).v, true
}


Whereas I think it should be:

// Find returns the value associated with a key, or zero if not present.
// The bool result reports whether the key was found.
func (m *Map(K, V)) Find(k K) (V, bool) {
pn := m.find(k)
if *pn == nil {
var zero V // see the discussion of zero values, above
return zero, false
}
return (*pn).v, true
}


Patch attached.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAGOxLdF76jLN0WCtPHP7FL1chVTJ5WJkf9%2BAmhVPgsyhh4W9OA%40mail.gmail.com.


fix-typo.patch
Description: Binary data


[go-nuts] Upgradable RLock

2023-01-29 Thread Diego Augusto Molina
>From times to times I write a scraper or some other tool that would 
authenticate to a service and then use the auth result to do stuff 
concurrently. But when auth expires, I need to synchronize all my 
goroutines and have a single one do the re-auth process, check the status, 
etc. and then arrange for all goroutines to go back to work using the new 
auth result.

To generalize the problem: multiple goroutines read a cached value that 
expires at some point. When it does, they all should block and some I/O 
operation has to be performed by a single goroutine to renew the cached 
value, then unblock all other goroutines and have them use the new value.

I solved this in the past in a number of ways: having a single goroutine 
that handles the cache by asking it for the value through a channel, using 
sync.Cond (which btw every time I decide to use I need to carefully re-read 
its docs and do lots of tests because I never get it right at first). But 
what I came to do lately is to implement an upgradable lock and have every 
goroutine do:

**
func (x implem) getProtectedValue() (someType, error) {
// acquires a read lock that can be upgraded
lock := x.upgradableLock.UpgradableRLock()
// the Unlock method of the returned lock does the right thing
// even if we later upgrade the lock
defer lock.Unlock()

// here we have read access to x.protectedValue

// if the cached value is no longer valid, upgrade the lock
// and update it
if !isValid(x.protectedValue) && lock.TryUpgrade() {
  // within this if, we know we *do* have write access
  // to x.protectedValue
x.protectedValue, x.protectedValueError = newProtectedValue()
}

// here we can only say everyone has read access to x.protectedValue
// (the goroutine that got the lock upgrade could still write
// here but as this code is shared, we should check the result
// of the previous lock.TryUpgrade() again)

return x.protectedValue, x.protectedValueError
}
**

The implementation is quite simple:
**
// Upgradable implements all methods of sync.RWMutex, plus a new
// method to acquire a read lock that can optionally be upgraded
// to a write lock.
type Upgradable struct {
readers sync.RWMutex
writers sync.RWMutex
}

func (m *Upgradable) RLock() { m.readers.RLock() }
func (m *Upgradable) TryRLock() bool { return m.readers.TryRLock() }
func (m *Upgradable) RUnlock() { m.readers.RUnlock() }
func (m *Upgradable) RLocker() sync.Locker { return m.readers.RLocker() }

func (m *Upgradable) Lock() {
m.writers.Lock()
m.readers.Lock()
}

func (m *Upgradable) TryLock() bool {
if m.writers.TryLock() {
if m.readers.TryLock() {
return true
}
m.writers.Unlock()
}
return false
}

func (m *Upgradable) Unlock() {
m.readers.Unlock()
m.writers.Unlock()
}

// UpgradableRLock returns a read lock that can optionally be
// upgraded to a write lock.
func (m *Upgradable) UpgradableRLock() *UpgradableRLock {
m.readers.RLock()
return &UpgradableRLock{
m:  m,
unlockFunc: m.RUnlock,
}
}

// UpgradableRLock is a read lock that can be upgraded to a write
// lock. This is acquired by calling (*Upgradable).
// UpgradableRLock().
type UpgradableRLock struct {
mu sync.Mutex
m  *Upgradable
unlockFunc func()
}

// TryUpgrade will attempt to upgrade the acquired read lock to
// a write lock, and return whether it succeeded. If it didn't
// succeed then it will block until the goroutine that succeeded
// calls Unlock(). After unblocking, the read lock will still be
// valid until calling Unblock().
//
// TryUpgrade panics if called more than once or if called after
// Unlock.
func (u *UpgradableRLock) TryUpgrade() (ok bool) {
u.mu.Lock()
defer u.mu.Unlock()

if u.m == nil {
panic("TryUpgrade can only be called once and not after Unlock")
}

if ok = u.m.writers.TryLock(); ok {
u.m.readers.RUnlock()
u.m.readers.Lock()
u.unlockFunc = u.m.Unlock

} else {
u.m.readers.RUnlock()
u.m.writers.RLock()
u.unlockFunc = u.m.writers.RUnlock
}

u.m = nil

return
}

// Unlock releases the lock, whether it was a read lock or a write
// lock acquired by calling Upgrade.
//
// Unlock panics if called more than once.
func (u *UpgradableRLock) Unlock() {
u.mu.Lock()
defer u.mu.Unlock()

if u.unlockFunc == nil {
panic("Unlock can only be called once")
}

u.unlockFunc()
u.unlockFunc = nil
u.m = nil
}
**

I obviously try to avoid using it for other than protecting values that 
require potentially long I/O operations (like in my case re-authenticating 
to a web service) and also having a lot of interested goroutines. The good 
thing is that most of the time this pattern only requires one RLock, but 
the (*UpgradableRLock).Unlock requires an additional lock/unlock to prevent 
misusage of the upgradable lock (I could potentially get rid of it but 
preferred to keep

[go-nuts] Safely implementing Is(target error) bool

2023-07-31 Thread Diego Augusto Molina
Hello everyone. In relation to standard errors package, I wanted to ask 
about safe ways of implementing Is(target error) bool. The current 
documentation 
(https://cs.opensource.google/go/go/+/refs/tags/go1.20.6:src/errors/wrap.go;l=35)
 
says the following:

// An error type might provide an Is method so it can be treated as 
equivalent 
// to an existing error. For example, if MyError defines 
// 
// func (m MyError) Is(target error) bool { return target == fs.ErrExist }

This seems wrong and actually leads users in the wrong direction, as if 
someone implements an error type like "type MyErrs []error" then it's 
sufficient to make that implementation panic, so the documentation is 
leading users to create panic-prone code. While it's true that interfaces 
are comparable, concrete types implementing them may not, and the error 
will happen at runtime.

Note that the code for the Is function in errors package actually uses 
reflectlite.TypeOf(target).Comparable() 
to avoid these panics, but that as users do not have access to reflectlite 
and, even if they do, the documentation is not warning against these 
possible panics, then it's very easy to make mistakes. Of course, nil 
comparisons are ok, because the comparison is of the value of the 
interface, not the value of the concrete type.

I propose: consider changing the documentation to warn users against this 
possible problem when implementing the Is method of their own error types.

My question to the community: how do you currently go about this issue.

Thank you.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/1fce8a3e-bcd2-41fc-b2e1-02fd70450c09n%40googlegroups.com.


[go-nuts] Re: Safely implementing Is(target error) bool

2023-07-31 Thread Diego Augusto Molina
I apologize, I think the actual issue is with the implementation I found. 
Let me explain what happened  to clarify:


var sentinelErr = someInternalLib.NewError("something happened. The 
implementation of this contains a slice")

type SomeInnocentErr struct {}

func (e SomeInnocentErr) Error() string { return "wrapped: " + 
e.Err.Error() }
func (e SomeInnocentErr) Is(target error) bool { return sentinelErr == 
target }


If the above sentinelErr had been defined with the standard errors.New then 
there had been no problem, or at least instead of doing "return sentinelErr 
== target" it had done something like "errors.Is(target, sentinelErr)".

This is evidently an internal issue, not with the standard library, sorry 
about the fuzz.

On Monday, 31 July 2023 at 17:22:11 UTC-3 Diego Augusto Molina wrote:

> Hello everyone. In relation to standard errors package, I wanted to ask 
> about safe ways of implementing Is(target error) bool. The current 
> documentation (
> https://cs.opensource.google/go/go/+/refs/tags/go1.20.6:src/errors/wrap.go;l=35)
>  
> says the following:
>
> // An error type might provide an Is method so it can be treated as 
> equivalent 
> // to an existing error. For example, if MyError defines 
> // 
> // func (m MyError) Is(target error) bool { return target == fs.ErrExist }
>
> This seems wrong and actually leads users in the wrong direction, as if 
> someone implements an error type like "type MyErrs []error" then it's 
> sufficient to make that implementation panic, so the documentation is 
> leading users to create panic-prone code. While it's true that interfaces 
> are comparable, concrete types implementing them may not, and the error 
> will happen at runtime.
>
> Note that the code for the Is function in errors package actually uses 
> reflectlite.TypeOf(target).Comparable() 
> to avoid these panics, but that as users do not have access to reflectlite 
> and, even if they do, the documentation is not warning against these 
> possible panics, then it's very easy to make mistakes. Of course, nil 
> comparisons are ok, because the comparison is of the value of the 
> interface, not the value of the concrete type.
>
> I propose: consider changing the documentation to warn users against this 
> possible problem when implementing the Is method of their own error types.
>
> My question to the community: how do you currently go about this issue.
>
> Thank you.
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/82a6f529-e6c1-4c45-b49e-363691366466n%40googlegroups.com.


[go-nuts] Generic zero value for compiler optimization

2023-08-14 Thread Diego Augusto Molina
Hi, thank you for reading. Whenever I need to use a zero value for a 
generic type in a func I do something like the following:


func SetZero[T any](pt *T) T {
var zeroT T
*pt = zeroT
}


That's all good, but I wonder how, if possible, it could be proved to the 
compiler that zeroT is the zero value for T. That would be to enable memclr 
optimization when bulk setting slice or array values to the zero value of 
their element type. Currently, as of 1.21, this only works when the src is 
a constant holding the zero value of the type. I also tried with something 
like *new(T), and it doesn't work either. But proving that the expression 
*new(T) in the src is the zero value for the type could potentially be 
easier than checking back if a certain variable (e.g. zeroT in the example) 
hasn't yet been reassigned or initialized to a non-zero value.
Also, as there's no guarantee of what would T could hold, it could use 
memclrHasPointers if that makes sense, which seems like a fare tradeoff at 
least for now if we want to play with slices with generic element type.
For reference, this is the code I'm trying:


package main
// file generic_slice_element_type_memclr.go

func clear[T any](s []T) {
for i := range s {
s[i] = *new(T)
}
}

func main() {
clear([]int{1, 2, 3})
}


And I'm compiling it with:


$ go version
go version go1.21.0 darwin/amd64
$ go tool compile -S generic_slice_element_type_memclr.go
...


Kind regards.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/b8ec1335-911c-42ed-96ce-a4b50153b8c9n%40googlegroups.com.


Re: [go-nuts] Generic zero value for compiler optimization

2023-08-14 Thread Diego Augusto Molina
Thank you very much, that's actually what I was looking for.

On Monday, 14 August 2023 at 13:57:35 UTC-3 Axel Wagner wrote:

> You might be interested in https://github.com/golang/go/issues/61372
>
> On Mon, Aug 14, 2023 at 3:52 PM Diego Augusto Molina <
> diegoaugu...@gmail.com> wrote:
>
>> Hi, thank you for reading. Whenever I need to use a zero value for a 
>> generic type in a func I do something like the following:
>>
>> 
>> func SetZero[T any](pt *T) T {
>> var zeroT T
>> *pt = zeroT
>> }
>> 
>>
>> That's all good, but I wonder how, if possible, it could be proved to the 
>> compiler that zeroT is the zero value for T. That would be to enable 
>> memclr optimization when bulk setting slice or array values to the zero 
>> value of their element type. Currently, as of 1.21, this only works when 
>> the src is a constant holding the zero value of the type. I also tried with 
>> something like *new(T), and it doesn't work either. But proving that the 
>> expression *new(T) in the src is the zero value for the type could 
>> potentially be easier than checking back if a certain variable (e.g. 
>> zeroT in the example) hasn't yet been reassigned or initialized to a 
>> non-zero value.
>> Also, as there's no guarantee of what would T could hold, it could use 
>> memclrHasPointers if that makes sense, which seems like a fare tradeoff 
>> at least for now if we want to play with slices with generic element type.
>> For reference, this is the code I'm trying:
>>
>> 
>> package main
>> // file generic_slice_element_type_memclr.go
>>
>> func clear[T any](s []T) {
>> for i := range s {
>> s[i] = *new(T)
>> }
>> }
>>
>> func main() {
>> clear([]int{1, 2, 3})
>> }
>> 
>>
>> And I'm compiling it with:
>>
>> 
>> $ go version
>> go version go1.21.0 darwin/amd64
>> $ go tool compile -S generic_slice_element_type_memclr.go
>> ...
>> 
>>
>> Kind regards.
>>
>> -- 
>> You received this message because you are subscribed to the Google Groups 
>> "golang-nuts" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to golang-nuts...@googlegroups.com.
>> To view this discussion on the web visit 
>> https://groups.google.com/d/msgid/golang-nuts/b8ec1335-911c-42ed-96ce-a4b50153b8c9n%40googlegroups.com
>>  
>> <https://groups.google.com/d/msgid/golang-nuts/b8ec1335-911c-42ed-96ce-a4b50153b8c9n%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/65671596-e4ba-488a-946c-d00f9aef3f20n%40googlegroups.com.


[go-nuts] slog.GroupValue could potentially not make a new slice

2023-08-14 Thread Diego Augusto Molina
Hello everyone, thank you for reading. I'm looking at the code of 
slog.GroupValue 
(https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/log/slog/value.go;l=171)
 
and was wondering if we could benefit from reusing the same slice like this:


// GroupValue returns a new Value for a list of Attrs. 
// The caller must not subsequently mutate the argument slice. 
func GroupValue(as ...Attr) Value { 
// Remove empty groups.
// It is simpler overall to do this at construction than
// to check each Group recursively for emptiness.
var write int
for read := range as {
if as[read].isEmptyGroup() {
as[read] = Attr{} // no need to keep a reference to the string 
Key
} else {
if read != write {
as[write] = as[read]
}
write++
}
}
as = as[:write]
return Value{num: uint64(len(as)), any: groupptr(unsage.SliceData(as))}
}


This, considering that the documentation of the func already states that 
the user must not mutate the argument slice, so not only the elements but 
the whole slice could be reused.
In the case of having lots of attributes with empty group elements, then 
the stored slice will be unnecessarily larger by that number of Attr 
elements, but I wonder if that would be negligible, probably an edge case 
of misuse.

Kind regards.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/791c5981-95ea-44e4-a6f9-38cd65842ae4n%40googlegroups.com.


[go-nuts] Re: slog.GroupValue could potentially not make a new slice

2023-08-14 Thread Diego Augusto Molina
Sorry, just thought it could also use clear to start getting accostumed :)


// GroupValue returns a new Value for a list of Attrs. 
// The caller must not subsequently mutate the argument slice. 
func GroupValue(as ...Attr) Value { 
// Remove empty groups.
// It is simpler overall to do this at construction than
// to check each Group recursively for emptiness.
var write int
for read := range as {
if !as[read].isEmptyGroup() {
if read != write {
as[write] = as[read]
}
write++
}
}
clear(as[write:]) // no need to keep a reference to the string Key
as = as[:write]
return Value{num: uint64(len(as)), any: groupptr(unsage.SliceData(as))}
}

On Monday, 14 August 2023 at 22:06:57 UTC-3 Diego Augusto Molina wrote:

> Hello everyone, thank you for reading. I'm looking at the code of 
> slog.GroupValue (
> https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/log/slog/value.go;l=171)
>  
> and was wondering if we could benefit from reusing the same slice like this:
>
> 
> // GroupValue returns a new Value for a list of Attrs. 
> // The caller must not subsequently mutate the argument slice. 
> func GroupValue(as ...Attr) Value { 
> // Remove empty groups.
> // It is simpler overall to do this at construction than
> // to check each Group recursively for emptiness.
> var write int
> for read := range as {
> if as[read].isEmptyGroup() {
> as[read] = Attr{} // no need to keep a reference to the 
> string Key
> } else {
> if read != write {
> as[write] = as[read]
> }
> write++
> }
> }
> as = as[:write]
> return Value{num: uint64(len(as)), any: groupptr(unsage.SliceData(as))}
> }
> 
>
> This, considering that the documentation of the func already states that 
> the user must not mutate the argument slice, so not only the elements but 
> the whole slice could be reused.
> In the case of having lots of attributes with empty group elements, then 
> the stored slice will be unnecessarily larger by that number of Attr 
> elements, but I wonder if that would be negligible, probably an edge case 
> of misuse.
>
> Kind regards.
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/71b83821-b6e2-4ed0-9d36-a97ed1dba290n%40googlegroups.com.