Skip to content

Interfaces

An interface is a set of method signatures. A type satisfies an interface by implementing all its methods. No explicit declaration is needed.

interface Shape {
fn area() -> int
}
struct Rectangle {
width: int,
height: int,
}
Rectangle implements area() so it satisfies Shape
impl Rectangle {
fn area(self) -> int {
self.width * self.height
}
}
fn describe(shape: Shape) -> string {
f"area is {shape.area()}"
}
describe(Rectangle { width: 10, height: 5 })Rectangle is accepted as a Shape

An interface can embed other interfaces, composing their methods into one:

interface Reader {
fn read(buf: Slice<byte>) -> Result<int, error>
}
interface Writer {
fn write(buf: Slice<byte>) -> Result<int, error>
}
satisfied only by a type with both read() and write()
interface ReadWriter {
embed Reader
embed Writer
}

Interfaces accept type parameters:

interface Iterator<T> {
fn next() -> Option<T>
}
struct Counter {
current: int,
end: int,
}
satisfies Iterator<T>
impl Counter {
fn next(self: mut Ref<Counter>) -> Option<int> {
if self.current >= self.end {
return None
}
let value = self.current
self.current += 1
Some(value)
}
}

Type arguments must match exactly, as in Go. Even if Cat satisfies Animal, a Box<Cat> is not a Box<Animal>.

interface Animal {
fn speak() -> string
}
struct Box<T> {
value: T,
}
impl<T> Box<T> {
fn get(self) -> T { self.value }
}
fn take_box(box: Box<Animal>) {
let _ = box.get()
}
struct Cat {}
impl Cat {
fn speak(self) -> string {
"meow"
}
}
let cat_box = Box { value: Cat {} }
take_box(cat_box)error: expected Box<Animal>, found Box<Cat>

A Slice<Animal> can hold a mix of different types, so long as each one satisfies Animal:

struct Dog {}
impl Dog {
fn speak(self) -> string {
"woof"
}
}
let pets: Slice<Animal> = [Cat {}, Dog {}]

A bound does the opposite. Every element must be the same concrete type:

fn speak_all<T: Animal>(pets: Slice<T>) -> string {
pets.fold("", |sounds, pet| sounds + pet.speak())
}
let cats = [Cat {}, Cat {}]
speak_all(cats)T is Cat

A Go interface holds a type and a value, so it has three states, not two:

var p *MyHandler = nil
var h1 http.Handleruntyped nil
var h2 http.Handler = ptyped nil
var h3 http.Handler = &MyHandler{}non-nil
handler.go

A literal encoding would distinguish all three:

match handler {
None => fmt.Println("untyped nil"),
Some(None) => fmt.Println("typed nil"),
Some(Some(h)) => h.ServeHTTP(w, r),
}

Lisette collapses both typed nil and untyped nil into None. Neither state holds a value, and a method call on either panics once it touches the receiver. Go allows one exception: a method that never reads through its receiver runs on a typed nil. Lisette treats this exception as too rare in practice to model.

match handler {
Some(h) => h.ServeHTTP(w, r),
None => fmt.Println("no handler"),
}