Lisette — Rust syntax, Go runtime

Source: original

Lisette

a little language inspired by Rust that compiles to Go

Algebraic data types · Pattern matching · No nil · Hindley-Milner type system · Immutable by default Interoperability with Go's ecosystem

cargo install lisette

import "go:fmt" import "go:io" import "go:os"

fn load_config(path: string) -> Result { let file = os.Open(path)? defer file.Close() let data = io.ReadAll(file)? parse_yaml(data) }

fn main() { match load_config("app.yaml") { Ok(config) => start(config), Err(e) => fmt.Println("error:", e), } }

Familiar

Syntactically similar to Rust

Enums & pattern matching

enum Message { Ready, Write(string), Move { x: int, y: int }, }

fn handle(msg: Message) -> string { match msg { Message.Ready => "ready", Message.Write(text) => f"wrote: {text}", Message.Move { x, y } => f"move to ({x}, {y})", } }

Structs & impl blocks

import "go:math"

struct Point { x: float64, y: float64, }

impl Point { fn distance(self, other: Point) -> float64 { let (dx, dy) = (self.x - other.x, self.y - other.y) math.Sqrt(dxdx + dydy) } }

Expression oriented

fn describe(score: int) -> string { let grade = if score >= 90 { "A" } else if score >= 70 { "B" } else { "C" }

let stars = { let count = score / 20 strings.Repeat("*", count) }

f"{grade}{stars}" }

Chaining and lambdas

fn server_url() -> string { let scheme = os.LookupEnv("HTTPS") .filter(|s| s == "1") .map(|_| "https") .unwrap_or("http")

let host = os.LookupEnv("HOST") .map(|s| strings.TrimSpace(s)) .filter(|s| s.length() > 0) .unwrap_or("localhost")

let port = os.LookupEnv("PORT") .unwrap_or("8080")

scheme + "://" + host + ":" + port }

Interfaces & generics

interface Metric { fn label(self) -> string fn value(self) -> float64 }

fn report(metrics: Slice) { for m in metrics { fmt.Println(m.label(), m.value()) } }

fn max(metrics: Slice) -> T { metrics.fold(metrics[0], |a, b| if a.value() > b.value() { a } else { b } ) }

if let & let else

type Headers = Map

fn handle_headers(h: Headers) -> Result<(), string> { if let Some(token) = h.get("Authorization") { let user = authenticate(token)? authorize(user)? } else { return Err("missing credentials") }

let Some(id) = h.get("X-Request-ID") else { return Err("missing request ID") }

process(id) }

Safe

Go runtime issues, caught at compile time

🔴 match is not exhaustive ╭─[example.lis:4:3] 2 │ enum Severity { Low, High, Critical } 3 │ fn should_alert(s: Severity) -> bool { 4 │ match s { · ───┬─── · ╰── not all patterns covered 5 │ Severity.Low => false, 6 │ Severity.High => true, 7 │ } ╰──── help: Handle the missing case Severity.Critical, e.g. Severity.Critical => { ... }

🔴 nil is not supported ╭─[users.lis:3:12] 1 │ fn find(name: string) -> Option { 2 │ if name.is_empty() { 3 │ return nil · ─┬─ · ╰── does not exist 4 │ } 5 │ db.lookup(name) 6 │ } ╰──── help: Absence is encoded with Option in Lisette. Use None to represent absent values

🟡 Result is silently discarded ╭─[files.lis:7:3] 5 │ fn cleanup() -> Result<(), error> { 6 │ os.RemoveAll("cache_dir")? 7 │ os.Remove("temp.txt") · ──────────┬────────── · ╰── failure will go unnoticed 8 │ } ╰──── help: Handle this Result with ? or match, or explicitly discard it with let _ = ...

🟡 Private type Config in public API ╭─[config.lis:6:24] 5 │ struct Config { host: string, port: int } 6 │ pub fn new_config() -> Config { · ───┬── · ╰── private 7 │ Config { host: "localhost", port: 8080 } ╰──── help: Config is private but exposed by new_config, which is public. Add pub to the private type or remove it from the public API

🔴 Immutable argument passed to mut parameter ╭─[sort.lis:5:13] 4 │ let nums = [3, 1, 2] 5 │ sort.Ints(nums) · ──┬─ · ╰── expected mutable, found immutable 6 │ } ╰──── help: sort.Ints() may mutate nums, so declare it mutable using let mut nums = ...

🔴 Struct Server is missing fields ╭─[server.lis:10:11] 9 │ fn start(mux: http.Handler) { 10 │ let s = Server { handler: mux } · ───┬── · ╰── missing fields: db, logger 11 │ http.ListenAndServe(":8080", s.handler) 12 │ } ╰──── help: Initialize all fields in this struct literal

LSP support for: VSCode Neovim Zed Helix GoLand

Ergonomic

Designed for interoperability with Go

Pipeline operator

import "go:strings"

fn slugify(input: string) -> string { input

strings.TrimSpace strings.ToLower strings.ReplaceAll(" ", "-") }

slugify(" Hello World ") // => "hello-world"

Try blocks

fn load_config() -> Config { let result = try { let data = os.ReadFile("app.toml")? parse_toml(data)? } match result { Ok(config) => config, Err(_) => Config.default(), } }

Concurrency

fn fetch_primary() -> string { ... } fn fetch_replica() -> string { ... }

let ch1 = Channel.new() let ch2 = Channel.new()

task { ch1.send(fetch_primary()) } // goroutine task { ch2.send(fetch_replica()) } // goroutine

let quickest = select { match ch1.receive() { Some(v) => v, None => "closed", }, match ch2.receive() { Some(v) => v, None => "closed", }, }

Serialization attributes

[json(camel_case)]

struct UserProfile { user_name: string, // => json:"userName"

[json("userID")]

account_id: int, // => json:"userID"

[json(omitempty)]

bio: Option, // => json:"bio,omitempty"

[json(string)]

score: int64, // => json:"score,string"

[tag("validate", "required")]

email: string, // => validate:"required"

[json(skip)]

internal_id: int, // => json:"-" }

Panic recovery

fn safe_divide(a: int, b: int) -> Result { let result = recover { a / b } // => Result result.map_err(|pv| pv.message()) }

match safe_divide(10, 0) { Ok(v) => fmt.Println(v), Err(e) => fmt.Println(e), }

Deferral

fn run(query: string) -> Result<(), error> { let db = connect()? defer db.Close()

let tx = db.Begin()? defer tx.Rollback()

tx.Exec(query)? tx.Commit() }

Transparent

Compiles to understandable Go

Lisette

fn classify(opt: Option) -> string { match opt { Some(x) if x > 0 => "positive", Some(x) if x < 0 => "negative", Some(_) => "zero", None => "none", } }

Compiled Go

func classify(opt lisette.Option[int]) string { if opt.Tag == lisette.OptionSome { x := opt.SomeVal if x > 0 { return "positive" } if x < 0 { return "negative" } return "zero" } return "none" }

Lisette

fn combine() -> Result { let x = first()? let y = second()? Ok(x + y) }

Compiled Go

func combine() lisette.Result[int, string] { check_1 := first() if check_1.Tag != lisette.ResultOk { return lisette.MakeResultErrint, string } x := check_1.OkVal check_2 := second() if check_2.Tag != lisette.ResultOk { return lisette.MakeResultErrint, string } y := check_2.OkVal return lisette.MakeResultOkint, string }

Learn more

By visiting the repository: github.com/ivov/lisette

By visiting the WASM playground: lisette.run/play