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] GUID Literal #27

Open
AdamSpeight2008 opened this issue Feb 22, 2017 · 40 comments
Open

[Proposal] GUID Literal #27

AdamSpeight2008 opened this issue Feb 22, 2017 · 40 comments

Comments

@AdamSpeight2008
Copy link
Contributor

(Ported from Roslyn Repo)

When guid are used their value tends not to change, throughout it's usage.

If we make it a compile-time constant,

  • It can be used in attributes.
  • Removes the runtime parse of the GUID string.
  • Compile-Time verification of the formatting.
  • If the GUID is a well known one, we could offer suggestions / completion like intellisense.

Guid.Parse currently supports 5 different formats.

Specifier Description Format
N 32 digits 00000000000000000000000000000000
D 32 digits seperated by hypens 00000000-0000-0000-0000-0000000000000
B 32 digits seperated by hypens, enclosed in braces {00000000-0000-0000-0000-0000000000000}
P 32 digits seperated by hypens, enclosed in parentheses (00000000-0000-0000-0000-0000000000000)
X Four hexadecimal values enclosed in braces, where the fourth value is a subset of eight hexadecimal values that is also enclosed in braces {0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}

These format should also be supported by any GUID literal.


Proposed Literal Syntax (VB)

We could extend VB's date time literal syntax #2016/06/01# to cover GUIDs.

Dim guid0 As Guid = #00000000000000000000000000000000# ' Specifier N
Dim guid1 As Guid = #00000000-0000-0000-0000-0000000000000# ' Specifier D
Dim guid2 As Guid = #{00000000-0000-0000-0000-0000000000000}# ' Specifier B
Dim guid3 As Guid = #(00000000-0000-0000-0000-0000000000000)# ' Specified P
Dim guid4 As Guid = #{0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}#

In an Attribute

Imports System
Imports System.Runtime.InteropServices


 <GuidAttribute(#9ED54F84-A89D-4fcd-A854-44251E925F09#)> _
 Public Class SampleClass
     ' Insert class members here.
 End Class

@craigajohnson
Copy link

Great proposal. This would eliminate the need to have a string attribute when you really want a GUID.

@Bill-McC
Copy link

yep, like.

@scottdorman
Copy link
Contributor

We could extend VB's date time literal syntax #2016/06/01# to cover GUIDs.

Would that really be the best syntax to use? I'd think using another character other than # would be better.

I can write a date literal either as #2016/06/01# or #2016-06-01#. If we use the # characters to enclose a GUID literal, how am I (and the compiler) to tell that #2016-06-01# should be parsed as a date and that it's not an improperly formed GUID?

@AnthonyDGreen
Copy link
Contributor

Certainly we could do this but why are GUID generally literals useful? If it's just validation of magic strings we can do that with analyzers or compiler diagnostics.

@AdamSpeight2008
Copy link
Contributor Author

@AnthonyDGreen We maybe able to make the compile-time constants.

@AdamSpeight2008
Copy link
Contributor Author

AdamSpeight2008 commented Sep 8, 2017

@scottdorman I'd forgotten about this proposal.
How about using square braces? [ ... ]

 <GuidAttribute([9ED54F84-A89D-4fcd-A854-44251E925F09])>
class Foo
  const aGuid = [9ED54F84-A89D-4fcd-A854-44251E925F09]
end class

@AnthonyDGreen is it possible to directly support Guid literal -> GuidAttribute?
eg

[9ED54F84-A89D-4fcd-A854-44251E925F09]
class Foo

@AnthonyDGreen
Copy link
Contributor

AnthonyDGreen commented Sep 8, 2017

We can definitely do this and I don't think it would be expensive at all. What I really want to know is that such a feature would be frequently useful.

Inspired by &H, &O, &B integral literals:

&G{05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5}
&G05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5
&G05A47EBB8BF04CBFAD2F3B71D75866F5

A little more explicit:

Guid{05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5}
Guid{05A47EBB8BF04CBFAD2F3B71D75866F5}

We'd want to use a prefix, rather than a suffix like (S, L, I in integers) so that tooling could switch into guid-mode as you're typing (or more likely pasting) instead of trying to first parse it as a large subtract expression and then seeing the G suffix at the end. I never see the P and X formats in the wild so I'd rather not worry about them for now.

We could avoid the typing issues with a suffix by just making it a validated string:

"{05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5}"G
"05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5"G
"05A47EBB8BF04CBFAD2F3B71D75866F5"G
"(05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5)"G

The compiler would make sure the format was right but still treat it as a string. Maybe we could support target-type conversions to System.Guid though.

The implementation would probably very closely follow what we do for System.Decimal, where there's a literal syntax that translates into a constructor call. Should probably use this one.

@AnthonyDGreen
Copy link
Contributor

@AdamSpeight2008 GuidAttribute would need a new constructor that takes a GUID or we'd need to special case it and basically convert the GUID back to a string when applied to an attribute.

@KevinRansom
Copy link
Member

@anthony ... not really, the compiler serializes the constructor blob anyway.

@AdamSpeight2008
Copy link
Contributor Author

&G{05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5} is very close to what the .ToString would produce.

@AdamSpeight2008
Copy link
Contributor Author

It may need a new attribute. <ValueGuid(guid As Guid)>

<AttributeUsage(AttributeTargets.Assembly + AttributeTargets.Interface + AttributeTargets.Class
                AttributeTargets.Enum + AttributeTargets.Struct + AttributeTargets.Delegate,
                Inherited = false),
System.Runtime.InteropServices.ComVisible(true)]
 public noninheritable class GuidAttribute : Attribute

   public readonly Value As Guid

   public sub New GuidAttribute(guid As Guid)
     me.Value = guid;
   end sub

 end class

