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
- Data Structures - Structs and enums
- Ownership - Method modifiers (mutating, consuming)
- Modules - Visibility and organization