Guid

Designed, not built. Nothing on this page compiles today; every sample carries // no-run for that reason. What runs is the type reference, and this page is kept apart from it on purpose (the specs index).

Guid is C#'s System.Guid: a 128-bit identifier, written as f81d4fae-7dec-11d0-a765-00a0c91e6bf6, compared and ordered by value, with an all-zero Guid.Empty and a random Guid.NewGuid().

It is the smallest page in this plan because it invents nothing: the sixteen-byte value, the machine module that moves it and the member-lowering table are all built by docs/specs/decimal.md and docs/specs/datetime.md, and this page spends them. What it adds is 16 bytes of layout, a hex parser, a hex formatter and a byte comparison — and one blocked function.


1. Representation #

type_new("Guid", 16, 16, TK_WIDE)

Sixteen bytes, sixteen-byte aligned, TK_WIDE — docs/specs/decimal.md § 1's registration exactly, moved by teko_wide.tk's three derived machine tables, by address and never in a register pair. Everything in that page's "How sixteen bytes travel" applies here word for word and is not repeated.

The sixteen bytes are in text order, big-endian, byte 0 first:

offsetholdsprints as
+0 .. +3time_lowthe first group, 8 hex digits
+4 .. +5time_midthe second group, 4
+6 .. +7time_hi_and_versionthe third group, 4
+8 .. +9clock_seqthe fourth group, 4
+10 .. +15nodethe fifth group, 12

That is RFC 4122's own order and the order the "D" format prints, which makes ToString a straight walk from byte 0 to byte 15 and Parse its inverse. It is not C#'s in-memory order: System.Guid stores _a as an int and _b/_c as shorts, so on a little-endian host its first eight bytes are byte-swapped relative to the text. The difference is invisible everywhere the surface can see it — ToString, Parse, == and != give identical answers either way — and it is visible in exactly two places C# users know are traps:

2. The surface #

Legal.

// no-run
#include "guid.tk"

i64 main() {
    Guid a = Guid.Parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6");
    Guid b = Guid.Parse("F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6");   // case-insensitive
    if (a != b) return 1;
    if (a == Guid.Empty) return 2;

    str s = a.ToString();                        // the "D" form, lowercase
    if (Guid.Parse(s) != a) return 3;
    if (a.ToString("N") != "f81d4fae7dec11d0a76500a0c91e6bf6") return 4;

    Guid z = Guid.Empty;
    if (z.ToString() != "00000000-0000-0000-0000-000000000000") return 5;
    if (z >= a) return 6;                        // ordered by the bytes, left to right

    Guid out_v = Guid.Empty;
    if (Guid.TryParse("nope", out out_v) != 0) return 7;
    return 42;
}

Illegal, and what each one earns.

// no-run
#include "guid.tk"

i64 main() {
    Guid a = Guid.Empty;
    i64 n = a;                    // teko: a value of type Guid does not convert to i64
    Guid b = 0;                   // teko: a value of type i64 does not convert to Guid
    Guid c = a + a;               // teko: no operator `+` takes these operands
    i64 d = (i64) a;              // teko: a Guid does not cast; `.ToString()` writes it
    Guid e = Guid.NewGuid();      // teko: Guid.NewGuid is not taught yet
    return 0;
}
writtenmessage
a Guid in any numeric slotteko: a value of type Guid does not convert to i64
anything in a Guid slotteko: a value of type i64 does not convert to Guid
+ - * / % & | ^ ~ << >>teko: no operator `+` takes these operands
(i64) g, (Guid) n written by handteko: a Guid does not cast; `.ToString()` writes it and `Guid.Parse(s)` reads it
an unknown memberteko: unknown member of Guid, teko: unknown static member of Guid
Guid.NewGuid()teko: Guid.NewGuid is not taught yet (§ 5)
a Guid on an externteko: an `extern` takes no Guid — the sixteen-byte convention is teko's own and crosses no C boundary
a Guid as a const or a switch labelteko: a const is an integer — the folder has no 128-bit arithmetic, exactly as for decimal

3. Operators #

writtenlowered toresult
a == b, a != btk_guid_eq, tk_guid_nei64 0/1
a < b, <=, >, >=tk_guid_lt … tk_guid_gei64 0/1
everything elserefused—