@AdamSpeight2008
Copy link
Contributor Author

AdamSpeight2008 commented Sep 8, 2017

Would be nice to have a prototype to put up on something like sharplab so we could try it out a see hey / nay.

@AnthonyDGreen &G{6.....} could be easily be mistaken as 66. Tilde ~ is very lightweight visually.
~{05A47EBB-8BF0-4CBF-AD2F-3B71D75866F5}

Potential having a GuidLiteralSyntaxNode would help creating analysers for Guids easier to write.

  • Generate New Guid
  • NoneUniqueGuidException

A quick survey of the .net framework source

@AdamSpeight2008
Copy link
Contributor Author

Very early stages of a Prototype
Example syntax may change
Dim g1 As Guid = ~"936DA01F-9ABD-4d9d-80C7-02AF85C822A8"

@KevinRansom
Copy link
Member

@AdamSpeight2008 why the quotes?

Surely it would be:

Dim g1 As Guid = ~936DA01F-9ABD-4d9d-80C7-02AF85C822A8

Although, hailing from the pre VB days of &H I would probably vote for:

Dim g1 As Guid = &G936DA01F-9ABD-4d9d-80C7-02AF85C822A8

and probably allow:

Dim g1 = &G936DA01F-9ABD-4d9d-80C7-02AF85C822A8
Dim g2 = &G{936DA01F-9ABD-4d9d-80C7-02AF85C822A8}
Dim g3 = &G936DA01F9ABD4d9d80C702AF85C822A8

@terrajobst
Immo ... We should add a guid constructor to the Guid attribute. Then we can add Guid literals to the compiler and have them be meaningful.

Kevin

@AdamSpeight2008
Copy link
Contributor Author

AdamSpeight2008 commented Sep 13, 2017

@KevinRansom At the moment I'm treating it as a proof of concept.
We can refine the syntax later, as it'll be likely not efficient code.
Personally like the minimal syntax noise of :-

  • ~936DA01F-9ABD-4d9d-80C7-02AF85C822A8
  • ~{936DA01F-9ABD-4d9d-80C7-02AF85C822A8}

@terrajobst It really needs a Value As Guid property, so it isn't reparsed again.

@KevinRansom
Copy link
Member

@AdamSpeight2008.

I will yield to you and @AnthonyDGreen on syntax, but be aware .... I will be in his office pestering for &G blah because I am old and stuck in my ways..

Kevin

