Known Limitations
Wyn is honest about what works and what doesn't, and this page is re-run rather than carried forward: each entry is checked against the current binary, and anything that no longer reproduces is moved to "no longer limitations" with the evidence.
Last re-run: v1.23.0-rc, on macOS arm64 and Linux arm64. Four entries moved out as a result, each using the example the entry itself had used to demonstrate the bug: print of a HashSet, println of a struct with a nested-struct or array field, unknown methods on a builtin namespace (now a checker error, not a build failure), and the regex shorthand classes \d/\w/\s — which also turned out to support backreferences. The previous re-run, against v1.22.0, had moved three: printing a map, printing none, and parallel { } overlapping only two CPU-bound branches.
Four stale entries in one release is the argument for re-running the list instead of editing around it. Entries that are still true were re-confirmed the same way, not assumed: string.len() counting bytes, Math.checked_add panicking rather than returning a Result, a top-level lambda failing to build, and Regex.find_all/split returning newline-joined strings.
Runtime
A wrong argument type on a stdlib call is not caught, and can segfault.
wyn checkdoes not verify builtin argument types, so passing anintwhere astringis expected compiles into a pointer cast:wynx = File.read(7) // check: no errors. build: OK. run: SIGSEGV (exit 139)Measured across 18 deliberately wrong calls: none was caught by
wyn check, 14 compiled, and every compiled one crashed.Crypto.sha256(3),Log.info(5),Url.encode(3),Http.get(42)andJson.parse(9)all behave this way. Arity is unchecked too -Uuid.v4("x")compiles although the function takes no arguments. This is the most serious item on this page: pass the types the API reference states, because nothing else will check them for you. Filed as issue #475.Not every builtin is in this state. The ones registered with a precise signature —
Http.status()andHttp.error()among them — do check both arity and argument types, and give an ordinary "wrong number of arguments" error.A lambda assigned at top level (script mode) fails to build. Inside
fn main()both lambda spellings work; at the top level of a script the variable is typed as an integer and the call fails withcalled object type 'long long' is not a function. Put lambdas inside a function.spawnon a closure returns the wrong answer, silently. A closure called directly returns its value; the same closurespawned returns0.wyn checkpasses, the build succeeds, and the process exits 0:wynvar n = 5 g = (() => n * 2) println(g()) // 10 f = spawn (() => n * 2) println(await f) // 0 <-- wrongspawnneeds a function pointer, and a closure's captured environment is not carried across. Until this is fixed, pass captured values as explicit parameters to a named function and spawn that. This is the most serious item on this page because nothing warns you.parallel { }leaves some branch shapes sequential, and says which. A method call on a value, a non-scalar argument, a non-word result, or more than eight arguments is refused by name rather than silently run inline. Everything else - a bare call, a declaration from a call, assignment to an outer variable, an explicitspawn, and a namespaced builtin in either the.or the::spelling - is dispatched.awaiton a spawned builtin yields0.Recursive spawn -
spawninside recursive functions works reliably up to ~28K outstanding tasks (fib(22)verified on v1.21.0). Beyond that, the thread pool can deadlock. Use sequential recursion for deep trees,spawnfor parallel leaf work.string.len()returns bytes - UTF-8 byte count, not character count."héllo".len()is 6 and"日本語".len()is 9.Math.checked_add()panics, it does not return aResult. It is a guard against silently wrapping, not a recoverable check:Math.checked_add(9223372036854775807, 1)aborts withpanic: integer overflow in checked_add(...).
No Longer Limitations
printof aHashSetprints a pointer address - fixed.s = {:"a"}/print(s)now prints{:"a"}, andprint({"a": 1})prints{"a": 1}, so maps and sets format alike. Re-verified on v1.23.0-rc.println(v)on a struct with a nested-struct or array field fails to build - fixed.println(o)onOuter { i: Inner { n: 1 }, xs: [1, 2] }printsOuter { i: Inner { n: 1 }, xs: [1, 2] }. Interpolation and the bare call now agree, so the workaround this entry recommended is no longer needed. Re-verified on v1.23.0-rc.Unknown methods on a builtin namespace pass
wyn checkand fail to build - fixed, and it is now a checker error with a Wyn-level message. The exact case this entry named:Time.millis()givesError at line 1: unknown method 'Time.millis' on namespace 'Time'and a help line pointing at the namespace's docs, atwyn checktime (exit 1). Re-verified on v1.23.0-rc.Regex shorthand classes
\d,\w,\sare unsupported and fail silently - fixed, on both platforms tested.Regex.match("a1", "\\d")istrue,Regex.match("abc", "\\d")isfalse, and the example this entry used to prove the bug now behaves correctly:Regex.replace("ab123cd", "\\d+", "N")isabNcd, not the letters rewritten.\wand\swork inmatch,replace,splitandfind_all. Backreferences also work -(ab)\1matchesababand does not matchabcd. Re-verified on v1.23.0-rc on macOS arm64 and Linux arm64.Fixed in v1.22.printlnof a map or ofnoneprints a pointer address.print(m)on a map gives{"a": 1}, interpolation gives the same, anOptionvariable printsSome(5)and an empty one printsnone. AHashSetstill prints a pointer, which is why that half stays on the list above.Fixed in v1.22 - and there were never any dispatch rounds. Four branches with distinct arguments now finish in 1.04x of one branch (5,664µs for one, 5,881µs for four). The original figure came from a fixture that gave every branch the same argument; because the function was pure, the C compiler collapsed the identical sibling calls into one, so two branches measured like perfect overlap while dispatching nothing, and adding more branches looked like a second round.parallel { }overlaps only two CPU-bound branches; three or four take two dispatch rounds.A bareFixed in v1.22. Eight branches of a 200ms sleep take 210ms against a 204ms single-branch baseline; on v1.21.0 the same program took 1,649ms. Wrapping the call in a one-line user function is no longer necessary.Time.sleep(200)in aparallel { }block runs inline.Fixed in v1.22. The RC header's cached length is now filled on a read that misses, not only by whichever constructor remembered to set it, so every string is O(1) after its first.len()is O(n) on someStringBuilderresults..len()- about 1.7ns regardless of length. On a 100,000-character builder result, 100,000 calls went from 242ms to 0.16ms. The advice to hoist.len()out of a loop no longer applies. Filling the cache on a read initially cost a literal receiver 1.75x, because a literal lives in read-only memory and can never hold a cached length, so it paid for the lookup forever; that is measured and fixed too - a literal is back to its original 1.5ns.Integer overflow - silent wrap-around.As of v1.21.0, wrapping is defined. Wyn compiles with-fwrapv, so signed integer overflow wraps two's complement and is guaranteed to:9223372036854775807 + 1is-9223372036854775808, every time, at every optimisation level. Previously the optimiser was entitled to assume overflow could not happen, which is how wrapping code turns into deleted comparisons. It costs nothing measurable - fib(35) is unchanged. UseMath.checked_add()if you want the overflow to abort instead.Sort performance - slower than Go.Re-measured on v1.21.0 with both sides optimised:[int].sort()does 1M ints in 73ms against Go'ssort.Intsat 83ms, and 100K in 6.3ms against 7.0ms. The old figures (124ms vs 101ms) compared a Wyn dev build againstgo build, which optimises by default. A dev build really does take 110ms, so use--releasefor anything you intend to time.
Compile Time
- Compile speed -
wyn buildtakes ~356ms (dev) or ~1.5s (release) for hello world, and ~660ms / ~3.0s on 5,000 lines of realistic code.wyn checkis ~9ms on hello world - but that is almost entirely process startup (wyn versionalone is ~8.4ms), and checking rises to ~17ms at 1,000 lines and ~72ms at 5,000. The bottleneck for builds is the C compiler (clang/gcc), not Wyn's codegen. Go's purpose-built compiler is faster (go buildhello world, forcing a real link: 189ms).
Platform Notes
Windows cross-compilation - not yet supported from macOS. Linux x64/ARM64 work.
Regex: lookaround and capture-group extraction are unsupported.
Regex.match("abc", "a(?=b)")isfalse, andRegex.matchreturns a plainbool- there is no match object and no.group(n), so you cannot pull a captured substring out. Everything else tested works:., classes,*,+,?,{n},{n,m}, alternation,^/$, the shorthand classes\d/\w/\s, and backreferences.Two engines, one namespace.
Regex.findlowers to a POSIXregcomp/regexecpath (#ifndef _WIN32), whilematch/replace/find_all/splituse the built-in NFA, and on WindowsRegex.findfalls back to the NFA as well. They agreed on every case tested here, but they are separate implementations with separate notions of an invalid pattern, so a pattern that is rejected by one may not be by the other.Regex.find_allandRegex.splitreturn a newline-joined string, not an array - as doesDb.query(rows by\n, columns by|). Iterate with.trim().split("\n").Mobile -
system()/popen()return stubs on iOS/Android.WASM - not yet supported. Runtime has POSIX dependencies that need
#ifdefguards.
See Also
- FAQ - common questions
- Benchmarks - performance numbers
- Version History - all releases