Skip to main content

Traits

Traits define shared behavior that types can implement. Oxide uses Rust-style trait definitions with Swift-style extension blocks for implementations.

Defining Traits

trait Drawable {
fn draw()
fn width(): Int
}

trait Printable {
fn description(): String
}

Transpiles to:

trait Drawable {
fn draw(&self);
fn width(&self) -> isize;
}

trait Printable {
fn description(&self) -> String;
}

Implementing Traits

Use extension Type: Trait to implement a trait:

struct Circle(radius: Int)

extension Circle: Drawable {
fn draw() {
println!("Drawing circle with radius $self.radius")
}

fn width(): Int { self.radius * 2 }
}

Transpiles to:

impl Drawable for Circle {
fn draw(&self) {
println!("Drawing circle with radius {}", self.radius);
}

fn width(&self) -> isize { self.radius * 2 }
}

Inherent Implementations

Add methods to a type without a trait:

extension Circle {
fn area(): Float64 { 3.14159 * (self.radius as Float64).pow(2.0) }

fn scale(factor: Int): Circle { Circle(self.radius * factor) }
}

Transpiles to:

impl Circle {
fn area(&self) -> f64 { 3.14159 * (self.radius as f64).powi(2) }

fn scale(&self, factor: isize) -> Circle { Circle { radius: self.radius * factor } }
}

Generic Traits

trait Container<T> {
fn get(): T?
mutating fn set(value: T)
fn isEmpty(): Bool
}

extension Vec<T>: Container<T> {
fn get(): T? { self.first().cloned() }

mutating fn set(value: T) {
self.push(value)
}

fn isEmpty(): Bool { self.len() == 0 }
}

Supertraits

Require a trait to implement other traits:

trait PrintableDrawable: Drawable + Printable {
fn printAndDraw() {
println!(self.description())
self.draw()
}
}

Transpiles to:

trait PrintableDrawable: Drawable + Printable {
fn print_and_draw(&self) {
println!("{}", self.description());
self.draw();
}
}

Associated Types

Traits can define associated types:

trait Iterator {
type Item
fn next(): Self.Item?
}

trait Collection {
type Element: Clone
fn first(): Self.Element?
}

trait Container {
type Item = Int // Default type
fn get(): Self.Item
}

Transpiles to:

trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}

trait Collection {
type Element: Clone;
fn first(&self) -> Option<Self::Element>;
}

trait Container {
type Item = isize;
fn get(&self) -> Self::Item;
}

Implementing Associated Types

struct Counter(current: Int, max: Int)

extension Counter: Iterator {
type Item = Int

mutating fn next(): Self.Item? {
if self.current < self.max {
let val = self.current
self.current += 1
return val
}
null
}
}

Using Associated Types

Use dot syntax to access associated types:

fn <T: Iterator> processNext(iter: &var T): T.Item? {
iter.next()
}

Transpiles to:

fn process_next<T: Iterator>(iter: &mut T) -> Option<T::Item> {
iter.next()
}

Trait Aliases

Create shorthand for common trait combinations:

trait Printable = Debug + Display

trait SerdeCompat = Serialize + Deserialize + Clone + Default

public trait SendSync = Send + Sync

Transpiles to:

trait Printable = Debug + Display;

trait SerdeCompat = Serialize + Deserialize + Clone + Default;

pub trait SendSync = Send + Sync;
note

Trait aliases require Rust nightly with the trait_alias feature.

Opaque Return Types

Use some Trait for opaque return types:

fn makeDrawable(): some Drawable {
Circle(10)
}

fn createIterator(): some Iterator<Item = Int> {
(0..10).into_iter()
}

Transpiles to:

fn make_drawable() -> impl Drawable {
Circle { radius: 10 }
}

fn create_iterator() -> impl Iterator<Item = isize> {
(0..10).into_iter()
}

Trait Objects (dyn)

For runtime polymorphism:

fn drawAll(items: &[&dyn Drawable]) {
for item in items {
item.draw()
}
}

// Multiple bounds with parentheses
fn process(value: &dyn (Clone + Debug)) {
// ...
}

Transpiles to:

fn draw_all(items: &[&dyn Drawable]) {
for item in items {
item.draw();
}
}

fn process(value: &dyn (Clone + Debug)) {
// ...
}

Default Method Implementations

Traits can provide default implementations:

trait Greetable {
fn name(): String

fn greet(): String { "Hello, ${self.name()}!" }

fn shout(): String { self.greet().uppercased() }
}

Static Methods and Constants

extension Circle {
// Associated constant
const PI: Float64 = 3.14159265359

// Static method (no self)
static fn unit(): Circle { Circle(1) }

// Instance method (has self)
fn circumference(): Float64 = 2.0 * Circle.PI * (self.radius as Float64)
}

Transpiles to:

impl Circle {
const PI: f64 = 3.14159265359;

fn unit() -> Circle { Circle { radius: 1 } }

fn circumference(&self) -> f64 { 2.0 * Circle::PI * (self.radius as f64) }
}

See Also