@AdamSpeight2008
Copy link
Contributor Author

I think other languages use a similar syntax for guids, so it should be easy to copy and paste in from text.

@reduckted
Copy link
Contributor

GuidAttribute would need a new constructor that takes a GUID
We should add a guid constructor to the Guid attribute.

Unless I'm mistaken, wouldn't that be a violation of the language specification (.NET, VB and C#)?

Common Type System & Common Language Specification:

The constructor or the properties of a CLS-compliant attribute can expose only the following types:

  • Boolean, Byte, Char, Double, Int16, Int32, Int64, Single, String, Type
  • Any enumeration type whose underlying type is Byte, Int16, Int32, or Int64.

Visual Basic Language Specification:

The types that can be used in positional parameters and public instance variables and properties are restricted to attribute types. A type is an attribute type if it is one of the following:

  • Any primitive type except for Date and Decimal.
  • The type Object.
  • The type System.Type.
  • An enumerated type, provided that it and the types in which it is nested (if any) have Public accessibility.
  • A one-dimensional array of one of the previous types in this list.

C# 6.0 draft specification:

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Attribute specification).
  • Single-dimensional arrays of the above types.

Adding the ability to specify a GUID in an attribute would require more than just changes to VB.


Perhaps for a more lightweight change, we could:

  1. Allow a literal string to be assigned to a GUID
  2. At compile-time, validate that the string is a valid GUID.
  3. Convert the literal string into an actual Guid.

For example, this:

Dim g As Guid = "{2C151561-BA1B-462A-AD04-ACB8E53D9F98}"

Would be compiled to:

Dim g As Guid = New Guid(&H2C151561, &HBA1BS, &H462AS, {&HAD, &H4, &HAC, &HB8, &HE5, &H3D, &H9F, &H98})