Every one is a call, because a sixteen-byte value has no cmp — the same rule docs/specs/decimal.md § 5 states. The comparison walks the bytes from +0, unsigned, and stops at the first difference.

4. The API #

staticinstance
Guid.Empty.ToString() — the "D" form
Guid.Parse(str).ToString(str fmt) — "D" or "N"
Guid.TryParse(str, out Guid).CompareTo(Guid), .Equals(Guid)
Guid.NewGuid() — blocked, § 5.IsEmpty — an i64 0/1

Text. ToString() writes 36 characters, lowercase, 8-4-4-4-12, which is C#'s "D" and C#'s own casing. ToString("N") writes the same 32 hex digits with no dashes. "B" (braces), "P" (parentheses) and "X" (the C struct form) are not taught: they are three more formats for the same sixteen bytes and nothing asks for them.

Parse accepts "D" and "N", either case, and panics on anything else (teko: the string is not a Guid). TryParse(s, out g) answers 0/1 and writes Guid.Empty on failure — the C# pair, and the reason out is worth having. tk_guid_fmt(ptr buf, Guid g, i64 fmt) is the allocation-free half, the split <float_rt> makes between putf64 and fmt_f64.

.IsEmpty is not a C# member — C# writes g == Guid.Empty — and it is here because it is one comparison the caller would otherwise write against a static that costs a load. It is the one addition on this page, and it is additive: g == Guid.Empty works too.

5. Guid.NewGuid is blocked on mc #

A version-4 Guid is sixteen bytes of cryptographically random data with the version nibble set to 4 and the variant bits to 10. The nibbles are arithmetic; the randomness is not, and teko cannot get it:

hostthe name
Linuxgetrandom(2), or /dev/urandom
macOSgetentropy(3), or /dev/urandom
WindowsBCryptGenRandom — bcrypt.dll, and not one of the kernel32 entry points <sys> binds

mc has no conditional compilation, the five legs compile one source, and the Windows leg resolves <sys>'s libc-shaped names through mc's own runtime object, which is mc's file and not this repository's. Writing a second include per host, or a [libs]/[externs] mapping in the leg config, would push an operating-system choice into every consumer's teko.toml for one function — which is exactly the workaround docs/specs/datetime.md § 8 refuses for the wall clock, for the same reasons.

The ask is one function, and it goes to mc's notices file beside the wall clock:

i64 sys_random(uptr buf, i64 n);      // n bytes of entropy; 0 on success

one name across the three hosts, getrandom/getentropy under it on the two POSIX legs and BCryptGenRandom on Windows. It unblocks on whichever mc release carries it. Until then Guid.NewGuid is refused by name, and every other member of this page lands without it.

A Guid built from a counter, a clock or an address would compile and would be a wrong answer — two processes would collide — so it is not the fallback. There is no fallback; there is a refusal and a filed ask.

6. What stays out #

left outwhy
Guid.NewGuid§ 5, blocked on <sys>
version 1, 3, 5 and 7 Guidsv1 needs a MAC address and a clock, v3/v5 need MD5/SHA-1, v7 needs a clock; all of them are a library over NewGuid's own primitive
ToByteArray, new Guid(byte[])the byte order question of § 1 becomes visible the moment either exists, and neither is asked for
"B", "P", "X" formatsthree more spellings of the same bytes
C#'s field-wise CompareTo order§ 1: teko orders by the bytes as they print. C#'s order is a documented trap; matching it would mean matching the in-memory byte swap too, and then ToString stops being a straight walk
a Guid on an externteko's sixteen-byte convention is not a C ABI

7. The hooks, by module #

modulewhat it grows
teko_wide.tknothing — it already moves any TK_WIDE id; Guid is the second one it carries and proves the module is general rather than decimal-shaped
teko_guid.tk (new)one type_new, one syntax_expr for the type word, and the rows of § 3 and § 4 in the primitive-member and primitive-operator tables
teko_prim.tknothing: the tables are docs/specs/datetime.md § 9's, and this page adds rows to them
teko_typeof.tknothing: the § 5 clause of that page already refuses every conversion
teko.tk#include and one _init() call
lib/guid.tk (new)the parser, the formatter, the comparison and Empty: about 200 lines of ordinary teko over ld8/st8 through &g
lib/rt.tk, core_teko.mc, user.mcnothing

