Geno 0.5.0 is out. It carries one deliberate breaking change: when main returns an Int, that integer is now the exit status of your program. The release first shipped as 0.5.0-rc.1 and was promoted to 0.5.0 unchanged, under proposal 0002. Geno is still a pre-1.0 preview, so the language may change before 1.0.
$ pip install geno-lang
$ geno --version
geno 0.5.0
Exit codes, on every boundary
Until now, geno run printed an integer result as a => 2 line and exited 0. That was pleasant for small examples and awkward for real command-line tools. A link checker that found broken links had to throw to fail CI, which turned an expected outcome into a stack trace.
Accepted proposal 0001 fixes that. A main() -> Int now supplies the process status, normalized modulo 256:
func main() -> Int
let broken = check_links()
print(f"{broken} broken links")
if broken > 0 then
return 2
end if
return 0
end func
$ geno run sitecheck.geno
3 broken links
$ echo $?
2
The rule holds on all seven executable boundaries the conformance corpus now covers: the in-process lane, the three geno run lanes, standalone compiled Python, the Node script, and directly executed Node ESM. Output written before the result still arrives, and a normal nonzero status carries no traceback.
Things that are not process boundaries keep returning the raw value. geno.api.run(), the hosted server and imported artifacts never terminate their caller because main returned a number. Browser builds keep displaying the result, since a browser tab has no exit status.
Migrating
If your integer was there to be seen, print it and return Unit:
func main() -> Unit
print(double(21))
return ()
end func
If you run geno run --json under set -e or with check=True, note that the envelope still reports the raw value (258 stays 258) while the process now exits with the normalized status (2).
Named arguments for constructors
Point(x: 10, y: 20) now parses, as the specification has always written it. The checker validates names against the variant's fields and reports unknown, repeated, missing and positional-after-named arguments. Arguments evaluate in source order on every backend.
type Point = Point(x: Int, y: Int)
func origin() -> Point
example () -> Point(x: 0, y: 0)
return Point(x: 0, y: 0)
end func
An Async value runs once
Section 7.15 of the 0.5 specification now says it plainly: awaiting the same Async[T] twice, even while the first await is still in progress, produces the result of a single run. A run that threw throws the same error to every awaiter. Python, JavaScript and the interpreter agree.
Module constants
A let outside any function declares a constant every function in the file can read. The initializer must be a literal, so importing a module still never runs code.
let usage: String = "roman <number>"
let numerals: List[String] = ["I", "V", "X", "L", "C", "D", "M"]
Fixes from the 0.5 evaluation
- Large integers in
geno run --jsonstay exact JSON numbers past Python's 4,300-digit conversion limit, including inside collections. - A method defined twice in one
implblock is reported asE309instead of silently keeping the last one. - Record fields may begin with a single underscore on every backend.
path_parent,path_extensionandpath_is_absoluteagree across Python and JavaScript.- The REPL shows
printoutput and type-checks expressions.
Three further checks reject programs that 0.4.4 accepted: an entry main that declares parameters (E304), a bare name that two imports both export, and a repeated or built-in type name. Each of those programs already failed or misbehaved at run time on at least one backend, so the checker now says so earlier. The full list is in the changelog.
Try it
The playground on this site runs this exact release in your browser. Bugs and rough edges go to GitHub issues; questions and early impressions to Discussions.