You'd lose the ability to use type inferencing, but I can't think of any cases where you'd create a Guid in a local scope. In the places where you'd commonly use Guids, type inferencing wouldn't be used:

  • Passing as a parameter ✔️
  • Creating a shared field (without an As clause, the field is declared as an `Object) ✔️

@AnthonyDGreen
Copy link
Contributor

I don't think there's any value to be had modifying the GuidAttribute type because all code will continue to consume the data from that attribute as a string. To do otherwise would limit that code to only the bleeding edge of .NET Framework versions which no one would do.

@AdamSpeight2008
Copy link
Contributor Author

@AnthonyDGreen I'm considering the following structures for guid literals, they should be able to handle all of the Guid formats.

They definitely need refining.

    <!--********************
      -  Guid Literal Tokens
      **********************-->

    <enumeration name="Guid_Format">
      <description>Represents one of allowed Guid Formats.</description>
      <lm-equiv name="GuidFormat"></lm-equiv>
      <native-equiv name="guidFormat"></native-equiv>
      <native-equiv name="GuidFormat.Opcodes"></native-equiv>
      <enumerators>
        <enumerator name="None" />
        <enumerator name="B">
          <description>B  {00000000-0000-0000-0000-000000000000}</description>
        </enumerator>
        <enumerator name="D">
          <description>D  00000000-0000-0000-0000-000000000000</description>
        </enumerator>
        <enumerator name="N">
          <description>N  00000000000000000000000000000000</description>
        </enumerator>
        <enumerator name="P">
          <description>P  (00000000-0000-0000-0000-000000000000)</description>
        </enumerator>
        <enumerator name="X">
          <description>X  {0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}</description>
        </enumerator>
      </enumerators>
    </enumeration>

    <enumeration name="Guid_Seperator">
      <description>Represents one of allowed Guid Seperators.</description>
      <lm-equiv name="Guid_Seperator"></lm-equiv>
      <native-equiv name="guid_Seperator"></native-equiv>
      <native-equiv name="Guid_Seperator.Opcodes"></native-equiv>
      <enumerators>
        <enumerator name="None"/>
        <enumerator name="Hypen">
          <description>-</description>
        </enumerator>
        <enumerator name="Comma">
          <description>,</description>
        </enumerator>
      </enumerators>
    </enumeration>

    <enumeration name="Guid_Opening">
      <description>Represents one of allowed Guid Openings.</description>
      <lm-equiv name="Guid_Seperator"></lm-equiv>
      <native-equiv name="guid_Opening"></native-equiv>
      <native-equiv name="Guid_Opening.Opcodes"></native-equiv>
      <enumerators>
        <enumerator name="None"/>
        <enumerator name="Bo">
          <description>{</description>
        </enumerator>
        <enumerator name="Po">
          <description>(</description>
        </enumerator>
      </enumerators>
    </enumeration>

    <enumeration name="Guid_Closing">
      <description>Represents one of allowed Guid Closing.</description>
      <lm-equiv name="Guid_Seperator"></lm-equiv>
      <native-equiv name="guid_Closing"></native-equiv>
      <native-equiv name="Guid_Closing.Opcodes"></native-equiv>
      <enumerators>
        <enumerator name="None"/>
        <enumerator name="Bc">
          <description>}</description>
        </enumerator>
        <enumerator name="Pc">
          <description>)</description>
        </enumerator>
      </enumerators>
    </enumeration>

    <node-structure name="Guid_PrefixSyntax" parent ="SyntaxToken">
      <description>Represents the Guid literal prefix. (ie ~)</description>
      <node-kind name="Guid_Prefix"/>
    </node-structure>

    <node-structure name="Guid_LiteralSyntax" parent="VisualBasicSyntaxNode">
      <description>Represents the Guid literal. (ie ~)</description>
      <node-kind name="Guid_Literal" />
      <child name="Prefix" kind="Guid_Prefix" />
      <child name="Guid"   kind="Guid_Section" />
    </node-structure>

    <node-structure name="Guid_SectionSyntax" parent="VisualBasicSyntaxNode">
      <description>Represents a guid section, consisting of format and optional block.</description>
      <node-kind name="Guid_Section" />
      <field name="Format" type="Guid_Format" />
      <child name="Block"  kind="Guid_Block"  optional="true"/>
    </node-structure>

    <node-structure name="Guid_BlockSyntax" parent="VisualBasicSyntaxNode">
      <description>Represents a guid block.</description>
      <node-kind name="Guid_Block"/>
      <field name="Opening"  type="Guid_Opening" optional="true"/>
      <child name="Contents" kind="Guid_BlockContent" list="true"/>
      <field name="Closing"  type="Guid_Closing" optional="true"/>
    </node-structure>

    <node-structure name="Guid_BlockContentSyntax" parent="VisualBasicSyntaxNode">
      <description>Represents a guid block content.</description>
      <node-kind name="Guid_BlockContent"/>
      <child name="InnerBlock" kind="Guid_Block"     optional="true"/>
      <field name="Seperator"  type="Guid_Seperator" optional="true"/>
      <child name="HexDigits"  kind="Guid_HexDigits"  optional="true"/>
    </node-structure>

    <node-structure name="Guid_HexDigitsSyntax" parent="VisualBasicSyntaxNode" >
      <description>Represents a Guid Hex Value. (ie 0xFEDC FEDC)</description>
      <node-kind name="Guid_HexDigits"/>
      <child name="Prefix" kind="Guid_HexPrefixToken" optional="true"/>
      <child name="Digits" kind="Guid_HexDigit"  list="true"/>
    </node-structure>

    <node-structure name="Guid_HexDigitSyntax"  parent="VisualBasicSyntaxNode">
      <description>Represents a Guid Hex digit. (ie 0-9A-Fa-f)</description>
      <node-kind name="Guid_HexDigit"/>
      <child name="Value" kind="EmptyToken"/>
    </node-structure>

    <node-structure name="Guid_HexPrefixToken" parent="SyntaxToken">
      <description>Represents the Guid Hex Prefix. (ie 0x)</description>
      <node-kind name="Guid_HexPrefixToken"/>
    </node-structure>

@AnthonyDGreen
Copy link
Contributor

Seems like a lot of structure for a literal. Why do GUIDs require so much more structure than say, Date literals?

@terrajobst
Copy link
Member

@KevinRansom @AnthonyDGreen

Immo ... We should add a guid constructor to the Guid attribute. Then we can add Guid literals to the compiler and have them be meaningful.

That constructor wouldn't help because there is no encoding at the ECMA 335 metadata level to persist an instance of the Guid type in attribute construction. However, the compiler could trivially convert the literal to a string.

@AdamSpeight2008
Copy link
Contributor Author

AdamSpeight2008 commented Sep 25, 2017

@terrajobst It could be stored as Object, then trycasted out. Or as 2 x int64 or as byte[16]. Stringly-Type should be last option, as it involves reparsing (typically at runtime)

@AnthonyDGreen
Copy link
Contributor

I maintain that it doesn't matter if we added such a constructor because no consumer of the attribute would ever take advantage of it. Additionally we don't have any data to suggest that the cost of reparsing the GUID at runtime is consequential. So, it looks like the only value of this feature is compile-time validation of a string to match a particular format. That's still valuable, but I think it's not worth all the compiler magic suggested. At this point I think a more general solution would be some kind of annotation on a string that makes it easier for analyzers to target and validate string literals against some format, e.g.:

Straw-man syntax

? "2017-09-25"Date
? "0f8fad5b-d9cb-469f-a165-70867728950e"GUID
? "{'key': value}"JSON
? ".*"RegEx
? "{1}, {0}"FormatString
? "867-5309"PhoneNumber

Such annotated literals would be still be strings and behave in all ways like strings and work with all existing APIs, would still be compile-time constants that work in attributes, etc., but would be easily marked for validation by one or more analyzers.

@Bill-McC
Copy link

Agree it should be handled just like date literals are handled.
Also like the concept of a more verbose literal along the lines of the scarecrow annotations.
Wonder if that could be tagging, eg
"0f8fad5b-d9cb-469f-a165-70867728950e"#GUID

@mkane91301
Copy link

As for usefulness, we currently have a file containing more than 100 GUIDs. We use GUIDs all over the place to identify just about everything in our system. I'm constantly writing LINQ queries to search for objects by their GUIDs. It's annoying to always have to write 'where p.UID == new Guid("xxxx-xxxx-etc")' and there are many cases where we place attributes on stuff to indicate the GUID of the thing that this code maps to.

I want this also to be a C# proposal, along with all the CLR changes needed for this to work without it just being syntactic sugar over "new Guid".

@AnthonyDGreen
Copy link
Contributor

@mkane91301 aside from the annoyance of constructing the GUID could you elaborate on the difficulties you're having today working with them? You didn't mention any performance bottlenecks with parsing nor runtime errors from mistyped GUIDs. Why is syntactic sugar over New Guid inadequate?

@terrajobst
Copy link
Member

terrajobst commented Oct 9, 2017

@mkane91301

I don't think integrating GUIDs into the language will help with Linq queries. In the end, you still have to tell the system how to find the GUID off of a domain type. Even without language integration you can trivially create some syntactic sugar for yourself today:

// I assume you already have something like this:
//
// public interface IUnqiueObject
// {
//     Guid UID { get; }
// }

public static class GuidExtensions
{
    public static T SingleOrDefault<T>(this IEnumerable<T> source, Guid guid) where T: IUnqiueObject
    {
        return SingleOrDefault.Where(o => o.UID == guid);
    }

    public static T Single<T>(this IEnumerable<T> source, Guid guid) where T: IUnqiueObject
    {
        return Single.Where(o => o.UID == guid);
    }
}

// Usage

var myOrder = orders.Single(guid);

(Thank god you said C# otherwise I would feel guilty for not providing this sample in VB 😄 )

@AnthonyDGreen
Copy link
Contributor

You both should feel guilty for polluting this sacred space with semicolons ;)

@Bill-McC I'm liking your tagging syntax!

@terrajobst
Copy link
Member

terrajobst commented Oct 9, 2017

The idea of tagging strings seems reasonable to me too. Low overhead in the grammar while retaining the value prop of them being validated. And, as @AnthonyDGreen pointed out, easy to extend to allow other kinds of formats in the future.

Making instances of the Guid type actual literals would require a lof of work as it would require the metadata format (and as a result all the runtimes) to be changed to allow for that. I'm quite convinced that this is not worth it.

@HaloFour
Copy link

HaloFour commented Oct 9, 2017

If these are static GUIDs that you find yourself referencing all over the place, in either attributes or code, then you can already easily add them as string constants with corresponding initonly fields/properties in a helper type:

Public Module Guids
    Public Const ProductGuidName = "xxxx-xxxx-etc"
    Public Readonly ProductGuid As Guid = New Guid(ProductGuidName)
End Module

' Usage in attributes
<Guid(Guids.ProductGuidName)> _
Public Class Product : Inherits BaseClass
End Class

' Usage in LINQ
Dim myProducts = things.Where(Function (thing) thing.Guid == Guids.ProductGuid)

Obviously those constants could still accidentally be invalid GUIDs and that's where this proposed validator tag could come in real handy.

@terrajobst
Copy link
Member

terrajobst commented Oct 9, 2017

Agreed. One could slightly extend the rules to allow tagged literals to be target typed to the Guid type like so:

Public Module Guids
    Public Readonly ProductGuid As Guid = "xxxx-xxxx-etc"GUID
End Module

@HaloFour
Copy link

HaloFour commented Oct 9, 2017

@terrajobst

I'm pretty sure we've waded into C++ custom literals. 😁

@dsaf
Copy link

dsaf commented Oct 9, 2017

@HaloFour and that, together with strong type aliases, is a very-very good thing!

@terrajobst
Copy link
Member

terrajobst commented Oct 9, 2017

@HaloFour

I'm pretty sure we've waded into C++ custom literals.

Seems to me VB has jumped the shark when they added XML literals. So at this point, any literals no mater how convoluted the syntax seems fair game ;-). Hey @AnthonyDGreen if you guys add support for inline C# I might use VB more often :trollface:

@AnthonyDGreen
Copy link
Contributor

Nonsense, @terrajobst . XML literals don't create some slippery slope that can justify adding anything that comes to mind to the language.

On an unrelated note, have you looked at #101 - so exciting!

@HaloFour
Copy link

HaloFour commented Oct 9, 2017

Besides, C# did it first (well, Cω technically): https://msdn.microsoft.com/en-us/library/ms974195.aspx

using Microsoft.Comega;
using System;

public class NewsItem{

  attribute  string title;
  attribute  string author;
  struct { DateTime date; string body; } 

  public static void Main(){

    NewsItem news = <NewsItem title="Hello World" author="Dare Obasanjo">
                      <date>{DateTime.Now}</date>
                      <body>I am the first post of the New Year.</body>
                    </NewsItem>;

    Console.WriteLine(news.title + " by " + news.author + " on " + news.date);
    
  }
}

Wait, what were we talking about again?

@AnthonyDGreen
Copy link
Contributor

C-omega may have prototyped it, but VB perfected it :)

@Bill-McC
Copy link

Bill-McC commented Oct 9, 2017

@AnthonyDGreen if the tagging syntax was for any constant expression when you type the # intellisense could list the constant types. Would provide a nice experience rather than having to remember literals. E.g 42#Byte

@craigajohnson
Copy link

Pinging this as per comment on #213 above. Please give this some consideration in this latest round of reviews. We use GUIDs everywhere and having them as first-class citizens without resorting to wrapping them as strings would be helpful.

As an aside, for a DSL for a project (using Irony.NetCore with VB), one of the first things I added was a GUID literal expression:

Dim GuidLiteral = New RegexBasedTerminal(Terms.GuidLiteral, "(\{)?[A-Fa-f0-9]{8}(-)?[A-Fa-f0-9]{4}(-)?[A-Fa-f0-9]{4}(-)?[A-Fa-f0-9]{4}(-)?[A-Fa-f0-9]{12}(\})?")

In VB, I would use GUID literals for attributes (at present wrapping them as strings) as well as any known magic immutables. Compile-time checking that the thing is actually a GUID is another benefit.

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

No branches or pull requests