Coming from Rust
Every section below is a place where Lisette reads like Rust and behaves differently.
Bindings
Section titled “Bindings”let s1 = String::from("hello");let s2 = s1;println!("{}", s1);error: value movedlet s1 = "hello"let s2 = s1fmt.Println(s1)okRust moves s1 into s2. Lisette has no moves, so s1 stays usable.
See Bindings
Strings
Section titled “Strings”fn greet(name: &str) -> String { format!("Hello, {}!", name)}fn greet(name: string) -> string { f"Hello, {name}!"}String and &str collapse into one string. No owned versus borrowed distinction.
See String
References
Section titled “References”let x = 42;let r: &i32 = &x;println!("{}", *r);
fn increment(r: &mut i32) { *r += 1;}let x = 42let r: Ref<int> = &xfmt.Println(r.*)
fn increment(r: mut Ref<int>) { r.* += 1}Ref<T> and mut Ref<T> line up with Rust’s &T and &mut T, but mut Ref<T> is not exclusive. &x yields whatever x allows, so a let mut binding gives a mut Ref<T> and a plain let gives a read-only one.
See References
Write permission
Section titled “Write permission”let a = vec![1, 2, 3];let mut b = a;b[0] = 99;println!("{:?}", a);error: borrow of moved valuelet a = [1, 2, 3]let mut b = aallowed, but b inherits a's permissionb[0] = 99error: b is read-only
let mut c = a.clone()independent storagec[0] = 99write permitted, a untouchedA slice is a handle. Both names reach the same storage rather than a copy of it, and b inherits the read-only permission of a. .clone() severs the alias and hands back writable storage.
struct Index { counts: HashMap<String, i32>, tags: Vec<String>,}struct Index { counts: mut Map<string, int>,writable, through a writable Index tags: Slice<string>,read-only}Rust makes a whole value mutable at once, through &mut self or a mut binding. In Lisette, each field declares its own permission.
See Bindings, Structs and Safety
Closures
Section titled “Closures”let mut total = 0;let add = |n: i32| { total += n; };error: cannot borrow add as mutableadd(5);let mut total = 0let add = |n: int| { total += n }add(5)A closure captures by reference, and the garbage collector keeps what it captured alive. No borrow rules, and no move keyword.
See Lambdas
Traits and interfaces
Section titled “Traits and interfaces”trait Display { fn to_string(&self) -> String;}
impl Display for Point { fn to_string(&self) -> String { format!("({}, {})", self.x, self.y) }}interface Display { fn to_string() -> string}
impl Point {Point satisfies Display fn to_string(self) -> string { f"({self.x}, {self.y})" }}In Lisette, a type satisfies an interface simply by having matching methods.
See Interfaces
Equality
Section titled “Equality”#[derive(PartialEq)]struct Order { id: i32, tags: Vec<String>,}#[equality]struct Order { pub id: int, pub tags: Slice<string>,}Rust needs the derive before == works. Lisette accepts == without it, but only if every field is comparable. A slice, map or function field removes ==, and #[equality] supplies an equals() method in its place.
See Equality
Error handling
Section titled “Error handling”let bytes = std::fs::read(path).unwrap();let Ok(bytes) = os.ReadFile(path) else { fmt.Println("could not read") return}?, match, let else, and map_or work the same. Lisette has no unwrap().
See Failures