Lisette compiles to Go. Rust-like syntax, Go runtime. No ownership, no borrowing, no lifetimes.
| Rust | Lisette |
|---|---|
&T, &mut T |
Ref<T> |
*ptr |
ptr.* |
Vec<T> |
Slice<T> |
HashMap<K, V> |
Map<K, V> |
String, &str |
string |
Color::Red |
Color.Red |
trait Foo |
interface Foo |
impl Foo for Bar |
just implement the methods (structural) |
.unwrap() |
DOES NOT EXIST — use match, let else, ?, or .unwrap_or() |
println!("{}", x) |
fmt.Println(f"{x}") |
format!("...") |
f"..." |
vec![1, 2, 3] |
[1, 2, 3] |
v.push(x) |
v = v.append(x) (returns new slice) |
v.len() |
v.length() |
.iter().map().collect() |
.map() directly on Slice<T> |
Box<dyn Error> |
error (lowercase, it's an interface) |
Prelude variants need no prefix: Some(x), None, Ok(x), Err(e).
| Go | Lisette |
|---|---|
func |
fn |
x := 5 |
let x = 5 |
var x int |
let x: int = ... (must initialize) |
nil |
DOES NOT EXIST — use None for absent values |
switch |
match (exhaustive) |
go func() { ... }() |
task { ... } |
make(chan int) |
Channel.new<int>() |
ch <- v |
ch.send(v) |
v := <-ch |
ch.receive() returns Option<T> |
if err != nil { return err } |
let x = fallible()? |
(T, error) return |
Result<T, error> |
(T, error) non-exclusive |
Partial<T, error> |
(T, bool) return |
Option<T> |
for i := 0; i < n; i++ |
for i in 0..n |
for {} |
loop {} |
f(args...) (variadic spread) |
f(args...) |
Variables are immutable by default. Use let mut for mutable bindings.
"..." strings can span multiple lines — newlines and indentation are preserved verbatim. Use r"..." for raw strings (no escape processing), useful for regex, Windows paths, or any text with backslashes.
Lisette requires all struct fields to be initialized. Use .. to fill any unspecified fields with their zero value: Point { x: 10, .. } leaves y at its zero value, Point { .. } zero-fills everything. Useful for Go structs with many config fields.
A few Go functions return (T, error) where both can be meaningful at once, e.g. io.Reader.Read. These bind as Partial<T, E> with three variants: Partial.Ok(T), Partial.Err(E), Partial.Both(T, E). Match all three; ? does not work on Partial.
my_project/
├── lisette.toml // project manifest
├── AGENTS.md
└── src/
├── main.lis // entry point
└── models/ // module named "models"
└── user.lis
All .lis files go in src/. Subdirectories are modules, imported by path: import "models".
import "go:fmt" // Go stdlib — always prefix with go:
import "go:net/http" // nested packages too
import "models" // Lisette module — no prefix
NEVER convert Go function names to snake_case. Keep PascalCase:
strings.ToUpper("hello") // correct
strings.to_upper("hello") // WRONG
Go type mappings:
| Go | Lisette |
|---|---|
*T |
Ref<T> (non-null) or Option<Ref<T>> (nullable) |
[]T |
Slice<T> |
map[K]V |
Map<K, V> |
chan T |
Channel<T> |
any, interface{} |
Unknown (narrow with assert_type<T>()) |
...T |
VarArgs<T> |
import "go:fmt"
import "go:net/http"
import "go:io"
import "go:strings"
import "go:errors"
#[json]
struct User {
name: string,
#[json(omitempty)]
email: Option<string>,
}
enum Role {
Admin,
Member { team: string },
}
impl User {
fn display(self) -> string {
f"User({self.name})"
}
fn set_email(self: Ref<User>, email: string) {
self.email = Some(email)
}
}
fn fetch_body(url: string) -> Result<string, error> {
let resp = http.Get(url)?
defer resp.Body.Close()
let body = io.ReadAll(resp.Body)?
Ok(body as string)
}
fn find_admin(users: Slice<User>) -> Result<User, error> {
let Some(user) = users.find(|u| u.name == "admin") else {
return Err(errors.New("no admin found"))
};
Ok(user)
}
fn main() {
let users = [
User { name: "Alice", email: Some("alice@co.com") },
User { name: "Bob", email: None },
]
let names = users
.map(|u| u.name)
.filter(|n| n.length() > 3)
for name in names {
let slug = name |> strings.ToLower |> strings.TrimSpace
fmt.Println(f"slug: {slug}")
}
match find_admin(users) {
Ok(admin) => fmt.Println(admin.display()),
Err(e) => fmt.Println(f"error: {e}"),
}
let ch = Channel.new<int>()
task { ch.send(42) }
match ch.receive() {
Some(v) => fmt.Println(v),
None => {},
}
let result = recover { fetch_body("http://example.com") }
match result {
Ok(body) => fmt.Println(body),
Err(pv) => fmt.Println(pv.message()),
}
}
The last expression in a block is the return value. Do NOT add a trailing semicolon.
Full reference: https://github.com/ivov/lisette/tree/main/docs/reference
lis run— compile and runlis build— compile totarget/lis check— type check onlylis format— format codelis add google/uuid- add a third-party Go dependencylis sync- reconcilelisette.tomlwith importslis doc Slice— show a prelude type and its methodslis doc Option.map— show a single methodlis doc go:os— browse a Go stdlib packagelis doc -s split— search prelude and Go stdlib
NEVER modify anything inside target/ — all generated by the compiler.