8. What it costs in mc limits #

rowbeforeafterwhy
types—+1one type_new
syntax—+1Guid.Empty and Guid.Parse need the type word to open an expression
passes, intrin, alias—unmovedby design, as every page in this plan

The machine module adds no row: teko_wide.tk is already derived, and a derived table shadows the name it registers rather than consuming a machines slot.

9. Fixtures #

fixtureassertsexpect-exit
tests/primitives_guid.tkParse/ToString round-trip in both cases and both formats; Empty; the six comparisons against a known ordering; a Guid through a local, a parameter, a return, a field, a global and a Guid[] element; a recursive Guid function, proving the sixteen-byte return buffer under nesting42
tests/primitives_guid_bytes.tkthe sixteen bytes read back through &g with ld8 match the documented order for a known text form — the layout has an oracle, not a comment42
tests/primitives_guid_tryparse.tkTryParse on a good "D", a good "N", a wrong length, a bad character and a misplaced dash; Empty written on every failure42
tests/primitives_guid_parse_bad.tkGuid.Parse("nope")70

Every one is a whole program with #include "../lib/guid.tk", returning 42 on success and a small distinct number per failed assertion.

10. The crumb #

N3 — Guid (M) #

The registration, the tables, lib/guid.tk, and the NewGuid refusal. Depends on docs/specs/decimal.md's C3 for teko_wide.tk and the sixteen-byte value, and on docs/specs/datetime.md's C1 for the primitive-member and primitive-operator tables. It depends on nothing else — in particular not on decimal's arithmetic, so it can land between C3 and C4.

Gate: the four fixtures at their exit codes on all five legs — a sixteen-byte value in a parameter and a return is what a single leg cannot prove — every other fixture unchanged with --dump-ast byte-identical, FIXPOINT OK, mc limits verdict ok with passes and intrin not moved, sh scripts/check-docs.sh green. Owes: a Guid section in types.md, the refusals in diagnostics.md, lib/guid.tk in runtime.md, the NewGuid row in not-yet.md, and the new module in modules.md.

N9 — Guid.NewGuid (S, blocked) #

One static row, one call into lib/guid.tk, one fixture asserting that two consecutive NewGuid() values differ, that the version nibble is 4 and that the variant bits are 10. Blocked until mc's <sys> carries § 5's entropy function on the three hosts.

11. Risks and law tensions #

tensionrecommended resolution
The ordering diverges from C#. a < b answers by the printed bytes, C# by its fields.§ 1, deliberately, and written into types.md beside the type. Matching C# would mean matching its in-memory byte swap, which would make ToString a shuffle and Parse its inverse, for an ordering C# users are warned about anyway. The fixture pins teko's order with values that only pass under it.
Guid is the second TK_WIDE type and the first that is not decimal. If teko_wide.tk turned out to be decimal-shaped, this is where it shows.That is a feature of the order, not a risk of it: N3 lands right after C3 and before decimal's arithmetic, so the machine module is proved general while it is still small. The _bytes.tk fixture reads raw bytes through & rather than trusting an arithmetic result, which is docs/specs/decimal.md § 13's own technique.
NewGuid invites a workaround — an extern getentropy would work on four of the five legs.Refuse it. Four legs out of five is a silently wrong build on the fifth, and D2 says a construct mc cannot express is reported, never worked around. The refusal is by name and the ask is filed with the wall clock.
Guid.NewGuid and DateTime.Now are blocked on the same kind of thing.They are one ask with two functions, and it should be sent as one: <sys> grows a wall clock and an entropy source. A release carrying either unblocks its own crumb independently.
The include. #include "guid.tk" is a build-time step C# does not have.Same answer as docs/specs/datetime.md § 13: refuse the type word with the include named in the message, and leave the flip into lib/rt.tk open for the owner — it is a one-line change either way, and it is the same decision for time.tk, decimal.tk, guid.tk and string.tk, so it should be taken once for all four.

Edit this page